首頁 > 軟體

大三Java後端暑期實習面經總結——Java基礎篇

2021-05-27 08:30:14

博主現在大三在讀,從三月開始找暑期實習,暑假準備去tx實習啦!總結下了很多面試真題,希望能幫助正在找工作的大家!相關參考都會標註原文連結,尊重原創!

目錄

1. JDK、JRE、JVM的區別和聯絡2. 採用位元組碼的好處3. 介面和抽象類的區別4. 面向物件的四大特性5. 面向物件和麵向過程6. 靜態繫結&動態繫結7. 過載和重寫8. Java異常體系9. final關鍵字10. String、StringBuilder、StringBuffer11. 單例模式12. 工廠模式和建造者模式的區別13. 深拷貝和淺拷貝14. 泛型知識15. Java泛型的原理?什麼是泛型擦除機制?16. Java編譯器具體是如何擦除泛型的17. Array陣列中可以用泛型嗎?18. PESC原則&限定通配符和非限定通配符19. Java中List<?>和List的區別20. for迴圈和forEach效率問題16. NIO、BIO、AIO17. 什麼是反射18. 序列化&反序列化19. 動態代理是什麼?有哪些應用?20. 怎麼實現動態代理21. 如何實現物件克隆?

參考:

https://zhuanlan.zhihu.com/p/64147696https://www.bilibili.com/video/BV1Eb4y1R7zd?p=2&t=4948

1. JDK、JRE、JVM的區別和聯絡

JDK(java程式開發包)=JRE +Tools

JRE=JVM(虛擬機器)+API

2. 採用位元組碼的好處

Java中引入了jvm,即在機器和編譯程式之間加了一層抽象的虛擬機器器,這臺機器在任何平臺上都提供給編譯程式一個共同的介面。

編譯程式只需要面向虛擬機器,生成虛擬機器能夠理解的程式碼,然後由直譯器來將虛擬機器程式碼轉換為特定系統的機器碼來執行。在Java中,這種供虛擬機器理解的程式碼叫做位元組碼(.class),它不面向任何特定的處理器,只面向虛擬機器每一種平臺的直譯器是不同的,但是實現的虛擬機器是相同的。Java源程式通過編譯器進行編譯後轉換為位元組碼,位元組碼在虛擬機器上執行,虛擬機器將每一條要執行的位元組碼送給直譯器,直譯器將其翻譯成特定機器上的機器碼,然後在特定的機器上運行。這也解釋了Java的編譯與解釋共存的特點。

Java原始碼-->編譯器-->jvm可執行的java位元組碼-->jvm中直譯器-->機器可執行的二進位制機器碼-->程式運行

Java語言採用位元組碼的方式,一定程度上解決了傳統解釋型語言執行效率低(運行需要解釋環境,速度比編譯的要慢,佔用資源也要多一些)的問題,同時又保留了解釋型語言可移植的特點,所以Java程式運行時很高效,此外,由於位元組碼不針對一種特定的機器,因此Java源程式無需重新編譯即可在不同的計算機上運行,實現一次編譯,多次運行

3. 介面和抽象類的區別

1 從語法上來說

抽象類可以存在普通成員函數,而介面中只能存在public abstract方法。抽象類中的成員變數可以是各種類型的,而介面中的成員變數只能是public static final類型的。抽象類只能繼承一個,介面可以實現多個

2 從設計目的來說

介面是用來對類的形為進行約束。也就是提供了一種機制,可以強制要求所有的類具有相同的形為,只約束了行為的有無,不限制形為的具體實現抽象類是為了程式碼複用。當不同的類具有相同的行為A,且其中一部分形為B的實現方式一致時,可以讓這些類都派生於一個抽象類,這個抽象類中實現了B,避免讓所有的子類來實現B,以此來達到程式碼複用的目的。而A-B的部分,交給各個子類自己實現,正是由於這裡A-B的行為沒有實現,所以抽象類不允許例項化

3 從本質上來說

介面是對行為的抽象,表達的是like a的關係,比如 Bird like a Aircraft(鳥像飛行器一樣可以飛);介面的核心是定義行為,即介面的實現類可以做什麼,至於實現類如何實現,主體是誰,介面並不關心抽象類是 對類本質的抽象,表達的是is a的關係,比如 BaoMa is a Car(寶馬是一輛車);抽象類包含並實現子類的通用特性,將子類存在差異化的特性進行抽象,交給子類去實現

總結:

當你關注一個事物的本質的時候,用抽象類;當你關注一個操作的時候,用介面。抽象類的功能要遠超過介面,但是,定義抽象類的代價高。因為高階語言來說(從實際設計上來說也是)每個類只能繼承一個類。在這個類中,你必須繼承或編寫出其所有子類的所有共性。雖然介面在功能上會弱化許多,但是它只是針對一個動作的描述。而且你可以在一個類中同時實現多個介面。在設計階段會降低難度

4. 面向物件的四大特性

1 抽象

將一類物件的共同特徵總結出來構造類的過程

2 封裝

將過程和資料包圍起來,對資料的訪問只能通過特定的介面(例如私有變數的get/set方法)

3 繼承

從現有類派生出新類的過程

4 多型

編譯時多型:同一方法可根據物件的不同產生不同的效果,也就是 方法的過載運行時多型:父類的引用指向子類物件,一個物件的實際類型確定,但是指向其的引用類型可以有很多

5. 面向物件和麵向過程

面向過程(Procedure Oriented)和麵向物件(Object Oriented,OO)都是對軟體分析、設計和開發的一種思想,它指導著人們以不同的方式去分析、設計和開發軟體。早期先有面向過程思想,隨著軟體規模的擴大,問題複雜性的提高,面向過程的弊端越來越明顯的顯示出來,出現了面向物件思想併成為目前主流的方式。兩者都貫穿於軟體分析、設計和開發各個階段,對應面向物件就分別稱為面向物件分析(OOA)、面向物件設計(OOD)和麵向物件程式設計(OOP)。C語言是一種典型的面向過程語言,Java是一種典型的面嚮物件語言。

面向物件和麵向過程是兩種不同的處理問題角度

面向過程注重事情的每一步以及順序面向過程諸眾事情有哪些參與者(物件),以及各自需要做什麼

比如:洗衣機洗衣服

面向過程會將任務拆解成一系列的步驟(函數):1、開啟洗衣機–>2、放衣服–>3、放洗衣粉–>4、清洗–>5、烘乾面向物件會拆出人和洗衣機兩個物件:人:開啟洗衣機放衣服放洗衣粉洗衣機:清洗烘乾

由此可見,面向過程比較直接高效,而面向物件更易於複用、擴展和維護

面向物件和麵向過程的總結

都是解決問題的思維方式,都是程式碼組織的方式。解決簡單問題可以使用面向過程解決複雜問題:宏觀上使用面向物件把握,微觀處理上仍然是面向過程。面向物件具有三大特徵:封裝性、繼承性和多型性,而面向過程沒有繼承性和多型性,並且面向過程的封裝只是封裝功能,而面向物件可以封裝資料和功能。所以面向物件優勢更明顯

6. 靜態繫結&動態繫結

在Java方法呼叫的過程中,JVM是如何知道呼叫的是哪個類的方法原始碼呢?這就涉及到程式繫結,程式繫結指的是一個方法的呼叫與方法所在的類(方法主體)關聯起來。對Java來說,繫結分為靜態繫結和動態繫結,或者叫做前期繫結和後期繫結。

1 靜態繫結

針對Java,可以簡單地理解為程式編譯期的繫結。

這裡特別說明一點,Java當中的方法只有final,static,private和構造方法是靜態繫結。

# 關於final,static,private和構造方法是前期繫結的理解: 對於private的方法,首先一點它不能被繼承,既然不能被繼承那麼就沒辦法通過它子類的物件來呼叫,而只能通過這個類自身的物件來呼叫。因此就可以說private方法和定義這個方法的類繫結在了一起。 final方法雖然可以被繼承,但不能被重寫(覆蓋),雖然子類物件可以呼叫,但是呼叫的都是父類中所定義的那個final方法,(由此我們可以知道將方法聲明為final類型,一是為了防止方法被覆蓋,二是為了有效地關閉java中的動態繫結)。 構造方法也是不能被繼承的(網上也有說子類無條件地繼承父類的無參數建構函式作為自己的建構函式,不過個人認為這個說法不太恰當,因為我們知道子類是通過super()來呼叫父類的無參構造方法,來完成對父類的初始化, 而我們使用從父類繼承過來的方法是不用這樣做的,因此不應該說子類繼承了父類的構造方法),因此編譯時也可以知道這個構造方法到底是屬於哪個類。 對於static方法,具體的原理我也說不太清。不過根據網上的資料和我自己做的實驗可以得出結論:static方法可以被子類繼承,但是不能被子類重寫(覆蓋),但是可以被子類隱藏。(這裡意思是說如果父類裡有一個static方法,它的子類裡如果沒有對應的方法,那麼當子類物件呼叫這個方法時就會使用父類中的方法。而如果子類中定義了相同的方法,則會呼叫子類的中定義的方法。唯一的不同就是,當子類物件上轉型為父類物件時,不論子類中有沒有定義這個靜態方法,該物件都會使用父類中的靜態方法。因此這裡說靜態方法可以被隱藏而不能被覆蓋。這與子類隱藏父類中的成員變數是一樣的。隱藏和覆蓋的區別在於,子類物件轉換成父類物件後,能夠訪問父類被隱藏的變數和方法,而不能訪問父類被覆蓋的方法) 由上面我們可以得出結論,如果一個方法不可被繼承或者繼承後不可被覆蓋,那麼這個方法就採用的靜態繫結。

2 動態繫結

在運行時根據具體物件的類型進行繫結。也就是說,編譯器此時依然不知道物件的類型,但方法呼叫機制能自己去調查,找到正確的方法主體

動態繫結的過程:

虛擬機器提取物件的實際類型的方法表;虛擬機器搜尋方法簽名呼叫方法

7. 過載和重寫

重寫:發生在父子類中,方法名、參數列表必須相同;子類的返回值範圍小於等於父類,拋出異常範圍小於等於父類,訪問修飾符範圍大於等於父類;如果父類方法訪問修飾符為private則子類不能重寫該方法。

過載:發生在同一個類中,參數類型不同、個數不同、順序不同都可以構成過載;

過載方法的返回值可以不同,但是不能僅僅返回值不同,否則編譯時報錯過載方法的訪問控制符也可以不同,但是不能僅僅訪問控制符不同,否則編譯時報錯

8. Java異常體系

Java中的所有異常都來自頂級父類Throwable,Throwable有兩個子類Exception和Error

Error是程式無法處理的錯誤,一旦出現錯誤,則程式將被迫停止運行Exception不會導致程式停止,又分為RunTimeException和CheckedExceptionRunTimeException常常發生在程式運行過程中,會導致程式當前執行緒執行失敗//除0錯誤:ArithmeticException//錯誤的強制類型轉換錯誤:ClassCastException//陣列索引越界:ArrayIndexOutOfBoundsException//使用了空物件:NullPointerExceptionCheckedException常常發生在程式編譯過程中,會導致程式編譯不通過例如:開啟不存在的檔案

9. final關鍵字

1.作用

修飾類:表示類不可被繼承修飾方法:表示方法不可被子類覆蓋,但是可以過載修飾變數:表示變數一旦被賦值就不可以更改它的值

2.修飾不同變數的區別

1 修飾成員變數

如果final修飾的是類變數,只能在靜態初始化塊中指定初始值或者聲明該類變數時指定初始值。如果final修飾的是成員變數,可以在非靜態初始化塊、聲明該變數或者構造器中執行初始值。

2 修飾局部變數

系統不會為局部變數進行初始化,局部變數必須由程式設計師顯示初始化。因此使用final修飾局部變數時,即可以在定義時指定預設值(後面的程式碼不能對變數再賦值),也可以不指定預設值,而在後面的程式碼中對final變數賦初值(僅一次) 3 修飾基本資料類型和引用類型資料

如果是基本資料類型的變數,則其數值一旦在初始化之後便不能更改如果是引用類型的變數,則在對其初始化之後便不能再讓其指向另一個物件。但是引用的值可變。

2.為什麼局部內部類和匿名內部類只能訪問局部final變數

局部內部類或匿名內部類編譯之後會產生兩個class檔案:Test.class、Test$1.class,一個是類class,一個是內部類class 局部內部類: 首先需要知道的一點是:內部類和外部類是處於同一個級別的,內部類不會因為定義在方法中就會隨著方法的執行完畢就被銷燬。

這裡就會產生問題:當外部類的方法結束時,局部變數就會被銷燬了,但是內部類物件可能還存在(只有沒有人再引用它時,才會死亡),這裡就出現了一個矛盾:內部類物件訪問了一個不存在的變數。為了解決這個問題,就將局部變數複製了一份作為內部類的成員變數,這樣當局部變數死亡後,內部類仍可以訪問它,實際訪問的是局部變數的"copy".這樣就好像延長了局部變數的生命週期

將局部變數複製為內部類的成員變數時,必須保證這兩個變數是一樣的,也就是如果我們在內部類中修改了成員變數,方法中的局部變數也得跟著改變,怎麼解決問題呢?

就將局部變數設定為final,對它初始化後,我就不讓你再去修改這個變數,就保證了內部類的成員變數和方法的局部變數的一致性。這實際上也是一種妥協。使得局部變數與內部類內建立的拷貝保持一致。

10. String、StringBuilder、StringBuffer

String底層是final修飾的char[]陣列,不可變,每次操作都會產生新的String物件StringBuffer和StringBuilder都是在原物件上操作StringBuffer執行緒安全(所有方法都用synchronized修飾),StringBuilder執行緒不安全

效能:StringBuilder>StringBuffer>String

使用場景:經常需要改變字元串內容時使用後面兩個,優先使用 StringBuilder,多執行緒使用共享變數時使用 StringBuffer

11. 單例模式

徹底玩轉單例模式

12. 工廠模式和建造者模式的區別

工廠模式一般都是創建一個產品,注重的是把這個產品創建出來,而不關心這個產品的組成部分。從程式碼上看,工廠模式就是一個方法,用這個方法來生產出產品建造者模式也是創建一個產品,但是不僅要把這個產品創建出來,還要關心這個產品的組成細節,組成過程。從程式碼上看,建造者模式在創建產品的時候,這個產品有很多方法,建造者模式會根據這些相同的方法按不同的執行順序建造出不同組成細節的產品

13. 深拷貝和淺拷貝

淺拷貝:複製物件時只複製物件本身,包括基本資料類型的屬性,但是不會複製引用資料類型屬性指向的物件,即拷貝物件的與原物件的引用資料類型的屬性指向同一個物件淺拷貝沒有達到完全複製,即原物件與克隆物件之間有關係,會相互影響深拷貝:複製一個新的物件,引用資料類型指向物件會拷貝新的一份,不再指向原有引用物件的地址深拷貝達到了完全複製的目的,即原物件與克隆物件之間不會相互影響

14. 泛型知識

Java泛型深度解析以及面試題_周將的部落格-CSDN部落格

Java泛型是在JDK5引入的新特性,它提供了編譯時類型安全檢測機制。該機制允許程式設計師在編譯時檢測到非法的類型,泛型的本質是參數類型。

1 使用泛型的好處

泛型可以增強編譯時錯誤檢測,減少因類型問題引發的運行時異常。泛型可以避免類型轉換。泛型可以泛型演算法,增加程式碼複用性。

2 Java中泛型的分類

泛型類:它的定義格式是class name<T1, T2, ..., Tn>,如下, 返回一個物件中包含了code和一個data, data是一個物件,我們不能固定它是什麼類型,這時候就用T泛型來代替,大大增加了程式碼的複用性。publicclassResult<T>{ private T data;privateint code;public T getData(){ return data;}publicvoidsetData(T data){ this.data = data;}}泛型介面:和泛型類使用相似泛型方法:它的定義是[public] [static] <T> 返回值類型 方法名(T 參數列表),只有在前面加<T>這種的才能算是泛型方法,比如上面的setData方法雖然有泛型,但是不能算泛型方法

3 常見的泛型參數

K 鍵V 值N 數字T 類型E 元素S, U, V 等,泛型聲明的多個類型

4 鑽石運算符Diamond

鑽石操作符是在 java 7 中引入的,可以讓程式碼更易讀,但它不能用於匿名的內部類。在 java 9 中, 它可以與匿名的內部類一起使用,從而提高程式碼的可讀性。

JDK7以下版本需要 Box<Integer> box = new Box<Integer>();JDK7及以上版本 Box<Integer> integerBox1 = new Box<>();

5 受限類型參數

它的作用是對泛型變數的範圍作出限制,格式:單一限制:<U extends Number>多種限制:<U extends A & B & C>多種限制的時候,類必須寫在第一個

6 通配符

通配符用?標識,分為受限制的通配符和不受限制的通配符,它使程式碼更加靈活,廣泛運用於框架中。

比如List<Number>和List<Integer>是沒有任何關係的。如果我們將print方法中參數列表部分的List聲明為List<Number> list, 那麼編譯是不會通過的,但是如果我們將List定義為List<? extends Number> list或者List<?> list,那麼在編譯的時候就不會報錯了

受限制的通配符:語法為<? extends XXX>,它可以擴大相容的範圍(XXX以及它的子類)比如上面例子中print中如果改為List<Number>,雖然它能儲存Integer和Double等類型的元素,但是作為參數傳遞的時候,它只能接受List<Number>這一種類型。如果聲明為List<? extends Number> list就不一樣了,相當於擴大了類型的範圍,使得程式碼更加的靈活,程式碼複用性更高。<? super T>和extends一樣,只不過extends是限定了上限,而super是限定了下限非受限制的通配符:不適用關鍵字extends或者super。比如上面print參數列表聲明為List<?> list也可以解決問題。?代表了未知類型。所以所有的類型都可以理解為List<?>的子類。它的使用場景一般是泛型類中的方法不依賴於類型參數的時候,比如list.size(), 遍歷集合等,這樣的話並不關心List中元素的具體類型。

7 泛型中的PECS原則

PECS原則的全拼是"Producer Extends Consumer Super"。當需要頻繁取值,而不需要寫值則使用上界通配符? extends T作為資料結構泛型。=相反,當需要頻繁寫值,而不需要取值則使用下屆通配符? super T作為資料結構泛型。

案例分析:創建Apple,Fruit兩個類,其中Apple是Fruit的子類

public class PECS { ArrayList<? extends Fruit> exdentFurit; ArrayList<? super Fruit> superFurit; Apple apple = new Apple(); private void test() { Fruit a1 = exdentFurit.get(0); Fruit a2 = superFurit.get(0); //Err1 exdentFurit.add(apple); //Err2 superFurit.add(apple); }}

其中Err1和Err2行處報錯,因為這些操作並不符合PECS原則,逐一分析:

Err1 使用? super T規定泛型的資料結構,其儲存的值是T的父類,而這裡superFruit.get()的物件為Fruit的父類物件,而指向該物件的引用類型為Fruit,父類缺少子類中的一些資訊,這顯然是不對的,因此編譯器直接禁止在使用? super T泛型的資料結構中進行取值,只能進行寫值,正是開頭所說的CS原則。Err2 使用? extends T規定泛型的資料結構,其儲存的值是T的子類,這裡exdentFruit.add()也就是向其中新增Fruit的子類物件,而Fruit可以有多種子類物件,因此當我們進行寫值時,我們並不知道其中儲存的到底是哪個子類,因此寫值操作必然會出現問題,所以編譯器接禁止在使用? extends T泛型的資料結構中進行寫,只能進行取值,正是開頭所說的PE原則。

8 類型擦除

類型擦除作用:因為Java中的泛型實在JDK1.5之後新加的特性,為了相容性,在虛擬機器中運行時是不存在泛型的,所以Java泛型是一種偽泛型,類型擦除就保證了泛型不在運行時候出現。場景:編譯器會把泛型類型中所有的類型參數替換為它們的上(下)限,如果沒有對類型參數做出限制,那麼就替換為Object類型。因此,編譯出的位元組碼僅僅包含了常規類,介面和方法。在必要時插入類型轉換以保持類型安全。生成橋方法以在擴展泛型時保持多型性Bridge Methods 橋方法 當編譯一個擴展參數化類的類,或一個實現了參數化介面的介面時,編譯器有可能因此要創建一個合成方法,名為橋方法。它是類型擦除過程中的一部分。下面對橋方法程式碼驗證一下:publicclassNode<T>{ T t;publicNode(T t){ this.t = t;}publicvoidset(T t){ this.t = t;}}classMyNodeextendsNode<String>{ publicMyNode(String s){ super(s);}@Overridepublicvoidset(String s){ super.set(s);}上面Node<T>是一個泛型類型,沒有聲明上下限,所以在類型擦除後會變為Object類型。而MyNode類已經聲明瞭實際類型參數為String類型,這樣在呼叫父類set方法的時候就會出現不匹配的情況,所以虛擬機器在編譯的時候為我們生成了一個橋方法,我們通過javap -c MyNode.class檢視位元組碼檔案,看到確實為我們生成了一個橋方法

15. Java泛型的原理?什麼是泛型擦除機制?

Java的泛型是JDK5新引入的特性,為了向下相容,虛擬機器其實是不支援泛型,所以Java實現的是一種偽泛型機制,也就是說Java在編譯期擦除了所有的泛型資訊,這樣Java就不需要產生新的類型到位元組碼,所有的泛型類型最終都是一種原始類型,在Java運行時根本就不存在泛型資訊。類型擦除其實在類常量池中儲存了泛型資訊,運行時還能拿到資訊,比如Gson的TypeToken的使用。泛型演算法實現的關鍵:利用受限類型參數。

16. Java編譯器具體是如何擦除泛型的

檢查泛型類型,獲取目標類型擦除類型變數,並替換為限定類型 如果泛型類型的類型變數沒有限定,則用Object作為原始類型如果有限定,則用限定的類型作為原始類型如果有多個限定(T extends Class1&Class2),則使用第一個邊界Class1作為原始類在必要時插入類型轉換以保持類型安全生成橋方法以在擴展時保持多型性

17. Array陣列中可以用泛型嗎?

不能,簡單的來講是因為如果可以創建泛型陣列,泛型擦除會導致編譯能通過,但是運行時會出現異常。所以如果禁止創建泛型陣列,就可以避免此類問題。

18. PESC原則&限定通配符和非限定通配符

如果你只需要從集合中獲得類型T , 使用<? extends T>通配符如果你只需要將類型T放到集合中, 使用<? super T>通配符如果你既要獲取又要放置元素,則不使用任何通配符。例如List<String><?> 非限定通配符既不能存也不能取, 一般使用非限定通配符只有一個目的,就是為了靈活的轉型。其實List<?> 等於 List<? extends Object>。

19. Java中List<?>和List<Object>的區別

雖然他們都會進行類型檢查,實質上卻完全不同。List<?> 是一個未知類型的List,而List<Object>其實是任意類型的List。你可以把List<String>, List<Integer>賦值給List<?>,卻不能把List<String>賦值給List<Object>。

20. for迴圈和forEach效率問題

== 遍歷ArrayList測試==

這裡向ArrayList中插入10000000條資料,分別用for迴圈和for each迴圈進行遍歷測試

packagefor迴圈效率問題;import java.util.ArrayList;publicclassTest{ publicstaticvoidmain(String[] args){ ArrayList<Integer> arrayList =newArrayList<>();for(int i =; i <10000000; i++){ arrayList.add(i);}int x =;//for迴圈遍歷 long forStart = System.currentTimeMillis();for(int i =; i < arrayList.size(); i++){ x = arrayList.get(i);}long forEnd = System.currentTimeMillis(); System.out.println("for迴圈耗時"+(forEnd - forStart)+"ms");//for-each遍歷 long forEachStart = System.currentTimeMillis();for(int i : arrayList){ x = i;}long forEachEnd = System.currentTimeMillis(); System.out.println("foreach耗時"+(forEachEnd - forEachStart)+"ms");}}

根據執行結果,可以看到for迴圈速度更快一點,但是差別不太大

我們反編譯class檔案看看

packagefor迴圈效率問題;import java.util.ArrayList;import java.util.Iterator;publicclassTest{ publicTest(){ }publicstaticvoidmain(String[] args){ ArrayList<Integer> arrayList =newArrayList();int x;for(x =; x <10000000;++x){ arrayList.add(x);}int x =false;long forStart = System.currentTimeMillis();for(int i =; i < arrayList.size();++i){ x =(Integer)arrayList.get(i);}long forEnd = System.currentTimeMillis(); System.out.println("for迴圈耗時"+(forEnd - forStart)+"ms");long forEachStart = System.currentTimeMillis();int i;for(Iterator var9 = arrayList.iterator(); var9.hasNext(); i =(Integer)var9.next()){ }long forEachEnd = System.currentTimeMillis(); System.out.println("foreach耗時"+(forEachEnd - forEachStart)+"ms");}}

可以看到增強for迴圈本質上就是使用iterator迭代器進行遍歷

== 遍歷LinkedList測試==

這裡向LinkedList中插入測試10000條資料進行遍歷測試,實驗中發現如果迴圈次數太大,for迴圈直接卡死;

packagefor迴圈效率問題;import java.util.LinkedList;publicclassTest2{ publicstaticvoidmain(String[] args){ LinkedList<Integer> linkedList =newLinkedList<>();for(int i =; i <10000; i++){ linkedList.add(i);}int x =;//for迴圈遍歷 long forStart = System.currentTimeMillis();for(int i =; i < linkedList.size(); i++){ x = linkedList.get(i);}long forEnd = System.currentTimeMillis(); System.out.println("for迴圈耗時"+(forEnd - forStart)+"ms");//for-each遍歷long forEachStart = System.currentTimeMillis();for(int i : linkedList){ x = i;}long forEachEnd = System.currentTimeMillis(); System.out.println("foreach耗時"+(forEachEnd - forEachStart)+"ms");}}

根據結果可以看到,遍歷LinkedList時for each速度遠遠大於for迴圈速度

反編譯class檔案的源碼

Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//packagefor迴圈效率問題;import java.util.Iterator;import java.util.LinkedList;publicclassTest2{ publicTest2(){ }publicstaticvoidmain(String[] args){ LinkedList<Integer> linkedList =newLinkedList();int x;for(x =; x <10000;++x){ linkedList.add(x);}int x =false;long forStart = System.currentTimeMillis();for(int i =; i < linkedList.size();++i){ x =(Integer) linkedList.get(i);}long forEnd = System.currentTimeMillis(); System.out.println("for迴圈耗時"+(forEnd - forStart)+"ms");long forEachStart = System.currentTimeMillis();int i;for(Iterator var9 = linkedList.iterator(); var9.hasNext(); i =(Integer) var9.next()){ }long forEachEnd = System.currentTimeMillis(); System.out.println("foreach耗時"+(forEachEnd - forEachStart)+"ms");}}

== 總結 ==

1 區別:

for 迴圈就是按順序遍歷,隨機訪問元素for each迴圈本質上是使用iterator迭代器遍歷,順序連結串列訪問元素;

2 效能比對:

對於arraylist底層為陣列類型的結構,使用for迴圈遍歷比使用foreach迴圈遍歷稍快一些,但相差不大對於linkedlist底層為單鏈表類型的結構,使用for迴圈每次都要從第一個元素開始遍歷,速度非常慢;使用foreach可以直接讀取當前結點,速度比for快很多

3 原理接釋:

ArrayList陣列類型結構對隨機訪問比較快,而for迴圈中的get()方法,採用的即是隨機訪問的方法,因此在ArrayList裡,for迴圈較快# 順序表a[3]- 用for迴圈,從a[0]開始直接讀到元素,接著直接讀a[1](順序表的優點,隨機訪問)- 用foreach,得到a[0]-a[2]的全部地址放入佇列,按順序取出隊裡裡的地址來訪問元素LinkedList連結串列形結構對順序訪問比較快,iterator中的next()方法,採用的即是順序訪問的方法,因此在LinkedList裡,使用iterator較快# 單鏈表b[3]- 用for迴圈,從a[0]開始讀元素、然後通過a[0]的next讀到a[1]元素、通過a[0]的next的next讀到a[2]元素,以此類推,效能影響較大,慎用!- 用foreach,得到a[0]-a[2]的全部地址放入佇列,按順序取出隊裡裡的地址來訪問元素;

16. NIO、BIO、AIO

(1條訊息) Netty_youthlql的部落格-CSDN部落格

尚矽谷Netty教程(B站最火,人氣最高,好評如潮)_嗶哩嗶哩 (゜-゜)つロ 乾杯~-bilibili

阻塞IO 和 非阻塞IO

IO操作分為兩個部分,即發起IO請求和實際IO操作,阻塞IO和非阻塞IO的區別就在於第二個步驟是否阻塞

若發起IO請求後請求執行緒一直等待實際IO操作完成,則為阻塞IO若發起IO請求後請求執行緒返回而不會一直等待,則為非阻塞IO

同步IO 和 非同步IO

IO操作分為兩個部分,即發起IO請求和實際IO操作,同步IO和非同步IO的區別就在於第一個步驟是否阻塞

若實際IO操作阻塞請求程序,即請求程序需要等待或輪詢檢視IO操作是否就緒,則為同步IO若實際IO操作不阻塞請求程序,而是由作業系統來進行實際IO操作並將結果返回,則為非同步IO

NIO、BIO、AIO

BIO表示同步阻塞式IO,伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,當然可以通過執行緒池機制改善。

NIO表示同步非阻塞IO,伺服器實現模式為一個請求一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。

AIO表示非同步非阻塞IO,伺服器實現模式為一個有效請求一個執行緒,客戶端的I/O請求都是由作業系統先完成IO操作後再通知伺服器應用來啟動執行緒進行處理。

17. 什麼是反射

反射是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為 Java 語言的反射機制。

反射實現了把java類中的各種結構法、屬性、構造器、類名)對映成一個個的Java物件

優點:可以實現動態創建物件和編譯,體現了很大的靈活性

缺點:對效能有影響,使用反射本質上是一種接釋操作,慢於直接執行java程式碼

應用場景:

JDBC中,利用反射動態載入了資料庫驅動程式。Web伺服器中利用反射呼叫了Sevlet的服務方法。Eclispe等開發工具利用反射動態刨析物件的類型與結構,動態提示物件的屬性和方法。很多框架都用到反射機制,注入屬性,呼叫方法,如Spring。

18. 序列化&反序列化

Java基礎學習總結——Java物件的序列化和反序列化 - 孤傲蒼狼 - 部落格園 (cnblogs.com)

1 什麼是序列化?

序列化是指將Java物件轉化為位元組序列的過程,而反序列化則是將位元組序列轉化為Java物件的過程

2 為什麼需要序列化?

我們知道不同執行緒/程序進行遠端通訊時可以相互發送各種資料,包括文字圖片音視訊等,Java物件不能直接傳輸,所以需要轉化為二進位制序列傳輸,所以需要序列化

3 序列化的用途?

把物件的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中在很多應用中,需要對某些物件進行序列化,讓它們離開記憶體空間,入住物理硬碟,以便長期儲存。比如最常見的是Web伺服器中的Session物件,當有10萬用戶併發訪問,就有可能出現10萬個Session物件,記憶體可能吃不消,於是Web容器就會把一些seesion先序列化到硬碟中,等要用了,再把儲存在硬碟中的物件還原到記憶體中在網路上傳送物件的位元組序列當兩個程序在進行遠端通訊時,彼此可以傳送各種類型的資料。無論是何種類型的資料,都會以二進位制序列的形式在網路上傳送。傳送方需要把這個Java物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為Java物件

4 JDK類庫中的序列化API

java.io.ObjectOutputStream代表物件輸出流,它的writeObject(Object obj)方法可對參數指定的obj物件進行序列化,把得到的位元組序列寫到一個目標輸出流中java.io.ObjectInputStream代表物件輸入流,它的readObject()方法從一個源輸入流中讀取位元組序列,再把它們反序列化為一個物件,並將其返回

只有實現了Serializable和Externalizable介面的類的物件才能被序列化。Externalizable介面繼承自Serializable介面,實現Externalizable介面的類完全由自身來控制序列化的行為,而僅實現Serializable介面的類可以 採用預設的序列化方式

物件序列化包括如下步驟:

創建一個物件輸出流,它可以包裝一個其他類型的目標輸出流,如檔案輸出流通過物件輸出流的writeObject()方法寫物件

物件反序列化的步驟如下:

創建一個物件輸入流,它可以包裝一個其他類型的源輸入流,如檔案輸入流通過物件輸入流的readObject()方法讀取物件

5 serialVersionUID的作用

serialVersionUID: 字面意思上是序列化的版本號,凡是實現Serializable介面的類都有一個表示序列化版本標識符的靜態變數

如果實現Serializable介面的類如果類中沒有新增serialVersionUID,那麼就會出現警告提示

serialVersionUID有兩種生成方式:

採用Add default serial version ID方式生成的serialVersionUID是1L,例如:privatestaticfinallong serialVersionUID =1L;採用Add generated serial version ID這種方式生成的serialVersionUID是根據類名,介面名,方法和屬性等來生成的,例如:privatestaticfinallong serialVersionUID =4603642343377807741L;

19. 動態代理是什麼?有哪些應用?

當想要給實現了某個介面的類中的方法,加一些額外的處理。比如說加日誌,加事務等。可以給這個類創建一個代理,故名思議就是創建一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新類。這個代理類並不是定義好的,是動態生成的。具有解耦意義,靈活,擴展性強。

應用:

Spring的AOP加事務加許可權加日誌

20. 怎麼實現動態代理

在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler介面,通過這個類和這個介面可以生成JDK動態代理類和動態代理物件

java.lang.reflect.Proxy是所有動態代理的父類。它通過靜態方法newProxyInstance()來創建動態代理的class物件和例項。每一個動態代理例項都有一個關聯的InvocationHandler。通過代理例項呼叫方法,方法呼叫請求會被轉發給InvocationHandler的invoke方法。

首先定義一個IncocationHandler處理器介面實現類,實現其invoke()方法通過Proxy.newProxyInstance生成代理類物件

package demo3;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;publicclassProxyInvocationHandlerimplementsInvocationHandler{ //定義真實角色private Rent host;//真實角色set方法publicvoidsetHost(Rent host){ this.host = host;}/** 生成代理類方法 1. 類載入器,為當前類即可 2. 代理類實現的介面 3. 處理器介面物件 **/public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), host.getClass().getInterfaces(),this);}//處理代理例項,並返回結果//方法在此呼叫public Object invoke(Object proxy, Method method, Object[] args)throws Throwable { //呼叫真實角色方法,相當於呼叫rent()方法 Object result = method.invoke(host, args);//附加方法seeHouse();contract();fare();return result;}//看房publicvoidseeHouse(){ System.out.println("中介帶你看房");}//籤合同publicvoidcontract(){ System.out.println("租賃合同");}//收中介費publicvoidfare(){ System.out.println("收中介費");}}

package demo3;publicclassClient{ publicstaticvoidmain(String[] args){ //真實角色:房東 Host host =newHost();//處理器介面物件 ProxyInvocationHandler handler =newProxyInvocationHandler();//設定要代理的真實角色 handler.setHost(host);//動態生成代理類 Rent proxy =(Rent) handler.getProxy();//呼叫方法 proxy.rent();}}

21. 如何實現物件克隆?

有兩種方式:

實現Cloneable介面並重寫Object類中的clone()方法;protected Object clone()throws CloneNotSupportedException { test_paper paper =(test_paper)super.clone(); paper.date =(Date) date.clone();return paper;}實現Serializable介面,通過物件的序列化和反序列化實現克隆,可以實現真正的深度克隆import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.Date;@SuppressWarnings("all")publicclassClient{ publicstaticvoidmain(String[] args)throws Exception { Date date =newDate(); String name ="zsr"; test_paper paper1 =newtest_paper(name, date);//通過序列化和反序列化來實現深克隆 ByteArrayOutputStream bos =newByteArrayOutputStream(); ObjectOutputStream obs =newObjectOutputStream(bos); obs.writeObject(paper1);byte a[]= bos.toByteArray(); ObjectInputStream ois =newObjectInputStream(newByteArrayInputStream(a)); test_paper paper3 =(test_paper) ois.readObject();//獲取到新物件 paper3.getDate().setDate(1000);//改變非基本類型屬性 System.out.println(paper1); System.out.println(paper3);}}

,https://blog.csdn.net/qq_45173404/article/details/117201769


IT145.com E-mail:sddin#qq.com