1——面向對象和JVM基礎
1.java中的4種通路制權限:
(1).public:最大通路控制權限,對所有的類都可見。
(2).protect:同一包可見,不在同一個包的所有子類也可見。
(3).default:包通路權限,即同一個包中的類可以可見。預設不顯式指定通路控制權限時就是default包通路控制權限。
(4).private:最嚴格的通路控制權限,僅該類本身可見,對外一切類都不可以通路(反射機制可以通路)。
2.面向對象程式設計中兩種對象組合方式——is-a 和 has-a:
(1).is-a組合:一個類繼承具有相似功能的另一個類,根據需要在所繼承的類基礎上進行擴充。
優點:具有共同屬性和方法的類可以将共享資訊抽象到父類中,增強代碼複用性,同時也是多态的基礎。
缺點:子類中擴充的部分對父類不可見,另外如果共性比較少的時候使用繼承會增加備援代碼。
(2).has-a組合:has-a組合是在一個類中引用另一個類作為其成員變量。
優點:可擴充性和靈活性高。在對象組合關系中應優先考慮has-a組合關系。
缺點:具有共性的類之間看不到派生關系。
3.多态:
在面向對象程式設計中,子類中擁有和父類相同方法簽名的方法稱為子類方法覆寫父類方法,當調用子類方法的某個操作時,不必明确知道子類的具體類型,隻需要将子類類型看作是父類的引用調用其操作方法,在運作時,JVM會根據引用對象的具體子類類型而調用應該的方法,這就是多态。
多态的基礎是java面向對象程式設計的晚綁定機制。程式設計中有如下兩種綁定機制:
(1).早綁定:一般在非面向對象程式設計語言中使用,在程式編譯時即計算出具體調用方法體的記憶體位址。
(2).晚綁定:面向對象程式設計語言中經常使用,在程式編譯時無法計算出具體調用方法體的記憶體位址,隻進行方法參數類型和傳回值類型的校驗,在運作時才能确定具體要調用方法體的記憶體位址。
4.java單繼承的優點:
相比于C++的多繼承,java隻支援類的單繼承,java中的所有類的共同基類是Object類,Object類java類樹的唯一根節點,這種單繼承有以下好處:
(1).單繼承可以確定所有的對象擁有某種共同的特性,這樣對于JVM虛拟機對所有的類進行系統級的操作将提供友善,所有的java對象可以友善地在記憶體堆棧中建立,傳遞參數也變的更加友善簡單。
(2).java的單繼承使得實作垃圾回收器功能更加容易,因為可以確定JVM知道所有對象的類型資訊。
5.選擇容器對象兩個原則:
(1).容器所能提供不同的類型的接口和外部行為是否能夠滿足需求。
(2).不同容器針對不同的操作效率不同。
6.類型轉換:
Java中有兩種常見的類型轉換:向上類型轉換(upcast)和向下類型轉換(downcast):
(1).向上類型轉換(upcast):
向上類型轉換是将子類對象強制類型轉換為父類類型,經典用法是面向對象的多态特性。向上類型轉換時,子類對象的特性将不可見,隻有子類從父類繼承的特性仍然保持可見,向上類型轉換時編譯器會自動檢查是否類型相容,通常是安全的。
(2).向下類型轉換:
向下類型轉換是将父類類型強制轉換為子類類型,轉換過後父類中不可見的子類特性又恢複可見性,向下類型轉換時,編譯器無法自動檢測是否類型相容,往往會産生類型轉換錯誤的運作時異常,通常不安全。
7.java中5個存放資料的地方:
(1).寄存器(Registers):位于CPU内部,是速度最快的存儲區,但是數量和容量有限。在java中不能直接操作寄存器。
(2).棧(Stack):棧位于通用随機通路存儲器 (General random-access memory,RAM,記憶體) 中,通過處理器的棧指針通路,棧指針從棧頂向棧底配置設定記憶體,從棧底向棧頂釋放記憶體。棧是僅次于寄存器的速度第二快的存儲器,在java程式中,一般的8種 基本類型資料和對象的引用通常存放在棧記憶體中,不通過new關鍵字的字元串對象也是存放在棧的字元串池中。棧的優勢是,存取速度比堆要快,僅次于寄存器, 棧資料可以共享。但缺點是,存在棧中的資料大小與生存期必須是确定的,缺乏靈活性。
(3).堆(Heap):也是位于通用随機通路存儲器 (General random-access memory,RAM,記憶體) 中的共享記憶體池。Java的堆是一個運作時資料區,類的對象從中配置設定空間,凡是通過new關鍵字建立的對象都存放在堆記憶體中,它們不需要程式代碼來顯式的 釋放。堆是由垃圾回收來負責的,堆的優勢是可以動态地配置設定記憶體大小,生存期也不必事先告訴編譯器,因為它是在運作時動态配置設定記憶體的,Java的垃圾收集器 會自動收走這些不再使用的資料。但缺點是,由于要在運作時動态配置設定記憶體,存取速度較慢。
(4).常量存儲器(Constant storage):java中的常量是存放在系統内嵌的隻讀存儲器中(read-only memory,ROM)的。
(5).非随機存儲器(Non-RAM storage):對于流對象和持久化對象,通常存放在程式外的存儲器,如硬碟。
8.javadoc隻處理public和protected通路控制權限的文檔注釋,private和default權限的穩定注釋将被忽略。
9.java中指派運算:
基本類型指派是直接複制值,指派操作後,互相不影響。
引用類型指派是複制引用值,相當于給對象取一個别名,指派之後兩個引用指向同一個引用對象,互相之間有影響。
在Java中,向方法傳遞引用類型參數會改變參數的值,不讓參數受到影響的解決方法:在方法内首先先将引用克隆一份,然後操作克隆的對象。
10.移位運算:
左移運算符<<:将比特位左移指定位數,右邊部分補0,左移一位相當于乘2。
右移運算符>>:将比特位右移指定位數,如果是正數,左邊第一位(符号位)補0,其餘位補0,如果是負數,左邊第一位補1,其餘位補0。右移一位相當于除2。
無符号右移運算符>>>:将比特位右移指定位數,不論是正數或者負數,左邊移除位統統補0。
11.java中,比int類型小的原始類型(char、byte、short)進行數學運算或者位運算時,資料類型首先轉換成int類型,然後進行相應的運算。
12.方法重載(overloading):方法同名,參數清單不同稱為方法重載,注意方法的傳回值類型不同不能作為方法重載。
13.java中的析構函數:
Java中沒有像C/C++的析構函數,用來銷毀不用的對象是否記憶體空間,隻有以下三個方法用于通知垃圾回收器回收對象。
(1).finalize( )隻是通知JVM的垃圾收集器目前的對象不再使用可以被回收了,但是垃圾回收器根據記憶體使用狀況來決定是否回收。
finalize()最有用的地方是在JNI調用本地方法時(C/C++方法),調用本地方法的析構函數消耗對象釋放函數。
(2). System.gc()是強制析構,顯式通知垃圾回收器釋放記憶體,但是垃圾回收器也不一定會立即執行,垃圾回收器根據目前記憶體使用狀況和對象的生命周期自行決定是否回收。
(3).RunTime.getRunTime().gc()和System.gc()類似。
注意:這三個函數都不能保證垃圾回收器立即執行,推薦不要頻繁使用。
14.垃圾回收器原理:
(1).引用計數(ReferenceCounting)垃圾回收算法:
一種簡單但是速度較慢的垃圾回收算法,每個對象擁有一個引用計數器(Reference Counter),當每次引用附加到這個對象時,對象的引用計數器加1。當每次引用超出作用範圍或者被設定為null時,對象的引用計數器減1。垃圾回收 器周遊整個對象清單,當發現一個對象的引用計數器為0時,将該對象移出記憶體釋放。
引用計數算法的缺點是,當對象環狀互相引用時,對象的引用計數器總不為0,要想回收這些對象需要額外的處理。
引用計數算法隻是用來解釋垃圾回收器的工作原理,沒有JVM使用它實作垃圾回收器。
引用計數的改進算法:
任何存活的對象必須被在靜态存儲區或者棧(Stack)中的引用所引用,是以當周遊全部靜态存儲區或棧中的引用時,即可以确定所有存活的對象。每當 周遊一個引用時,檢查該引用所指向的對象,同時檢查該對象上的所有引用,沒有引用指向的對象和互相自引用的對象将被垃圾回收器回收。
(2).暫停複制(stop-and-copy)算法:
垃圾回收器的收集機制基于:任何一個存活的對象必須要被一個存儲在棧或者靜态存儲區的引用所引用。
暫停複制的算法是:程式在運作過程中首先暫停執行,把每個存活的對象從一個堆複制到另一個堆中,已經不再被使用的對象被回收而不再複制。
暫停複制算法有兩個問題:
a.必須要同時維護分離的兩個堆,需要程式運作所需兩倍的記憶體空間。JVM的解決辦法是在記憶體塊中配置設定堆空間,複制時簡單地從一個記憶體塊複制到另一個記憶體塊。
b.第二個問題是複制過程的本身處理,當程式運作穩定以後,隻會産生很少的垃圾對象需要回收,如果垃圾回收器還是頻繁地複制存活對象是非常低性能的。JVM的解決方法是使用一種新的垃圾回收算法——标記清除(mark-and-sweep)。
一般來說标記清除算法在正常的使用場景中速度比較慢,但是當程式隻産生很少的垃圾對象需要回收時,該算法就非常的高效。
(3).标記清除(mark-and-sweep)算法:
和暫停複制的邏輯類似,标記清除算法從棧和靜态存儲區開始追蹤所有引用尋找存活的對象,當每次找到一個存活的對象時,對象被設定一個标記并且不被回收,當标記過程完成後,清除不用的死對象,釋放記憶體空間。
标記清除算法不需要複制對象,所有的标記和清除工作在一個記憶體堆中完成。
注意:SUN的文檔中說JVM的垃圾回收器是一個背景運作的低優先級程序,但是在早期版本的JVM中并不是這樣實作的,當記憶體不夠用時,垃圾回收器先暫停程式運作,然後進行垃圾回收。
(4).分代複制(generation-copy)算法:
一種對暫停複制算法的改進,JVM配置設定記憶體是按塊配置設定的,當建立一個大對象時,需要占用一塊記憶體空間,嚴格的暫停複制算法在釋放老記憶體堆之前要求把每個存活的對象從源堆拷貝到新堆,這樣做非常的消耗記憶體。
通過記憶體堆,垃圾回收器可以将對象拷貝到回收對象的記憶體堆中,每個記憶體塊擁有一個世代計數(generation count)用于标記對象是否存活。每個記憶體塊通過對象被引用獲得世代計數,一般情況下隻有當最老的記憶體塊被回收時才會建立新的記憶體塊,這主要用于處理大 量的短存活周期臨時對象回收問題。一次完整的清理過程中,記憶體塊中的大對象不會被複制,隻是根據引用重新獲得世代計數。
JVM監控垃圾回收器的效率,當發現所有的對象都是長時間存活時,JVM将垃圾回收器的收集算法調整為标記清除,當記憶體堆變得零散碎片時,JVM又重新将垃圾回收器的算法切換會暫停複制,這就是JVM的自适應分代暫停複制标記清除垃圾回收算法的思想。
15.java即時編譯技術(JIT):
Java的JIT是just-in-timecomplier技術,JIT技術是java代碼部分地或全部轉換成本地機器碼程式,不再需要JVM解釋,執行速度更快。
當一個”.class”的類檔案被找到時,類檔案的位元組碼被調入記憶體中,這時JIT編譯器編譯位元組碼代碼。
JIT有兩個不足:
(1).JIT編譯轉換需要花費一些時間,這些時間貫穿于程式的整個生命周期。
(2).JIT增加了可執行代碼的size,相比于壓縮的位元組碼,JIT代碼擴充了代碼的size,這有可能引起記憶體分頁,進而降低程式執行速度。
對JIT不足的一種改進技術是延遲評估(lazy evaluation):其基本原理是位元組碼并不立即進行JIT編譯除非必要,在最近的JDK中采用了一種類似延遲JIT的HotSpot方法對每次執行的代碼進行優化,代碼執行次數越多,速度越快。
16.非内部類的通路控制權限隻能是預設的包通路權限或者是public的,不能是protected和private的。内部類的通路控制權限可以是protected和private。
17.Java中的高精度數值類型:
BigInteger和BigDecimal是java中的高精度數值類型,由于它們是用于包裝java的基本資料類型,是以這兩個高精度數值類型沒有對應的原始類型。
(1).BigInteger支援任意精度的整數,即使用BigInteger可以表示任意長度的整數值而在運算中不會因為範圍溢出丢失資訊。
(2).BigDecimal支援任意精度的固定位數浮點數,可以用來精确計算貨币等數值。
普通的float和double型浮點數因為受到小數點位數限制,在運算時不能準确比較,隻能以誤差範圍确定是否相等,而BigDecimal就可以支援固定位數的浮點數并進行精确計算。
18.Java隻處理public和protected通路控制權限成員的文檔注釋,private和預設的包通路控制權限成員的文檔注釋将被忽略。
19.Java中指派運算:
Java中指派運算是把指派運算符”=”右邊的值簡稱右值拷貝到指派運算符左邊的變量,如a=b,即把b代表的變量或常量值複制給變量a,切記a隻能是變量,不能說常量值。
(1).原始類型指派運算:
Java中8種原始資料類型指派運算是将指派運算符右邊的值拷貝到指派運算符左邊的變量中。
原始類型指派運算後,無論改變指派運算符那一邊的值,都不會影響指派運算符另一邊的值。
(2).引用類型的指派運算:
Java中除了8中原始資料類型外,所有的資料類型都是對象類型,對象類型的指派運算是操作引用,如a=b,把b引用指派給a引用,即原本b引用指向的對象現在由a和b引用同時指向。
引用指派運算符又叫别名運算符,即它相當于給引用對象取了一個别名,其實引用的還是同一個對象。
引用類型的指派運算,如果指派運算符任意一邊的引用改變了被引用對象的值,指派運算符另一邊的引用也會受影響,因為兩個引用指向的是同一個被引用的對象。
2——對象初始化和面向對象特性
1.java類的初始化順序:
(1).在一個類中,初始化順序由變量在類中的聲明定義順序決定,成員變量(非set方法和構造方法的初始化)的初始化發生在方法調用之前,包括構造方法。
(2).靜态變量在整個存儲區隻保留一份拷貝,本地變量不能使用靜态關鍵字,基本類型的靜态變量不需要初始化,它會根據類型獲得初始化值,引用類型的靜态變量預設初始化為null。
靜态變量的初始化發送在需要使用的時候,一旦被初始化之後,靜态變量就不會再初始化。
(3).靜态初始化塊和靜态變量類似的執行也在構造方法之前,并且僅執行一次。
(4).動态初始化塊(與靜态初始化塊類似,隻是沒有static關鍵字,即放在一對大括号中的代碼塊)在靜态初始化塊初始化結束後執行,動态初始化塊每次建立新對象都會初始化一次。
(5).構造方法執行時,先執行父類的構造方法,後執行子類的構造方法。
(6).本地變量初始化最晚,在方法中初始化。
綜述,類的初始化順序依次為:
a.父類的靜态變量/靜态初始化塊;
b.子類類的靜态變量/靜态初始化塊;
c.父類的動态初始化塊、非構造方法和set方法的成員變量初始化
d.子類的動态初始化塊、非構造方法和set方法的成員變量初始化
e.父類的構造方法。
f.子類的構造方法。
g.父類本地變量。
h.子類的本地變量。
2.數組初始化:
Java中數組初始化有以下3中方式:
(1).數組聲明時直接初始化,如:
int[] a = {1,2,3};
(2).動态數組初始化,如:
int[] a = new int[]{1,2,3};
注意:動态數組初始化時,不能在new()操作符中指定數組的大小,即int a = new int[3]{1,2,3}的寫法是錯誤的,數組的大小由初始化數組元素個數決定。
(3).固定長度數組初始化,如:
int[] a = new int[3];
a[1] = 0;
a[2] = 1;
a[3] = 2;
注意:固定長度大小的數組初始化時不能大于所聲明的數組長度,沒有聲明的數組元素使用其預設值,如int預設為0,對象類型的值為引用,預設為null.
3.java代碼重用4中方式:
java面向對象程式設計中提供了如下4中代碼重用的方式:
(1).組合:
面向對象程式設計中最常用的代碼複用方式,具體的方式是在一個對象中将另一個對象引用最為成員變量,其最大的優點是既實作松散耦合,有可能提高代碼複用率。
(2).繼承:
面向對象程式設計中常用的提高代碼複用率的方法之一,适用于子類和父類是同一種抽象類型,具有共同的屬性情況。
使用繼承,子類可以複用父類除private私有房屋控制權限以為的所有屬性和方法,編譯器将父類封裝為子類對象内部的一個對象。
需要注意的是:調用子類初始化構造方法時,編譯器會確定首先調用父類的構造方法初始化父類,然後才初始化子類,如果父類中沒有預設的構造方法,即需要顯式傳入參數的構造方法時,子類必須通過super關鍵字顯式傳入參數調用父類的構造方法。
(3).委派:
Java中不支援委派方式的代碼複用,但是開發人員可以使用委派機制實作代碼的重用。
委派是指,java對象的所有方法其實都是委派調用另一個類的方法實作,但是目前類又不是所委派類的類型,是以使用繼承不太合适,解決方式群組合類似,将被委派類作為委派類的成員變量,委派類的方法直接調用被委派類對象應用的方法。
如:
[java] view plaincopy
1. //委派類
2. public Class A{
3. //被委派類
4. private B b = new Class B();
5. public void method1(){
6. b.method1();
7. }
8. public void method2(){
9. b.method2();
10. }
11. ……
12. }
(4).聯合使用組合和繼承方式:
因為java中不允許多繼承,如果某種情況下,一個java類需要使用多個其他類功能,且該類和其中某個類具有很多共同屬性,即可以看作同一類,則可以使目前類繼承具體共同屬性的類,同時将其他類作為成員變量組合引用。
4.組合和繼承的差別:
組合和繼承都可以複用代碼,很多java程式員,甚至是架構師都分不清楚什麼情況下該使用組合,什麼情況下應該使用繼承,具體的差別如下:
(1).組合:
組合通常是在一個類中想使用另一個類已有的特性,但是卻不想使用其接口。
使用組合可以可以将一個類作為目前類的内嵌對象,這樣在目前類中就可以顯式地使用内嵌類已經實作的功能,與此同時又不會影響目前類的調用接口。
(2).繼承:
繼承是隐式地使用被繼承類的功能,相當于提供了一個新版本的父類實作。
使用繼承,子類不但可以複用父類的功能,同時還複用了父類的接口,子類和父類的對外調用接口相同的情況下适合使用繼承。
使用繼承時,很多情況下需要向上類型轉換,即将子類看作其父類。在程式設計時到底選用組合方式還是繼承方式,一個簡單的判斷依據是:是否需要向上類型轉換,如果需要就使用繼承,如果不需要,則選擇組合。
5.final方法:
Java中使用final類型的方法有以下兩種原因:
(1).設計原因:
final類型的方法不允許其子類修改方法,即不允許子類覆寫父類的final方法。
(2).效率原因:
在早期的java實作中,如果方法被聲明為final,編譯器将final方法調用編譯為内聯調用。
正常的方法調用是:如果方法調用時,将目前的方法上下文保持到棧中,調用被調用的方法,然後在将調用上下文出棧恢複調用現場。
内聯調用是:如果方法調用時,将被調用方法體拷貝到目前調用的地方合并成一個方法體,這樣就避免因需要儲存方法調用線程而進行的進棧和出棧操作,可以提高效率。
新版的使用hotspot技術的java虛拟機可以探測方法調用情況而做效率優化,final方法不再作為提高效率的手段,唯一的作用是確定方法不被子類覆寫。
注意:任何private的方法都是隐式的final類型,同final方法類似,private方法不能被子類所覆寫,但是private比final更嚴格,基類的private方法對子類不可見,private方法不再是接口的一部分。
6.多态性:
面向對象程式設計中的多态和繼承往往是一起發揮作用的,使用繼承,所有的子類和父類使用相同的對外接口,而多态的基礎是晚綁定或動态綁定或運作時綁定, 即對象引用使用基類類型,在編譯時編譯器無法确切知道到底調用哪一個具體類,隻有在運作時,java虛拟機才通過類型檢查确定調用對象的具體類型。
Java中預設對象引用全部是晚綁定,隻有static和final類型的引用時早綁定或編譯時綁定。
多态的優勢是:程式的可擴張性好,無論添加多少個子類,基類的接口都不用改變,隻需要在子類對應方法中提供具體實作即可,也就是所謂的将程式變化的部分和程式保持不變的部分分離。
注意:隻有正常的方法可以使用多态,字段和靜态方法沒有多态機制。
構造方法也不支援多态機制,構造方法是隐式的static聲明。
3——内部類
1.java中,可以将一個類的定義放在另一個類的内部,這種叫做内部類。
内部類允許程式設計人員将邏輯上相關的類組織在一起,并且控制内部類對其他類的可見性。
2.在外部類的非靜态方法中建立内部類的對象文法:
外部類類名.内部類類名 對象名 = 外部類對象.new 内部類類名();
如:
[java] view plaincopy
1. public class Outter{
2. class inner{
3. }
4. }
5. Outter out = new Outter();
6. Outter.Inner inner = out.new Inner();
注意:非靜态的内部類必須要有外部類對象之後才能建立,因為外部類對象持有内部類的引用,如果内部類是靜态的,則不需要外部類對象引用内部類對象。
3.内部類對外部類對象的引用:
外部類中所有的元素對内部類都是可見的,内部類持有對外部類對象引用的文法:外部類名稱.this。如:
[java] view plaincopy
1. public class Outter{
2. void f(){
3. System.out.println(“Outter f() method”);
4. }
5. class Inner{
6. public Outter outer(){
7. return Outter.this;
8. }
9. }
10. public Inner inner(){
11. return new Inner();
12. }
13. }
4.方法内部類:
除了最常見的最為成員變量的内部類以外,内部類還可以定義在方法中,如:
[java] view plaincopy
1. public class Outter{
2. public Inner getInner(){
3. class Inner{
4. public void f(){
5. System.out.println(“Method inner class”);
6. }
7. }
8. return new Inner();
9. }
10. public void fout(){
11. Inner inner = getInner();
12. inner.f();
13. }
14. }
5.匿名内部類:
Java中匿名内部類的應用十分廣泛,所謂的匿名内部類就是指所建立的内部類沒有類名稱,也就是不知道内部類的具體類型,如:
[java] view plaincopy
1. public class Outter{
2. public Inner getInner(){
3. return new Inner(){
4. private String name = “inner”;
5. public String getName(){
6. return name;
7. }
8. };
9. }
10. }
上面的return newInner{……};就是一個匿名内部類,這個匿名内部類繼承了Inner類,即其基類是Inner,但是其具體類型不清楚,該匿名内部類等效于下面的内部類寫法:
[java] view plaincopy
1. public class Outter{
2. class MyInner implement Inner{
3. private String name = “Inner class”;
4. public String getName(){
5. return name;
6. }
7. }
8. public Inner getInner(){
9. return new MyInner();
10. }
11. }
注意:匿名内部類是實作了new關鍵字之後的接口或繼承了類,隻是沒有具體的類名稱。
6.内名内部類傳遞final參數:
如果在建立匿名内部類,需要外部類傳遞參數時,參數必須是final類型的,否則,編譯時會報錯。如:
[java] view plaincopy
1. public class Outter{
2. public Inner getInner(final String name){
3. return new Inner(){
4. public name getName(){
5. return name;
6. }
7. };
8. }
9. }
注意:如果外部類傳遞的參數在内部類中使用,則必須是final類型,如果沒有在内部類中使用(如,僅在基類中使用),則可以不用是final類型的。
7.靜态内部類:
靜态内部類又稱為嵌套類,靜态内部類和普通内部類的差別:
(1).對于非靜态的内部類來說,内部類和外部類必須保持對象引用,内部類可以通路外部類的任何元素.
(2).靜态内部類不需要和外部類保持對象引用,靜态内部類隻能通路外部類的靜态元素。
8.為什麼要使用内部類:
使用内部類主要有以下兩個原因:
(1).解決java中類不能多繼承的問題:
Java中的繼承是單繼承,在某些情況下接口的多繼承可以解決大部分類似C++中多繼承的問題,但是如果一個類需要繼承兩個父類而不是接口,在java中是沒法實作這種功能的,内部類可以幫助我們部分解決這種問題,如:
[java] view plaincopy
1. abrstact class A{
2. abstract public void f();
3. }
4. abstract class B{
5. abstract public void g();
6. }
7. public class D extends A{
8. public void f(){}
9. public B makeB(){
10. return new B(){
11. public void g(){};
12. }
13. }
14. }
這樣既繼承了A類,有在makeB方法中使用匿名内部類繼承了B類。
(2).閉包方法問題:
在面向對象中有個術語叫閉包,所謂閉包是指一個可調用的對象,它記錄了一些資訊,這些資訊來自于建立它的作用域。Java中不支援閉包,但是 java的内部類可以看作是對閉包的一種解決方案,因為外部類的所有元素對于普通的内部類來說都是可見的,并且内部類還包含一個指向外部類的對象引用,因 此在作用域内,内部類有權操作外部類的所有成員包括private成員。如:
[java] view plaincopy
1. <pre name="code" class="java">interface Incrementable {
2. void increment();
3. }
4.
5. class Callee1 implements Incrementable {
6. private int i=0;
7. public void increment() {
8. i++;
9. System.out.println(i);
10. }
11. }
12.
13. class MyIncrement {
14. void increment() {
15. System.out.println("other increment");
16. }
17. static void f(MyIncrement mi) {
18. mi.increment();
19. }
20. }
21.
22. class Callee2 extends MyIncrement {
23. private int i=0;
24. private void incr() {
25. i++;
26. System.out.println(i);
27. }
28. //閉包内部類
29. private class Closure implements Incrementable {
30. public void increment() {
31. incr();
32. }
33. }
34. //回調函數
35. Incrementable getCallbackReference() {
36. //建立内部類
37. return new Closure();
38. }
39. }
40.
41. class Caller {
42. private Incrementable callbackRefference;
43. Caller(Incrementable cbh) {
44. callbackRefference = cbh;
45. }
46. void Go() {
47. //調用increment()方法
48. callbackRefference.increment();
49. }
50. }
51.
52. public class Callbacks {
53. public static void main(String [] args) {
54. Callee1 c1=new Callee1();
55. Callee2 c2=new Callee2();
56. MyIncrement.f(c2);
57. Caller caller1 =new Caller(c1);
58. //将内部類中的Closure賦給Caller
59. Caller caller2=new Caller(c2.getCallbackReference());
60. caller1.go();
61. caller1.go();
62. caller2.go();
63. caller2.go();
64. }
65. }
66.
67.
68. 輸出:
69. other increment
70. 1
71. 2
72. 1
73. 2
74. Callee2 通過使用内名内部類提供了一個通路自身increment方法的鈎子,通過getCallbackReference()方法擷取内部類對象的引用,進而通 過内部類的increment方法調用外部類的回調方法,這就是java中用于實作回調方法的閉包。
75.
4——集合容器
1.集合中添加另一個集合的方法:
(1).Collection.addAll(被添加的Collection對象)方法:
如:list1.addAll(list2);
(2).Collections.addAll(添加到的目标Collection對象,可變參數的集合或者對象)方法:
如:Collections.addAll(list1, new Object1(), new Object2()…);
Collectionns.addAll(list1, list2…);
注意:Collections是java集合容器的工具類,相比于(1),使用Collections的(2)更靈活。
2.Java集合中常用的交集、并集和差集操作:
并集:collection對象1.addAll(collection對象2);
交集:collection對象1. retainAll(collection對象2);
差集:collection對象1. removeAll(collection對象2);
注意:上述的集合操作時,集合元素的equals方法會影響操作結果。
3.将其他類型集合轉換為List:
Arrays.asList(非List類型的集合對象/可變參數的對象);方法可以将傳遞的參數轉變為List集合。如:Arrays.asList(new Object1(),new Object2(),…);
Arrays和Collections類似,是Array數組類型集合的工具類。
注意:Arrays.asList()方法轉換後的List對象是一個size不能改變的對象,如果對該對象做增加或者删除元素操作時,将會報不支援的操作異常。
4.List集合:
List集合主要有兩種具體的集合容器:ArrayList和LinkedList。
(1).ArrayList:底層實作是數組,提供了根據數組下标快速随機通路的能力,但是增加和删除元素時因為需要引動數組的元素,是以比較慢。
(2).LinkedList:底層實作是連結清單,連結清單通路元素時必須從連結清單頭至連結清單尾挨個查找,是以隻能順序通路,速度比随機通路要慢。但是增加和删除元素時,隻需要修改連結清單的指針而不需要移動元素,是以速度比較快。
5.LinkedList:
LinkedList除了實作了基本的List接口以外,還提供了一些特定的方法,使得LinkedList可以友善地實作Stack、Queue以及雙端Queue的功能。
LinkedList提供的非List接口方法:
(1).getFirst():擷取并且不移除LinkedList集合中第一個元素。如果集合為空,抛出NoSuchElementException異常。
(2).element():擷取并且不移除LinkedList集合中第一個元素。如果集合為空,抛出NoSuchElementException異常。
(3).peek():擷取并且不移除LinkedList集合中第一個元素。如果集合為空,則傳回null。
(4).removeFirst():擷取并且移除LinkedList集合中第一個元素。如果集合為空,抛出NoSuchElementException異常。
(5).remove():擷取并且移除LinkedList集合中第一個元素。如果集合為空,抛出NoSuchElementException異常。
(6).poll():擷取并且移除LinkedList集合中第一個元素。如果集合為空,則傳回null。
(7).addFirst():向LinkedList集合的頭部插入一個元素。
(8).add():向LinkedList集合的尾部插入一個元素。
(9).offer():向LinkedList集合的尾部插入一個元素。
(10).removeLast():擷取并且移除LinkedList集合中最後一個元素。如果集合為空,抛出NoSuchElementException異常。
6.Iterator:
Iterator疊代器在java集合容器中應用比較廣泛,對于List類型的集合,可以通過下标索引值擷取到指定的元素,而對于Set類型的集合,因為Set是沒有索引的,是以隻能通過疊代器來周遊。
Iterator疊代器是一個順序選擇和周遊集合元素的對象,使用者不需要關心其底層的資料結構和實作方式。Java中的Iterator疊代器是單向的。
Iterator的常用方法如下:
(1).collection對象.iterator()方法:将集合對象轉換為Iterator疊代器。
(2).iterator對象.hasNext()方法:判斷疊代器中是否還有元素。
(3).iterator對象.next()方法:擷取疊代器中下一個元素。
(4).iterator對象.remove()方法:删除疊代器中目前元素。
注意:使用疊代器的好處是,當資料結構從List變為Set之後,疊代集合的相關代碼一點都不用改變。
7.ListIterator:
ListIterator是Iterator的子類,它隻能有List類型的集合産生,ListIterator是一個雙向的疊代器,即它可以向前和向後雙向周遊集合。ListIterator的常用方法如下:
(1).list類型對象.listIterator():将List類型的集合轉換為ListIterator疊代器。
(2).list類型對象.listIterator(int n):将List類型的集合轉換為ListIterator疊代器,同時指定疊代器的起始元素為第n個元素。
(3).listIterator對象.hasNext():判斷疊代器中是否還有下一個元素。
(4).listIterator對象.next():擷取疊代器中的下一個元素。
(5).listIterator對象.hasPrevious():判斷疊代器中是否還有前一個元素。
(6).listIterator對象.previous():擷取疊代器中的前一個元素。
(7).listIterator對象.set(元素對象):将目前疊代到的元素設定為另一個值。
8.Map周遊3中方法:
Map<String, Object>map = new HashMap<String, Object>();
map.put(“test1”, object1);
……
map.put(“testn” , objectn);
(1).Map的values()方法可以擷取Map值的集合:
[java] view plaincopy
1. Iterator it = map.values().iterator();
2. while(it.hasNext()){
3. Object obj = it.next();
4. }
(2).Map的keySet方法可以擷取Map鍵的Set集合:
[java] view plaincopy
1. Set<String> keys = map.keySet();
2. for(Iterator it = key.iterator(); it.hasNext(); ){
3. String key = it.next();
4. Object obj = map.get(key);
5. }
(3).通過使用Entry來得到Map的key和value:
[java] view plaincopy
1. Set<Map.Entry<String, Object>> entrySet = map.entrySet();
2. for(Iterator <Map.Entry<String, Object>> it = entrySet.iterator(); it.hasNext(); ){
3. Map.Entry<String, Object> entry = it.next();
4. String key = entry.getKey();
5. Object value = entry.getValue();
6. }
9.Collection和Iterator:
Collection是java中除了Map以外的集合容器的通用接口,如果想自定義一種集合容器類型的類,可以選擇實作Collection接口或者繼承Collection的子類。
實作Collection接口或者繼承Collection子類的時候,必須實作Collection接口的所有方法,而Iterator為定義自 定義集合容器類型提供了另一種友善,Iterator是一種輕量級的接口,隻需要實作hasNext(),next()方法即可,remove()方法是 可選方法。
注意:Foreach循環支援所有實作了Iterable接口的集合容器(Collection接口的父接口是Iterable),Map集合沒有實作Iterable接口,是以不支援Foreach循環。
10.java集合容器架構圖:
5——正規表達式量詞比對
Java正規表達式有3中量詞比對模式:
1.貪婪量詞:
先看整個字元串是否比對,如果沒有發現比對,則去掉最後字元串中的最後一個字元,并再次嘗試,如果還是沒有發現比對,那麼,再次去掉最後一個字元串的最後一個字元,整個比對過程會一直重複直到發現一個比對或者字元串不剩任何字元。簡單量詞都是貪婪量詞。
貪婪量詞比對時,首先将整個字元串作為比對的對象,然後逐漸從後向前移除不比對的字元,盡可能找到最多的比對。
2.惰性量詞:
先看字元串中的第一個字元是否比對,如果單獨一個字元不夠,則讀入下一個字元,組成兩個字元的字元串,如果還沒有發現比對,惰性量詞繼續從字元串中添加字元直到發現一個比對或者整個字元串全部檢查完都不比對。惰性量詞和貪婪量詞工作方式恰好相反。
惰性量詞比對時,隻比對第一個字元,然後依次添加字元,盡可能找到最少比對。
3.支配量詞:
隻嘗試比對整個字元串,如果整個字元串不能産生比對,則不進行進一步嘗試。
支配量詞目前隻有java中支援,支援量詞是貪婪量詞第一次比對不成功時,阻止正規表達式繼續比對,使得正規表達式效率更高。
貪婪量詞 惰性量詞 支配量詞 描述
X? X?? X?+ X出現0次或者1次
X* X*? X*+ X出現0次或者多次
X+ X+? X++ X出現1次或者多次
X{n} X{n}? X{n}+ X隻出現n次
X{n,} X{n,}? X{n,}+ X至少出現n次
X{n,m} X{n,m}? X{n,m}+ X至少出現n次,至多不超過m次
6——Java動态代理
代理是一種常用的程式設計模式,如同網絡代理一樣,代理是介于調用者和真正調用目标對象之間的中間對象,代理在調用真正目标對象時提供一些額外或者不同的操作,真正的對目标對象的操作還是通過代理調用目标對象來完成。
簡單的代理例子如下:
[java] view plaincopy
1. //接口
2. interface Interface{
3. void doSomething();
4. void somethingElse(String arg);
5. }
6. //目标對象
7. class RealObject implement Interface{
8. public void doSomething(){
9. System.out.println(“RealObject doSomething”);
10. }
11. public void somethingElse(String arg){
12. System.out.println(“RealObject somethingElse ” + arg);
13. }
14. }
15. //簡單代理對象
16. class SimpleProxy implements Interface(
17. private Interface proxied;
18. public SimpleProxy(Interface proxied){
19. this.proxied = proxied;
20. }
21. public void doSomething(){
22. System.out.println(“SimpleProxy doSomething”);
23. proxied.doSomething();
24. }
25. public void somethingElse(String arg){
26. System.out.println(“SimpleProxy somethingElse ” + arg);
27. proxied.somethingElse(arg);
28. }
29. )
30. Class SimpleProxyDemo{
31. public static void consumer(Interface iface){
32. iface.doSomething();
33. iface.somethingElse(“TestProxy”);
34. }
35. public static void main(String[] args){
36. //不是用代理
37. cosumer(new RealObject());
38. //使用代理
39. cosumer(new SimpleProxy(new RealObject()));
40. }
41. }
輸出結果為:
RealObject doSomething
RealObjectsomethingElse TestProxy
SimpleProxy doSomething
RealObject doSomething
SimpleProxy somethingElse TestProxy
RealObject somethingElse TestProxy
上面例子可以看出代理SimpleProxy在調用目标對象目标方法之前做了一些額外的操作。
Java中的代理是針對接口的動态代理,當然java也可以使用第三方的CGLIB實作針對類的代理,但是JDK中隻支援針對接口的動态代理,我們隻分析JDK的動态代理。
JDK動态代理的要素:
(1).實作了InvocationHandler的代理處理類,實作其invoke方法,該方法是代理調用目标對象方法以及提供額外操作的方法。
(2).使用Proxy.newProxyInstance(類加載器, 代理接口清單,InvocationHandler對象);方法建立實作了指定接口的動态代理。
JDK的代理例子如下:
[java] view plaincopy
1. //接口
2. interface Interface{
3. void doSomething();
4. void somethingElse(String arg);
5. }
6. //目标對象
7. class RealObject implement Interface{
8. public void doSomething(){
9. System.out.println(“RealObject doSomething”);
10. }
11. public void somethingElse(String arg){
12. System.out.println(“RealObject somethingElse ” + arg);
13. }
14. }
15. //代理處理類
16. class DynamicProxyHandler implements InvocationHandler{
17. provate Object proxied;
18. public DynamicProxyHandler(Object proxied){
19. this.proxied = proxied;
20. }
21. //動态代理調用目标對象的方法
22. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
23. System.out.println(“Dynamic proxy invoke”);
24. return method.invoke(proxied, args);
25. }
26. }
27. class SimpleDynamicProxy{
28. public static void consumer(Interface iface){
29. iface.doSomething();
30. iface.somethingElse(“DynamicProxy”);
31. }
32. public static void main(String[] args){
33. RealObject real = new RealObject();
34. //不是用代理
35. consumer(real);
36. //建立動态代理
37. Interface proxy = (Interface) Proxy.newProxyInstance(
38. Interface.class.getClassLoader(),
39. new Class[]{Interface.class},
40. new DynamicProxyHandler(real)
41. );
42. cosumer(proxy);
43. }
44. }
輸出結果為:
RealObject doSomething
RealObject somethingElse DynamicProxy
Dynamic proxy invoke
RealObject doSomething
Dynamic proxyinvoke
RealObject somethingElse DynamicProxy
7——泛型程式設計基礎
一般的類和方法都是針對特定資料類型的,當寫一個對多種資料類型都适用的類和方法時就需要使用泛型程式設計,java的泛型程式設計類似于C++中的模闆, 即一種參數化類型的程式設計方法,具體地說就是将和資料類型相關的資訊抽象出來,主要提供通用的實作和邏輯,和資料類型相關的資訊由使用時參數決定。
1.泛型類/接口:
(1).泛型接口:
如一個提供産生指定類的接口:
[java] view plaincopy
1. public interface Gernerator<T>{
2. T next() ;
3. }
4. public class A implement Generator<A>{
5. A next(){
6. return new A();
7. }
8. }
(2).泛型類:
一個使用泛型實作的棧資料結構如下:
[java] view plaincopy
1. public class LinkedListStack<T>{
2. //節點内部類
3. private static class Node<U>{
4. U item;
5. Node<U> next;
6. Node(){
7. item = null;
8. next = null;
9. }
10. Node(U item, Node<U> next){
11. this.item = item;
12. this.next = next;
13. }
14. Boolean end(){
15. return item == null && next == null;
16. }
17. }
18. private Node<T> top = new Node<T>();
19. public void push<T>(T item){
20. top = new Node<T>(item, top);
21. }
22. public T pop(){
23. T result = top.item;
24. if(!top.end()){
25. top = top.next();
26. }
27. return result;
28. }
29. }
使用這個使用泛型實作的棧,可以操作各種資料類型。
2.泛型方法:
例如:
[java] view plaincopy
1. public class GenericMethods{
2. public <T> void f(T x){
3. System.out.println(x.getClass().getName()) ;
4. }
5. public static void main(String[] args){
6. GenericMethods gm = new GenericMethods();
7. gm.f(“”);
8. gm.f(1);
9. gm.f(1.0);
10. ……
11. }
12. }
輸出結果為:
java.lang.String
java.lang.Integer
java.lang.Double
3.泛型集合:
Java中泛型集合使用的非常廣泛,在Java5以前,java中沒有引入泛型機制,使用java集合容器時經常遇到如下兩個問題:
a. java容器預設存放Object類型對象,如果一個容器中即存放有A類型對象,又存放有B類型對象,如果使用者将A對象和B對象類型弄混淆,則容易産生轉換錯誤,會發生類型轉換異常。
b. 如果使用者不知道集合容器中元素的資料類型,同樣也可能會産生類型轉換異常。
鑒于上述的問題,java5中引入了泛型機制,在定義集合容器對象時顯式指定其元素的資料類型,在使用集合容器時,編譯器會檢查資料類型是否和容器指定的資料類型相符合,如果不符合在無法編譯通過,從編譯器層面強制保證資料類型安全。
(1).java常用集合容器泛型使用方法:
如:
[java] view plaincopy
1. public class New{
2. public static <K, V> Map<K, V> map(){
3. return new HashMap<K, V>();
4. }
5. public static <T> List<T> list(){
6. return new ArrayList<T>() ;
7. }
8. public static <T> LinkedList<T> lList(){
9. return new LinkedList<T>();
10. }
11. public static <T> Set<T> set(){
12. return new HashSet<T>();
13. }
14. public static <T> Queue<T> queue(){
15. return new LinkedList<T>() ;
16. }
17. ;public static void main(String[] args){
18. Map<String, List<String>> sls = New.map();
19. List<String> ls = New.list();
20. LinkedList<String> lls = New.lList();
21. Set<String> ss = New.set();
22. Queue<String> qs = New.queue();
23. }
24. }
(2).Java中的Set集合是數學上邏輯意義的集合,使用泛型可以很友善地對任何類型的Set集合進行數學運算,代碼如下:
[java] view plaincopy
1. public class Sets{
2. //并集
3. public static <T> Set<T> union(Set<T> a, Set<T> b){
4. Set<T> result = new HashSet<T>(a);
5. result.addAll(b);
6. return result;
7. }
8. //交集
9. public static <T> Set<T> intersection(Set<T> a, Set<T> b){
10. Set<T> result = new HashSet<T>(a);
11. result.retainAll(b);
12. return result;
13. }
14. //差集
15. public static <T> Set<T> difference(Set<T> a, Set<T> b){
16. Set<T> result = new HashSet<T>(a);
17. result.removeAll(b);
18. return Result;
19. }
20. //補集
21. public static <T> Set<T> complement(Set<T> a, Set<T> b){
22. return difference(union(a, b), intersection(a, b));
23. }
24. }
8——泛型程式設計進階
1.泛型邊界:
Java泛型程式設計時,編譯器忽略泛型參數的具體類型,認為使用泛型的類、方法對Object都适用,這在泛型程式設計中稱為類型資訊檫除。
例如:
[java] view plaincopy
1. class GenericType{
2. public static void main(String[] args){
3. System.out.println(new ArrayList<String>().getClass());
4. System.out.println(new ArrayList<Integer>().getClass());
5. }
6. }
輸出結果為:
java.util.ArrayList
java.util.ArrayList
泛型忽略了集合容器中具體的類型,這就是類型檫除。
但是如果某些泛型的類/方法隻想針對某種特定類型擷取相關子類應用,這時就必須使用泛型邊界來為泛型參數指定限制條件。
例如:
[java] view plaincopy
1. interface HasColor{
2. java.awt.Color getColor();
3. }
4. class Colored<T extends HasColor>{
5. T item;
6. Colored(T item){
7. this.item = item;
8. }
9. java.awt.Color color(){
10. //調用HasColor接口實作類的getColor()方法
11. return item.getColor();
12. }
13. }
14. class Dimension{
15. public int x, y, z;
16. }
17. Class ColoredDimension<T extends Dimension & HasColor>{
18. T item;
19. ColoredDimension(T item){
20. this.item = item;
21. }
22. T getItem(){
23. return item;
24. }
25. java.awt.Color color(){
26. //調用HasColor實作類中的getColor()方法
27. return item.getColor();
28. }
29. //擷取Dimension類中定義的x,y,z成員變量
30. int getX(){
31. return item.x;
32. }
33. int getY(){
34. return item.y;
35. }
36. int getZ(){
37. return item.z;
38. }
39. }
40. interface Weight{
41. int weight();
42. }
43. class Solid<T extends Dimension & HasColor & Weight>{
44. T item;
45. Solide(T item){
46. this.item = item;
47. }
48. T getItem(){
49. return item;
50. }
51. java.awt.Color color(){
52. //調用HasColor實作類中的getColor()方法
53. return item.getColor();
54. }
55. //擷取Dimension類中定義的x,y,z成員變量
56. int getX(){
57. return item.x;
58. }
59. int getY(){
60. return item.y;
61. }
62. int getZ(){
63. return item.z;
64. }
65. int weight(){
66. //調用Weight接口實作類的weight()方法
67. return item.weight();
68. }
69. }
70. class Bounded extends Dimension implements HasColor, Weight{
71. public java.awt.Color getColor{
72. return null;
73. }
74. public int weight(){
75. return 0;
76. }
77. }
78. public class BasicBounds{
79. public static void main(String[] args){
80. Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
81. solid.color();
82. solid.getX();
83. solid.getY();
84. solid.getZ();
85. solid.weight();
86. }
87. }
Java泛型程式設計中使用extends關鍵字指定泛型參數類型的上邊界(後面還會講到使用super關鍵字指定泛型的下邊界),即泛型隻能适用于extends關鍵字後面類或接口的子類。
Java泛型程式設計的邊界可以是多個,使用如<T extends A & B & C>文法來聲明,其中隻能有一個是類,并且隻能是extends後面的第一個為類,其他的均隻能為接口(和類/接口中的extends意義不同)。
使用了泛型邊界之後,泛型對象就可以使用邊界對象中公共的成員變量和方法。
2.泛型通配符:
泛型初始化過程中,一旦給定了參數類型之後,參數類型就會被限制,無法随着複制的類型而動态改變,如:
[java] view plaincopy
1. class Fruit{
2. }
3. class Apple extends Fruit{
4. }
5. class Jonathan extends Apple{
6. }
7. class Orange extends Fruit{
8. }
9. 如果使用數組:
10. public class ConvariantArrays{
11. Fruit fruit = new Apple[10];
12. Fruit[0] = new Apple();
13. Fruit[1] = new Jonathan();
14. try{
15. fruit[0] = new Fruit();
16. }catch(Exception e){
17. System.out.println(e);
18. }
19. try{
20. fruit[0] = new Orange();
21. }catch(Exception e){
22. System.out.println(e);
23. }
24. }
編譯時沒有任何錯誤,運作時會報如下異常:
java.lang.ArrayStoreException:Fruit
java.lang.ArrayStoreException:Orange
為了使得泛型在編譯時就可以進行參數類型檢查,我們推薦使用java的集合容器類,如下:
[java] view plaincopy
1. public class NonConvariantGenerics{
2. List<Fruit> flist = new ArrayList<Apple>();
3. }
很不幸的是,這段代碼會報編譯錯誤:incompatible types,不相容的參數類型,集合認為雖然Apple繼承自Fruit,但是List的Fruit和List的Apple是不相同的,因為泛型參數在聲 明時給定之後就被限制了,無法随着具體的初始化執行個體而動态改變,為解決這個問題,泛型引入了通配符”?”。
對于這個問題的解決,使用通配符如下:
[java] view plaincopy
1. public class NonConvariantGenerics{
2. List<? extends Fruit> flist = new ArrayList<Apple>();
3. }
泛型通配符”?”的意思是任何特定繼承Fruit的類,java編譯器在編譯時會根據具體的類型執行個體化。
另外,一個比較經典泛型通配符的例子如下:
public class SampleClass < T extendsS> {…}
假如A,B,C,…Z這26個class都實作了S接口。我們使用時需要使用到這26個class類型的泛型參數。那執行個體化的時候怎麼辦呢?依次寫下
SampleClass<A> a = new SampleClass();
SampleClass<B> a = new SampleClass();
…
SampleClass<Z> a = new SampleClass();
這顯然很備援,還不如使用Object而不使用泛型,使用通配符非常友善:
SampleClass<? Extends S> sc = newSampleClass();
3.泛型下邊界:
在1中大概了解了泛型上邊界,使用extends關鍵字指定泛型執行個體化參數隻能是指定類的子類,在泛型中還可以指定參數的下邊界,是一super關鍵字可以指定泛型執行個體化時的參數隻能是指定類的父類。
例如:
[java] view plaincopy
1. class Fruit{
2. }
3. class Apple extends Fruit{
4. }
5. class Jonathan extends Apple{
6. }
7. class Orange extends Fruit{
8. }
9. public superTypeWildcards{
10. public static void writeTo(List<? super Apple> apples){
11. apples.add(new Apple());
12. apples.add(new Jonathan());
13. }
14. }
通過? Super限制了List元素隻能是Apple的父類。
泛型下邊界還可以使用<?super T>,但是注意不能使用<Tsuper A>,即super之前的隻能是泛型通配符,如:
[java] view plaincopy
1. public class GenericWriting{
2. static List<Apple> apples = new ArrayList<Apple>();
3. static List<Fruit> fruits = new ArrayList<Fruit>();
4. static <T> void writeExact(List<T> list, T item){
5. list.add(item);
6. }
7. static <T> void writeWithWildcards(List<? super T> list, T item){
8. list.add(item);
9. }
10. static void f1(){
11. writeExact(apples, new Apple());
12. }
13. static void f2(){
14. writeWithWildcards(apples, new Apple());
15. writeWithWildcards(fruits, new Apple());
16. }
17. public static void main(String[] args){
18. f1();
19. f2();
20. }
21. }
4.無邊界的通配符:
泛型的通配符也可以不指定邊界,沒有邊界的通配符意思是不确定參數的類型,編譯時泛型檫除類型資訊,認為是Object類型。如:
[java] view plaincopy
1. public class UnboundedWildcard{
2. static List list1;
3. static List<?> list2;
4. static List<? extends Object> list3;
5. static void assign1(List list){
6. list1 = list;
7. list2 = list;
8. //list3 = list; //有未檢查轉換警告
9. }
10. static void assign2(List<?> list){
11. list1 = list;
12. list2 = list;
13. list3 = list;
14. }
15. static void assign3(List<? extends Object> list){
16. list1 = list;
17. list2 = list;
18. list3 = list;
19. }
20. public static void main(String[] args){
21. assign1(new ArrayList());
22. assign2(new ArrayList());
23. //assign3(new ArrayList()); //有未檢查轉換警告
24. assign1(new ArrayList<String>());
25. assign2(new ArrayList<String>());
26. assign3(new ArrayList<String>());
27. List<?> wildList = new ArrayList();
28. assign1(wildList);
29. assign2(wildList);
30. assign3(wildList);
31. }
32. }
List和List<?>的差別是:List是一個原始類型的List,它可以存放任何Object類型的對象,不需要編譯時類型檢 查。List<?>等價于List<Object>,它不是一個原始類型的List,它存放一些特定類型,隻是暫時還不确定是什 麼類型,需要編譯時類型檢查。是以List的效率要比List<?>高。
5.實作泛型接口注意事項:
由于泛型在編譯過程中檫除了參數類型資訊,是以一個類不能實作以泛型參數差別的多個接口,如:
[java] view plaincopy
1. interface Payable<T>{
2. }
3. class Employee implements Payable<Employee>{
4. }
5. class Hourly extends Employee implements Payable<Hourly>{
6. }
類Hourly無法編譯,因為由于泛型類型檫除,Payable<Employee>和Payable<Hourly>在編譯時是同一個類型Payable,是以無法同時實作一個接口兩次。
6.泛型方法重載注意事項:
由于泛型在編譯時将參數類型檫除,是以以參數類型來進行方法重載在泛型中要特别注意,如:
[java] view plaincopy
1. public class GenericMethod<W,T>{
2. void f(List<T> v) {
3. }
4. void f(List<W> v){
5. }
6. }
無法通過編譯,因為泛型檫除類型資訊,上面兩個方法的參數都被看作為Object類型,使用參數類型已經無法差別上面兩個方法,是以無法重載。
7.泛型中的自綁定:
通常情況下,一個類無法直接繼承一個泛型參數,但是你可以通過繼承一個聲明泛型參數的類,這就是java泛型程式設計中的自綁定,如:
[java] view plaincopy
1. class SelfBounded<T extends SelfBounded<T>>{
2. T element;
3. SelfBounded<T> set(T arg){
4. Element = arg;
5. return this;
6. }
7. T get(){
8. return element;
9. }
10. }
11. class A extends SelfBounded<A>{
12. }
13. class B extends SelfBounded<A>{
14. }
15. class C extends SelfBounded<C>{
16. C setAndGet(C arg){
17. set(arg);
18. return get();
19. }
20. }
21. public class SelfBounding{
22. public static void main(String[] args){
23. A a = new A();
24. a.set(new A());
25. a = a.set(new A()).get();
26. a = a.get();
27. C c = new C();
28. C = c.setAndGet(new C());
29. }
30. }
泛型的自綁定限制目的是用于強制繼承關系,即使用泛型參數的類的基類是相同的,強制所有人使用相同的方式使用參數基類。
9——集合容器進階
1.Arrays.asList()方法産生的List是一個固定長度的數組,隻支援不改變長度的操作,任何試圖改變其底層資料結構長度的操作(如,增加,删除等操作)都會抛出UnsupportedOperationException異常。
為了使Arrays.asList()方法産生的List集合長度可變,可以将其作為集合容器的構造方法參數,如:
Set set = new HashSet(Arrays.asList(newint[]{1,23}));
或者将其作為Collections.addAll()方法的參數,如:
Collections.addAll(Arrays.asList(new int[]{1,23}));
2.SortedSet是一個對其元素進行排序了的Set,SortedSet接口有以下方法:
(1).Comparator comparator():
傳回此Set中元素進行排序的比較器,如果該方法傳回null,則預設使用自然排序。
(2).Object first():
傳回Set中第一個(最低)元素。
(3).Object last():
傳回Set中最後一個(最高)元素。
(4).SortedSet subset(fromElement, toElement):
傳回此Set中從fromElement(包括)到toElement(不包括)的子Set。
(5).SortedSet headset(toElement):
傳回此Set中元素嚴格小于toElement的子Set。
(6).SortedSet tailSet(fromElement):
傳回此Set中元素大于等于fromElement的子Set。
3.SortedMap是一個根據Key排序的Map,SortedMap接口有以下方法:
(1).Comparator comparator():
傳回此Map中key進行排序的比較器,如果傳回的是null,則該Map的key使用自然排序。
(2).T firstKey():
傳回此Map中第一個(最低)key。
(3).T lastKey();
傳回此Map中最後一個(最高)key。
(4).SortedMap subMap(fromKey, toKey):
傳回此Map中key從fromKey(包括)到toKey(不包括)的子Map。
(5).SortedMap headMap(toKey):
傳回此Map中key嚴格小于toKey的子Map。
(6).SortedMap tailMap(fromKey):
傳回此Map中key大于等于fromKey的Map。
4.HashMap/HashSet等Hash算法集合重寫equals方法和hashCode方法:
HashSet,HashMap以及它們的子類等這些使用Hash算法的集合存放對象時,如果元素不是java中的8種基本類型(即元素都是對象類型),則必須重寫對象的equals和hashCode方法,否則會産生一些錯誤,例如:
[java] view plaincopy
1. Class A{
2. int i;
3. public A(int i){
4. this.i = i;
5. }
6. Public static void main(String args[]){
7. Map map = new HashMap();
8. map.put(new A(1), “First”);
9. map.put(new A(2), “Second”);
10. A a = new A(1);
11. boolean b = map.containsKey(a);
12. System.out.println(b);
13. }
14. }
輸出的結果是:false。
Map中有Key為A(1)對象,但是卻沒有找到,這是因為HashMap使用Hash算法根據對象的hashCode值來查找給定的對象,如果沒 有重寫hashCode和equals方法,則對象預設使用Object的hashCode方法,Object預設hashCode方法使用對象的記憶體地 址作為hashCode值。
為了使Hash算法集合存放對象類型資料符合使用者的期望,必須重寫對象的hashCode和equals方法,其中hashCode方法用于Hash算法的查找,equals方法用于對象比較,Hash算法中還要用到equals方法如下:
因為Hash算法所使用的hashCode可能會産生碰撞(不相等對象的hashCode值相同),當Hash算法産生碰撞時,就需要再次 Hash(即再次通過其他方法計算hashCode值),是以一個對象使用Hash算法時,其對應的HashCode值可能不止一個,而是一組,當産生碰 撞時就選擇另一個hashCode值。當hashCode值産生碰撞時,還必須使用equals方法方法對象是否相等。
注意:由于Hash算法有可能會産生碰撞(不相等的對象hashCode值相同),是以hashCode和equals方法有如下關系:
(1).equals方法相等的對象,hashCode方法值一定相同。
(2).hashCode方法相同的對象,equals不一定相等。
5.建立隻讀集合容器:
List,Set和Map類型的集合容器都可以通過下面的方法建立為隻讀,即隻可以通路,不能添加,删除和修改。
[java] view plaincopy
1. static Collection<String> data = new ArrayList<String>();
2. data.add(“test”);
3. static Map<String, String> m = new HashMap<String, String>();
4. m.put(“key”, “value”);
(1).隻讀集合:
[java] view plaincopy
1. Collection<String> c = Collections.unmodifiableCollection(new ArrayList<String>(data));
2. System.out.println(c); //可以通路
3. //c.add(“test2”);隻讀,不可添加
(2).隻讀List:
[java] view plaincopy
1. List<String> list = Collections.unmodifiableList(new ArrayList<String>(data));
2. System.out.println(list.get(0)); //可以通路
3. //list.remove(0);隻讀,不可删除
(3).隻讀Set:
[java] view plaincopy
1. Set<String> set = Collections.unmodifiableSet(new HashSet<String>(data));
2. System.out.println(set.Iterator().next()) //可以通路
3. //set.add(“test”);隻讀,不可添加
(4).隻讀Map:
[java] view plaincopy
1. Map<String, String> map = Collections.unmodifiableMap(new HashMap<String, String>(m));
2. System.out.println(map.get(“key”)); //可以通路
3. //map.put(“key2”, “value2”);隻讀,不可添加
隻讀集合容器會在編譯時檢查操作,如果對隻讀集合容器進行增删等操作時,将會抛出UnSupportedOperationException異常。
隻讀集合容器類似于将集合對象通路控制修飾符設定為private,不同之處在于,其他類可以通路,隻是不能修改。
6.線程同步集合容器:
Java集合容器中,Vector,HashTable等比較古老的集合容器是線程安全的,即處理了多線程同步問題。
而Java2之後對Vector和HashTable的替代類ArrayList,HashSet,HashMap等一些常用的集合容器都是非線程安全的,即沒有進行多線程同步處理。
Java中可以通過以下方法友善地将非線程安全的集合容器進行多線程同步:
(1).線程同步集合:
Collection<String> c= Collections.synchronizedCollection(newArrayList<String>());
(2).線程同步List:
List<String> c= Collections.synchronizedList(newArrayList<String>());
(3).線程同步Set:
Set<String> c= Collections.synchronizedSet(newHashSet<String>());
(4).線程同步Map:
Map<String> c= Collections.synchronizedMap(newHashMap<String, String>());
7.對象的強引用、軟引用、弱引用和虛引用:
JDK1.2以前版本中,隻存在一種引用——正常引用,即對象強引用,如果一個對象被一個引用變量所指向,則該對象是可觸及(reached)的狀态,JVM垃圾回收器不會回收它,弱一個對象不被任何引用變量指向,則該對象就處于不可觸及的狀态,垃圾回收器就會回收它。
從JDK1.2版本之後,為了更靈活的控制對象生命周期,引入了四種引用:強引用(java.lang.ref.Reference)、軟引用 (java.lang.ref.SoftReference)、弱引用(java.lang.ref.WeakReference)和虛引用 (java.lang.ref.PhantomReference):
(1). 強引用(java.lang.ref.Reference):
即Java程式中普遍使用的正常對象引用,存放在記憶體中得對象引用棧中,如果一個對象被強引用,則說明程式還在使用它,垃圾回收器不會回收,當記憶體不足時,JVM抛出記憶體溢出異常使程式異常終止。
(2). 軟引用(java.lang.ref.SoftReference):
如果一個對象隻具有軟引用,則記憶體空間足夠,垃圾回收器也不會回收它;如果記憶體空間不足了,就會回收這些對象的記憶體。隻要垃圾回收器沒有回收它,該對象就可以被程式使用。軟引用可用來實作記憶體敏感的高速緩存。
軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,JVM就會把這個軟引用加入到與之關聯的引用隊列中。
(3). 弱引用(java.lang.ref.WeakReference):
弱引用用來實作記憶體中對象的标準映射,即為了節約記憶體,對象的執行個體可以在一個程式内多處使用。
弱引用與軟引用的差別在于:隻具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的記憶體區域的過程 中,一旦發現了隻具有弱引用的對象,不管目前記憶體空間足夠與否,都會回收它的記憶體。不過,由于垃圾回收器是一個優先級很低的背景線程,是以不一定會很快發 現那些隻具有弱引用的對象。
弱引用也可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛拟機就會把這個弱引用加入到與之關聯的引用隊列中。
(4). 虛引用(java.lang.ref.PhantomReference):
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個差別在于:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的記憶體之前,把這個虛引用加入到與之 關聯的引用隊列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
JVM中,對象的引用往往都是很複雜的,各個對象之間互相引用形成一個記憶體引用樹,java中某個對象是否可觸及,有以下兩條判斷原則:
a.單條引用路徑可及性判斷:在這條路徑中,最弱的一個引用決定對象的可及性。
b.多條引用路徑可及性判斷:幾條路徑中,最強的一條的引用決定對象的可及性。
軟引用,弱引用,虛引用例子如下:
[java] view plaincopy
1. package com.test.reference;
2. //測試對象
3. class VeryBig {
4. private static final int SIZE = 10000;
5. private long[] la = new long[SIZE];
6. private String ident;
7.
8. public VeryBig(String id) {
9. ident = id;
10. }
11.
12. public String toString() {
13. return ident;
14. }
15.
16. protected void finalize() {
17. System.out.println("Finalizing " + ident);
18. }
19. }
20.
21. package com.test.reference;
22.
23. import java.lang.ref.PhantomReference;
24. import java.lang.ref.Reference;
25. import java.lang.ref.ReferenceQueue;
26. import java.lang.ref.SoftReference;
27. import java.lang.ref.WeakReference;
28. import java.util.LinkedList;
29.
30. public class TestReferences {
31. //引用隊列
32. private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();
33. //檢查引用隊列是否為空
34. public static void checkQueue() {
35. //強引用,輪詢引用隊列,看是否有可以的對象引用
36. Reference<? extends VeryBig> inq = rq.poll();
37. if (inq != null)
38. //如果有可以使用的對象引用,則列印出該引用指向的對象
39. System.out.println("In queue: " + inq.get());
40. }
41.
42. public static void main(String[] args) {
43. int size = 2;
44. //建立存放VeryBig對象的軟引用集合
45. LinkedList<SoftReference<VeryBig>> sa = new LinkedList<SoftReference<VeryBig>>();
46. for (int i = 0; i < size; i++) {
47. //将對象和軟引用添加到引用隊列中
48. sa.add(new SoftReference<VeryBig>(new VeryBig("Soft " + i), rq));
49. System.out.println("Just created: " + sa.getLast());
50. checkQueue();
51. }
52. //建立存放VeryBig對象的弱引用集合
53. LinkedList<WeakReference<VeryBig>> wa = new LinkedList<WeakReference<VeryBig>>();
54. for (int i = 0; i < size; i++) {
55. //将對象和弱引用添加到引用隊列中
56. wa.add(new WeakReference<VeryBig>(new VeryBig("Weak " + i), rq));
57. System.out.println("Just created: " + wa.getLast());
58. checkQueue();
59. }
60. SoftReference<VeryBig> s = new SoftReference<VeryBig>(new VeryBig(
61. "Soft"));
62. WeakReference<VeryBig> w = new WeakReference<VeryBig>(new VeryBig(
63. "Weak"));
64. //垃圾回收器回收,在回收之前調用對象的finalize()方法
65. System.gc();
66. //建立存放VeryBig對象的虛引用集合
67. LinkedList<PhantomReference<VeryBig>> pa = new LinkedList<PhantomReference<VeryBig>>();
68. for (int i = 0; i < size; i++) {
69. //将對象和虛引用添加到引用隊列中
70. pa.add(new PhantomReference<VeryBig>(new VeryBig("Phantom " + i),
71. rq));
72. System.out.println("Just created: " + pa.getLast());
73. checkQueue();
74. }
75. }
76. }
輸出結果為:
Just created:[email protected]
Just created:[email protected]
Just created:[email protected]
Just created:[email protected]
In queue: null
Finalizing Weak 0
Finalizing Weak
Finalizing Weak 1
Just created:[email protected]
In queue: null
Just created:[email protected]
注意:由于System.gc()隻是通知JVM虛拟機可以進行垃圾回收器可以進行垃圾回收了,但是垃圾回收器具體什麼時候允許說不清楚,是以這個輸出結果隻是個參考,每次運作的結果Finalize方法的執行順序不太一樣。
從程式可以看出,盡管對象被引用,垃圾回收器還是回收了被引用對象,引用隊列總是建立一個包含null對象的引用。
8.WeakHashMap:
WeakHashMap專門用于存放弱引用,WeakHashMap很容易實作弱引用對象标準映射功能。
在WeakHashMap中,隻存儲一份對象的執行個體及其值,當程式需要對象執行個體值時,WeakHashMap從現有的映射中找出已存在的對象值映射。
由于弱引用節約記憶體的技術,WeakHashMap允許垃圾回收器自動清除器存放的key和value。WeakHashMap自動将其中存放的key和value包裝為弱引用,當key不再被使用時,垃圾回收器自動回收該key和value。
10——檔案和目錄常用操作
1.檔案目錄的List操作:
Java中,File類其實代表檔案的路徑,它既可以代表一個特定檔案的檔案,也可以代表一個包含多個檔案的目錄名稱。如果File代表目錄,可以使用List列出目錄中檔案。
[java] view plaincopy
1. import java.util.regex.*;
2. import java.io.*;
3. import java.util.*;
4. public class DirList{
5. public static void main(String[] args){
6. //目前目錄
7. File path = new File(“.”);
8. String[] list;
9. //如果沒有指定參數,則将目錄中檔案全部列出
10. if(args.length == 0){
11. list = path.list();
12. }
13. //指定了參數,則根據指定檔案名過濾符合條件的檔案
14. else{
15. list = path.list(new DirFilter(args[0]));
16. }
17. Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
18. for(String dirItem : list){
19. System.out.println(dirItem);
20. }
21. }
22. }
23. class DirFilter implements FilenameFilter{
24. private Pattern pattern;
25. public DirFilter(String regex){
26. //将輸入的指令行參數編譯為正規表達式的模式串
27. pattern = Pattern.compile(regex);
28. }
29. //File的List方法回調方法
30. public boolean accept(File dir, String name){
31. //使用正規表達式比對給定目錄下的檔案名
32. return pattern.matcher(name).matches();
33. }
34. }
指令行輸入參數:“*\.java”
輸出結果如下:
DirFilter.java
DirList.java
2.java中除了File類可以表示檔案的路徑外,還可以表示目錄的路徑,可以通過File的isDirectory判斷File對象是一個檔案還是一個目錄。
如下的例子通過local()方法列出給定目錄中符合條件的檔案/目錄名稱,walk()方法周遊給定的目錄:
[java] view plaincopy
1. import java.util.regex.*;
2. import java.io.*;
3. import java.util.*;
4.
5. public final class Directory{
6. //列出目錄中符合條件的檔案名
7. public static File[] local(File dir, final String regex){
8. return dir.listFiles(new FilenameFilter(){
9. private Pattern pattern = Pattern.compile(regex);
10. public Boolean accept(File dir, String name){
11. return pattern.matcher(new File(name).getName()).matches();
12. }
13. });
14. }
15. //重載列出目錄下符合條件的檔案名方法
16. public static File[] local(String path, final String regex){
17. return local(new File(path), regex);
18. }
19. //代表檔案樹資訊的靜态内部類
20. public static class TreeInfo implements Iterable<File>{
21. public List<File> files = new ArrayList<File>();
22. public List<File> dirs = new ArrayList<File>();
23. //預設的疊代器方法,跌倒檔案樹元素對象
24. public Iterator<File> iterator(){
25. return files.iterator();
26. }
27. void addAll(TreeInfo other){
28. files.addAll(other.files);
29. dirs.addAll(other.dirs);
30. }
31. public String toString(){
32. return “dirs: ” + dirs + “\n\nfiles: ” + files;
33. }
34. }
35. //從指定的檔案/目錄開始周遊符合條件的檔案
36. public static TreeInfo walk(String start, String regex){
37. return recurseDirs(new File(start), regex);
38. }
39. //重載周遊檔案/目錄方法
40. \public static TreeInfo walk(File start, String regex){
41. return recurseDirs(start, regex);
42. }
43. //預設的指定檔案/目錄查找任何檔案名的檔案
44. public static TreeInfo walk(File start){
45. return recurseDirs(start, “.*”);
46. }
47. //重載預設的查找任何檔案的方法
48. public static TreeInfo walk(String start){
49. return recurseDirs(new File(start), “.*”);
50. }
51. //從指定的檔案/目錄開始周遊,查找符合條件的檔案名
52. static TreeInfo recurseDirs(File startDir, String regex){
53. TreeInfo result = new TreeInfo();
54. for(File item : startDir.listFiles()){
55. //如果周遊的檔案是目錄
56. if(item.isDirectory()){
57. result.dirs.add(item);
58. //疊代子目錄
59. result.addAll(recurseDirs(item, regex));
60. }
61. //如果周遊的的檔案是普通檔案
62. else{
63. if(item.getName().matches(regex)){
64. result.files.add(item);
65. }
66. }
67. }
68. return result;
69. }
70. }
3.檔案和目錄的其他操作:
檔案和目錄除了正常的查找和周遊操作意外,還有很多其他的操作,例如:建立、删除、判斷檔案/目錄是否已存在,擷取檔案.目錄的絕對路徑,已經檔案/目錄的權限等等,下面的小例子就展示檔案/目錄的這些操作:
[java] view plaincopy
1. import java.io.*;
2. public class MakeDirectories{
3. //擷取檔案/目錄的基本資訊
4. private static void fileData(File f){
5. System.out.println(
6. “Absolute path: ” + f.getAbsolutePath() +
7. “\n Can read: ” + f.canRead() +
8. “\n Can write: ” + f.canWrite() +
9. “\n getName: ” + f.getName() +
10. “\n getParent: ” + f.getParent() +
11. “\n getPath: ” + f.getPath() +
12. “\n length: ” + f.length() +
13. “\n lastModified: ” + f.lastModifed());
14. if(f.isFile()){
15. System.out.println(f.getName() + “ is a file”);
16. }
17. else if(f.isDirectory()){
18. System.out.println(f.getName() + “ is a directory”);
19. }
20. }
21. public static void main(String[] args){
22. File old = new File(“oldFile”);
23. File new = new File(“newFile”);
24. old.renameTo(new);
25. fileData(old);
26. fileData(new);
27. File d = new File(“/test”);
28. if(d.exists()){
29. System.out.println(“Deleting …” + d);
30. d.delete();
31. }
32. else {
33. System.out.prinln(“Creating…” + d);
34. d.mkdirs();
35. }
36. }
37. }
11——Java I/O
Java中使用流來處理程式的輸入和輸出操作,流是一個抽象的概念,封裝了程式資料于輸入輸出裝置交換的底層細節。JavaIO中又将流分為位元組流和字元流,位元組流主要用于處理諸如圖像,音頻視訊等二進制格式資料,而字元流主要用于處理文本字元等類型的輸入輸出。
1.位元組輸入流InputStream
輸入流InputStream負責從各種資料/檔案源産生輸入,輸入源包括:數組,字元串,檔案,管道,一系列其他類型的流,以及網絡連接配接産生的流等等。
常用位元組輸入流的主要類型:
(1).ByteArrayInputStream位元組數組輸入流:
主要功能:允許記憶體緩存作為輸入流。
ByteArrayInputStream包含一個内部緩沖區,該緩沖區包含從流中讀取的位元組。内部計數器跟蹤read()方法要提供的下一個位元組。
注意:關閉ByteArrayInputStream無效,該類中的方法在關閉此流之後仍可被調用,而不會産生任何的IOException。
(2).FileInputStream檔案輸入流:
主要功能:從檔案系統中的某個檔案獲得輸入位元組,用于讀取諸如圖像資料子類的原始位元組流。若要讀取字元流,請使用FileReader。
(3).PipedInputStream管道輸入流:
主要功能:和管道輸出流一起構成一個輸入輸出的管道,是管道的資料輸入端。
管道輸入流應該連接配接到管道輸出流,管道輸入流提供要寫入管道輸出流的所有資料位元組。通常,這些資料有某個線程從PipedInputStream對象中讀取,并有其他線程将其寫入到相應的PipedOutputStream對象中。
注意:不建議PipedInputStream和PipedOutputStream對象使用單線程,因為這樣可能思索線程。管道輸入流包含一個緩 沖區,可以在緩沖區限定範圍内将讀操作和寫操作分離開,如果先連接配接管道輸出流提供資料位元組的線程不再存在,則認為該管道已損壞。
(4).SequenceInputStream順序輸入流:
重要功能:将兩個或多個輸入流對象轉換為一個單個輸入流對象。
SequenceInputStream表示其他輸入流的邏輯串聯關系,它從輸入流的有序集合開始,并從第一個輸入流開始讀取,直到到達檔案末尾,接着從第二個輸入流讀取,以此類推,直到到達包含的最後一個輸入流的檔案末尾為止。
(5).FilterInputStream過濾輸入流:
主要功能:包含其他一些輸入流,将這些被包含的輸入流用作其基本資料源,它可以直接傳輸資料或者提供一些額外的功能。
常用的FilterInputStream是DataInputStream資料輸入流,主要用于允許程式以與機器無關的方式從底層輸入流中讀取java基本資料類型。其常用的方法有readInt(),readBoolean(),readChar()等等。
2.位元組輸出流OutputStream:
和位元組輸入流相對應,位元組輸出流負責位元組類型資料想目标檔案或裝置的輸出。常見的位元組輸出流如下:
(1).ByteArrayOutputStream位元組數組輸出流:
主要功能:在記憶體中建立一個緩沖區,将接收到的資料放入該記憶體緩沖區中。
ByteArrayOutputStream實作了一個輸出流,其中的資料被寫入一個byte數組中。緩沖區會随着資料的不斷寫入而自動增長,可使用toByteArray()和toString()方法擷取資料。
注意:和ByteArrayInputStream類似,關閉ByteArrayOutputStream也是無效的,此類中的方法在關閉此流後仍可被調用,而不會産生任何IOException。
(2).FileOutputStream檔案輸出流:
主要功能:将資料寫入到指定檔案中。
檔案輸出流是用于将資料寫入File或FIleDescriptor的輸出流,用于寫入諸如圖像資料之類的原始位元組流,若要寫入字元流,請使用FileWriter。
(3).PipedOutputStream管道輸出流:
主要功能:連接配接管道輸入流用來建立通信管道,管道輸出流是管道資料輸出端。
(4).FilterOutputStream過濾輸出流:
主要功能:用于将已存在的輸出流作為其基本資料接收器,可以直接傳輸資料或提供一些額外的處理。
常用的FIlterOutputStream是DataOutputStream資料輸出流,它允許程式以适當的方式将java基本資料類型寫入輸 出流中。其常用方法有writeInt(intV),writeChar(int v),writeByte(String s)等等。
3.字元流:
Java中得位元組流隻能針對位元組類型資料,即支援處理8位的資料類型,由于java中的是Unicode碼,即兩個位元組代表一個字元,于是在JDK1.1之後提供了字元流Reader和Writer。
字元流相關常用類如下:
(1).Reader:
用于讀取字元串流的抽象類,子類必須實作的方法隻有reader(char[],int, int)和close()。
(2).InputStreamReader:
是将位元組輸入流轉換為字元輸入流的轉換器,它使用指定的字元集讀取位元組并将其解碼為字元。即:位元組——>字元。
它使用的字元集可以有名稱指定或顯式給定,也可以使用平台預設的字元集。
(3).Writer:
用于寫入字元流的抽象類,子類必須實作的方法隻有write(char[],int, int)和close()。
(4).OutputStreamWriter:
是将字元輸出流轉換為位元組輸出流的轉換器,它使用指定的字元集将要寫入流的字元編碼成位元組。即:字元——>位元組。
4.綜合使用java IO各種流:
Java IO中的各種流,很少單獨使用,經常結合起來綜合使用,既可以滿足特定需求,又搞效。例子如下:
(1).使用緩沖流讀取檔案:
[java] view plaincopy
1. import java.io.*;
2.
3. public class BufferedInputFile{
4. public static String read(String filename) throws IOException{
5. //緩沖字元輸入流
6. BufferedReader in = new BufferedReader(new FileReader(filename));
7. String s;
8. StringBuilder sb = new StringBuilder();
9. //每次讀取檔案中的一行
10. While((s = in.readLine()) != null){
11. sb.append(s + “\n”);
12. }
13. in.close();
14. return sb.toString();
15. }
16. public static void main(String[] args) throws IOException{
17. System.out.println(read(“BufferedInputFile.java”));
18. }
19. }
(2).讀取記憶體中的字元串:
[java] view plaincopy
1. import java.io.*;
2.
3. public class MemoryInput{
4. public static void main(String[] args) throws IOException{
5. //将字元串包裝為字元輸入流
6. StringReader in = new StringReader(
7. BufferedInputFile.read(“BufferedInputFile.java”));
8. int c;
9. //讀取字元輸入流中的字元
10. while((c == in.read()) != -1){
11. System.out.println((char)c);
12. }
13. }
14. }
(3).資料輸入/輸出流:
[java] view plaincopy
1. import java.io.*;
2.
3. public class DataInputOutput{
4. public static void main(String[] args) thows IOException{
5. DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
6. new FileOutputStream(“Data.txt”)));
7. out.writeDouble(3.14159);
8. out.writeUTF(“That was pi”);
9. out.writeDouble(1.41413);
10. out.writeUTF(“Square root of 2”);
11. out.close();
12. DataInputStream in = new DataInputStream(new BufferedInputStream(
13. new FileOutputStream(“Data.txt”)));
14. System.out.println(in.readDouble());
15. System.out.println(in.readUTF());
16. System.out.println(in.readDouble());
17. System.out.println(in.readUTF());
18. }
19. }
輸出結果:
3.14159
That was pi
1.41413
Square root of 2
(4).文本檔案輸出流:
[java] view plaincopy
1. import java.io.*;
2.
3. public class TextFileOutput{
4. //輸出檔案名
5. static String file = “BasicFileOutput.out”;
6. public static void main(String[] args) throws IOException{
7. //将字元串先包裝成字元串輸入流,然後将字元串輸入流再包裝成緩沖字元輸入流
8. BufferedReader in = new BufferedReader(new StringReader
9. (BufferedInputFile.read(“TextFileOutput.java”)));
10. //字元輸出流
11. PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
12. int lineCount = 1;
13. String s;
14. While((s = in.readLine()) != null){
15. out.println(lineCount++ + “: ” + s);
16. }
17. out.close();
18. }
19. }
(5).二進制檔案讀寫:
[java] view plaincopy
1. import java.io.*;
2.
3. public class BinaryFileOutput{
4. //輸出檔案名
5. static String file = “BinaryFileOutput.out”;
6. public static void main(String[] args) throws IOException{
7. //将字元串先包裝成字元串輸入流,然後将字元串輸入流再包裝成緩沖字元輸入流
8. BufferedInputStream in = new BufferedInputStream(
9. new FileInputStream(“TestFile.png”)));
10. //字元輸出流
11. BufferedOutputStream out = new BufferedOutputStream (
12. new FileOutputStream(file));
13. byte[] buf = new byte[1024];
14. int n;
15. While((n = in.read(buf)) > 0){
16. out.write(buf, 0, n);
17. }
18. out.close();
19. }
20. }
12——Java new I/O(一)
為了提高Java I/O的速度和效率,從JDK1.4開始引入了java.nio.*包,即java new I/O(NIO)。
事實上,為了利用java nio的速度和效率優勢,原來的java I/O包中相關的類已經使用java nio重新實作,是以在程式設計中即使沒有顯式地使用java nio的代碼,使用傳統java I/O還是利用了nio的速度和效率優勢。Java nio的速度提高主要在:檔案I/O和網絡I/O兩個方面。
Java nio的速度提升主要因為使用了類似于作業系統本身I/O的資料結構:I/O通道(Channel)和緩沖區。
1.Channel通道:
通道表示實體,如硬體裝置、檔案、網絡套接字或可以執行的一個或多個不同的I/O操作(如讀取或寫入)的程式元件的開發的連結,用于I/O操作的連結。
通道可處于打開或關閉狀态。建立通道時它處于打開狀态,一旦将其關閉,則保持關閉狀态。一旦關閉了某個通道,試圖對其調用I/O操作都會抛出 ClosedChannelException異常,通過調用通道的isOpen()方法可以探測通道是否處于打開狀态。一般情況下通道對于多線程的通路 是安全的。
2.ByteBuffer位元組緩沖區:
位元組緩沖區是nio中唯一直接和通道channel打交道的緩沖區。位元組緩沖區可以存放原始位元組,也可以存放java的8中基本類型資料,但是不能存放引 用類型資料,String也是不能存放的。正是由于這種底層的存放類型似的位元組緩沖區可以更加高效和絕大部分作業系統的I/O進行映射。
位元組緩沖區通過allocation()方法建立,此方法為該緩沖區的内容配置設定空間,或者通過wrapping方法将現有的位元組數組包裝到緩沖區中來建立。
位元組緩沖區的常用操作:
(1).讀寫單個位元組的絕對和相對get和put方法:
a. 絕對方法:
get(int index):讀取指定索引處的位元組。
put(int index, byte b):将位元組寫入指定索引處。
b.相對方法:
get():讀取此緩沖區目前位置的位元組,然後該位置遞增。
put(byte b):将位元組寫入此緩沖區的目前位置,然後該位置遞增。
(2).相對批量get方法:
ByteBuffer get(byte[] dst):将此緩沖區的位元組傳輸到給定的目标數組中。
(3).相對批量put方法:
ByteBuffer put(byte[] src):将給定的源byte數組的所有内容傳輸到此緩沖區中。
(4).讀寫其他基本類型值:
getChar(), putChar(char value), getChare(int index), putChar(int index, char value).
getInt(), putInt(int value), getInt(int index), putInt(int index, int value)等等讀寫基本類型值得相對和絕對方法。
注意:基本類型值得相對和絕對讀寫方法,根據java基本類型資料底層位元組數進行緩沖區移動。
(5).建立視圖緩沖區:
為了通路同類二進制資料,允許将位元組緩沖區視為包含它們基本類型值的緩沖區,視圖緩沖區隻是其内容受該位元組緩沖區支援的另一種緩沖區。位元組緩沖區和視圖緩沖區内容更改是互相可見的。這兩種緩沖區的位置、限制和标記值都是獨立的。建立方法如下:
asCharBuffer(), asDoubleBuffer(), asFloatBuffer(), asIntBuffer(), asLongBuffer(), asReadOnlyBuffer(), asShortBuffer()。
與具體類型的get和put方法系列相比,視圖緩沖區優勢如下:
a視圖緩沖區不是根據位元組進行索引,而是根據其特定類型的值得大小進行索引。
b.視圖緩沖區提供了相對批量get和put方法,這些方法可以在緩沖區和數組或相同類型的其他緩沖區直接傳輸連續序列的值。
c.視圖緩沖區可能更搞效,因為當且僅當其支援的位元組緩沖區為直接緩沖區時它才是直接緩沖區。
(6).緩沖區其他操作:
a.ByteBuffer compact():壓縮緩沖區,從緩沖區寫入資料之後調用,以防寫入不完整。
b.ByteBuffer duplicate():建立共享此緩沖區内容的新的位元組緩沖區,新緩沖區中的内容為此緩沖區的内容,此緩沖區和新緩沖區内容更改是互相可見的。
c.ByteBuffer slice():建立新的位元組緩沖區,其内容是此緩沖區内容的共享子序列。
3.直接與非直接緩沖區:
位元組緩沖區要麼是直接的,要麼是非直接的。
如果位元組緩沖區是直接的,則JVM會盡最大努力直接在此緩沖區上執行本機的I/O操作,即在每次調用底層作業系統的一個本機I/O之前(或之後),JVM 都會盡量避免将緩沖區的内容複制到中間緩沖區中(或盡量避免從中間緩沖區複制内容)。直接位元組緩沖區可以通過調用allocateDirect()方法來 建立,此方法傳回的緩沖區進行配置設定和取消配置設定所需的成本通常高于非直接緩沖區。直接緩沖區的内容可以駐留在正常的垃圾回收堆之外,是以它對應用程式的記憶體 需求量造成的影響可能并不明顯,是以建議将直接緩沖區主要配置設定給那些容易受底層作業系統的本機I/O操作影響的大型的、持久的緩沖區
可以通過調用isDirect()來判斷位元組緩沖區是直接的還是非直接的。
4.檔案通道:
Java I/O的FileInputStream,FileOutputStream和RandomAccessFile可以産生檔案通道,例子如下:
[java] view plaincopy
1. import java.nio.*;
2. import java.nio.channels.*;
3. import java.io.*;
4.
5. public class FileChannel{
6. //配置設定位元組緩沖區時指定其大小
7. private static final int BSIZE = 1024;
8. public static void main(String[] args) throw Exception{
9. //擷取FileOutputStram的檔案通道
10. FileChannel fc = new FileOutputStream(“data.txt”).getChannel();
11. //向位元組緩沖區中寫入位元組數組
12. fc.write(ByteBuffer.wrap(“Some text”.getBytes()));
13. fc.close();
14. //以讀寫方式擷取随機通路檔案的檔案通道
15. fc = new RandomAccessFile(“data.txt”, “rw”).getChannel();
16. //定位到位元組緩沖區目前内容之後
17. fc.position(fc.size());
18. //向位元組緩沖區中追加内容
19. fc.write(ByteBuffer.wrap(“Some more”.getBytes()));
20. fc.close();
21. //擷取FileInputStream的檔案通道
22. fc = new FileInputStream(“data.txt”).getChannel();
23. //配置設定位元組緩沖區
24. ByteBuffer buff = ByteBuffer.allocate(BSIZE);
25. //将位元組數組從檔案通道讀入到位元組緩沖區中
26. fc.read(buff);
27. //放置緩沖區,為緩沖區寫出或相對擷取做準備
28. buff.flip();
29. //判斷緩沖區是是否還有元素
30. while(buff.hasRemaining()){
31. //擷取位元組緩沖區位元組的相對方法
32. System.out.println((char)buff.get());
33. }
34. }
35. }
輸出結果:
Some text Some more
傳統java I/O中FileInputStream, FileOutputStream和RandomAccessFile三個類可以産生檔案通道。
注意:Java new I/O的目标是提高I/O速度,快速移動大批量的資料,是以,位元組緩沖區的大小非常重要,例子中的1K大小不一定是合适的,應用程式需要在生産環境中測試确定合适的緩沖區大小。
5.檔案複制:
檔案通道和位元組緩沖區不但可以實作資料從檔案讀入和讀出,還可以實作檔案的複制,例子如下:
[java] view plaincopy
1. import java.nio.*;
2. import java.nio.channels
import java.util.*;
import java.lang.ref.*;
class Key {
String id;
public Key(String id) {
this.id = id;
}
public String toString() {
return id;
}
public int hashCode() {
return id.hashCode();
}
public boolean equals(Object r) {
return (r instanceof Key) && id.equals(((Key) r).id);
}
public void finalize() {
System.out.println("Finalizing Key " + id);
}
}
class Value {
String id;
public Value(String id) {
this.id = id;
}
public String toString() {
return id;
}
public void finalize() {
System.out.println("Finalizing Value " + id);
}
}
public class MapCache {
public static void main(String[] args) throws Exception {
int size = 1000;
// 或者從指令行獲得size的大小
if (args.length > 0)
size = Integer.parseInt(args[0]);
Key[] keys = new Key[size]; // 存放鍵對象的強引用
WeakHashMap<Key, Value> whm = new WeakHashMap<Key, Value>();
for (int i = 0; i < size; i++) {
Key k = new Key(Integer.toString(i));
Value v = new Value(Integer.toString(i));
if (i % 3 == 0)
keys[i] = k; // 使Key對象持有強引用
whm.put(k, v); // 使Key對象持有弱引用
}
// 催促垃圾回收器工作
System.gc();
// 把CPU讓給垃圾回收器線程
Thread.sleep(8000);
}
}
4. Java中的析構方法finalize
finalize()方法常稱之為終止器
protected void finalize(){
// finalization code here
}
對象即将被銷毀時,有時需要做一些善後工作。可以把這些操作寫在finalize()方法裡。
Java終止器卻是在對象被銷毀時調用。一旦垃圾收集器準備好釋放無用對象占用的存儲空間,它首先調用那些對象的finalize()方法,然後才真正 回收對象的記憶體。而被丢棄的對象何時被銷毀,應用是無法獲知的。大多數場合,被丢棄對象在應用終止後仍未銷毀。到程式結束的時候,并非所有收尾子產品都會得 到調用。
5. 應用能幹預垃圾回收嗎?
在應用代碼裡控制JVM的垃圾回收運作是不可能的事。
對垃圾回收有兩個途徑。第一個就是将指向某對象的所有引用變量全部移走。這就相當于向JVM發了一個消息:這個對象不要了。第二個是調用庫方法 System.gc()。第一個是一個告知,而調用System.gc()也僅僅是一個請求。JVM接受這個消息後,并不是立即做垃圾回收,而隻是對幾個 垃圾回收算法做了權重,使垃圾回收操作容易發生,或提早發生,或回收較多而已。
希望JVM及時回收垃圾,是一種需求。其實,還有相反的一種需要:在某段時間内最好不要回收垃圾。要求運作速度最快的實時系統,特别是嵌入式系統,往往希望如此。
Java的垃圾回收機制是為所有Java應用程序服務的,而不是為某個特定的程序服務的。是以,任何一個程序都不能指令垃圾回收機制做什麼、怎麼做或做多少。
6. 垃圾回收算法
* 引用計數
該算法在java虛拟機沒被使用過,主要是循環引用問題,因為計數并不記錄誰指向他,無法發現這些互動自引用對象。
-- 怎麼計數?
當引用連接配接到對象時,對象計數加1
當引用離開作用域或被置為null時減1
-- 怎麼回收?
周遊對象清單,計數為0就釋放
-- 有什麼問題?
循環引用問題。
* 标記算法
标記算法的思想是從堆棧和靜态存儲區的對象開始,周遊所有引用,标記活得對象。
對于标記後有兩種處理方式:
(1) 停止-複制
-- 所謂停止,就是停止在運作的程式,進行垃圾回收
-- 所謂複制,就是将活得對象複制到另外一個堆上,以使記憶體更緊湊
-- 優點在于,當大塊記憶體釋放時,有利于整個記憶體的重配置設定
-- 有什麼問題?
一、停止,幹擾程式的正常運作,二,複制,明顯耗費大量時間,三,如果程式比較穩定,垃圾比較少,那麼每次重新複制量是非常大的,非常不合算
-- 什麼時候啟動停止-複制?
記憶體數量較低時,具體多低我也不知道
(2) 清除 也稱标記-清除算法
-- 也就是将标記為非活得對象釋放,也必須暫停程式運作
-- 優點就是在程式比較穩定,垃圾比較少的時候,速度比較快
-- 有什麼問題?
很顯然停止程式運作是一個問題,隻清除也會造成很對記憶體碎片。
-- 為什麼這2個算法都要暫停程式運作?
這是因為,如果不暫停,剛才的标記會被運作的程式弄亂
java正規表達式學習總結,以及和javascript正規表達式的差別
用正規表達式處理字元串功能非常強大,下面總結一下java正規表達式的一些知識:
基本元字元
. 任何字元(與行結束符可能比對也可能不比對)
// 反斜杠
/t 間隔 ('/u0009')
/n 換行 ('/u000A')
/r 回車 ('/u000D')
/d 數字 等價于[0-9]
/D 非數字 等價于[^0-9]
/s 空白符号 [/t/n/x0B/f/r]
/S 非空白符号 [^/t/n/x0B/f/r]
/w 單獨字元 [a-zA-Z_0-9]
/W 非單獨字元 [^a-zA-Z_0-9]
/f 換頁符
/e Escape
/b 一個單詞的邊界
/B 一個非單詞的邊界
/G 前一個比對的結束
^為限制開頭
$為限制結尾
加入特定限制條件[]
[a-z] 條件限制在小寫a to z範圍中一個字元
[A-Z] 條件限制在大寫A to Z範圍中一個字元
[a-zA-Z] 條件限制在小寫a to z或大寫A to Z範圍中一個字元
[0-9] 條件限制在小寫0 to 9範圍中一個字元
[0-9a-z] 條件限制在小寫0 to 9或a to z範圍中一個字元
[0-9[a-z]] 條件限制在小寫0 to 9或a to z範圍中一個字元(交集)
[]中加入^後加再次限制條件[^]
[^a-z] 條件限制在非小寫a to z範圍中一個字元
[^A-Z] 條件限制在非大寫A to Z範圍中一個字元
[^a-zA-Z] 條件限制在非小寫a to z或大寫A to Z範圍中一個字元
[^0-9] 條件限制在非小寫0 to 9範圍中一個字元
[^0-9a-z] 條件限制在非小寫0 to 9或a to z範圍中一個字元
[^0-9[a-z]] 條件限制在非小寫0 to 9或a to z範圍中一個字元(交集)
表示比對次數的符号
{n,}:至少n次
“或”符号
可以使用“|”操作符。“|”操作符的基本意義就是“或”運算。要比對“toon”,使用“t(a|e|i|o|oo)n”正規表達式。這裡不能使用方擴号,因為方括号隻允許比對單個字元;這裡必須使用圓括号“()”。圓括号還可以用來分組。
捕獲組
>捕獲組可以通過從左到右計算其開括号來編号。例如,在表達式 ((A)(B(C))) 中,存在四個這樣的組:
1 | ((A)(B(C))) |
2 | /A |
3 | (B(C)) |
4 | (C) |
以 (?) 開頭的組是純的非捕獲 組,它不捕獲文本,也不針對組合計進行計數。
比對模式
java正規表達式預設使用的是最長比對模式,即:如果用ro*去比對room,并将其替換為r,則結果為rm。
如果某些情況下想使用最短比對,即:如果用ro?去比對room,并将其替換為b,則結果為boom。
總結:預設使用最長比對,但是結尾為"?"的模式使用的是最短比對。
基本用法
Pattern pattern = Pattern.compile(正規表達式模式串);
Matcher matcher = pattern.matcher(要驗證或處理的源字元串);
matcher.matches();如果比對傳回true,否則,false.
JavaScript正規表達式和java正規表達式的差別
javascript中的正規表達式和java的正規表達式基本上是相同的,差別在于分組引用和對象,方法
具體差別:
1).javascript正規表達式建立有兩種方法:
a.顯式建立:
var re = new RegExp("正規表達式模式串");
re.test(要校驗或處理的源字元串);
b.隐式建立:
var re = /正規表達式模式串/;
要校驗或處理的源字元串.match(re);
2).分組捕獲對象引用方式不同
javascript也是使用"()"進行分組,但是捕獲對象用RegExp對象的$1到$99來引用捕獲對象。
附錄:常用的javascript正規表達式,java的也類似
ip位址校驗正規表達式(IPv4):
/^(/d{1,2}|1/d/d|2[0-4]/d|25[0-5])(/.(/d{1,2}|1/d/d|2[0-4]/d|25[0-5])){3}$/
Email校驗正規表達式:
/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(/.[a-zA-Z0-9_-]+)+$/
格式為:2010-10-08類型的日期格式校驗正規表達式:
/^/d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[1-2]/d|3[0-1])$/
格式為:23:11:34類型的時間格式校驗正規表達式:
/^([0-1]/d|2[0-3]):[0-5]/d:[0-5]/d$/
防範JAVA記憶體洩漏解決方案
Java是如何管理記憶體
為了判斷Java中是否有記憶體洩露,我們首先必須了解Java是如何管理記憶體的。Java的記憶體管理就是對象的配置設定和釋放問題。在Java中,程式 員需要通過關鍵字new為每個對象申請記憶體空間 (基本類型除外),所有的對象都在堆 (Heap)中配置設定空間。另外,對象的釋放是由GC決定和執行的。在Java中,記憶體的配置設定是由程式完成的,而記憶體的釋放是有GC完成的,這種收支兩條線 的方法确實簡化了程式員的工作。但同時,它也加重了JVM的工作。這也是Java程式運作速度較慢的原因之一。因為,GC為了能夠正确釋放對象,GC必須 監控每一個對象的運作狀态,包括對象的申請、引用、被引用、指派等,GC都需要進行監控。
監視對象狀态是為了更加準确地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。
為了更好了解GC的工作原理,我們可以将對象考慮為有向圖的頂點,将引用關系考慮為圖的有向邊,有向邊從引用者指向被引對象。另外,每個線程對象可 以作為一個圖的起始頂點,例如大多程式從main程序開始執行,那麼該圖就是以main程序頂點開始的一棵根樹。在這個有向圖中,根頂點可達的對象都是有 效對象,GC将不回收這些對象。如果某個對象 (連通子圖)與這個根頂點不可達(注意,該圖為有向圖),那麼我們認為這個(這些)對象不再被引用,可以被GC回收。
以下,我們舉一個例子說明如何用有向圖表示記憶體管理。對于程式的每一個時刻,我們都有一個有向圖表示JVM的記憶體配置設定情況。以下右圖,就是左邊程式運作到第6行的示意圖。
Java使用有向圖的方式進行記憶體管理,可以消除引用循環的問題,例如有三個對象,互相引用,隻要它們和根程序不可達的,那麼GC也是可以回收它們 的。這種方式的優點是管理記憶體的精度很高,但是效率較低。另外一種常用的記憶體管理技術是使用計數器,例如COM模型采用計數器方式管理構件,它與有向圖相 比,精度行低(很難處理循環引用的問題),但執行效率很高。
什麼是Java中的記憶體洩露
下面,我們就可以描述什麼是記憶體洩漏。在Java中,記憶體洩漏就是存在一些被配置設定的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有 向圖中,存在通路可以與其相連;其次,這些對象是無用的,即程式以後不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的記憶體 洩漏,這些對象不會被GC所回收,然而它卻占用記憶體。
在C++中,記憶體洩漏的範圍更大一些。有些對象被配置設定了記憶體空間,然後卻不可達,由于C++中沒有GC,這些記憶體将永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,是以程式員不需要考慮這部分的記憶體洩露。
通過分析,我們得知,對于C++,程式員需要自己管理邊和頂點,而對于Java程式員隻需要管理邊就可以了(不需要管理頂點的釋放)。通過這種方式,Java提高了程式設計的效率。
是以,通過以上分析,我們知道在Java中也有記憶體洩漏,但範圍比C++要小一些。因為Java從語言上保證,任何對象都是可達的,所有的不可達對象都由GC管理。
對于程式員來說,GC基本是透明的,不可見的。雖然,我們隻有幾個函數可以通路GC,例如運作GC的函數System.gc(),但是根據Java 語言規範定義, 該函數不保證JVM的垃圾收集器一定會執行。因為,不同的JVM實作者可能使用不同的算法管理GC。通常,GC的線程的優先級别較低。JVM調用GC的策 略也有很多種,有的是記憶體使用到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心 這些。除非在一些特定的場合,GC的執行影響應用程式的性能,例如對于基于Web的實時系統,如網絡遊戲等,使用者不希望GC突然中斷應用程式執行而進行垃 圾回收,那麼我們需要調整GC的參數,讓GC能夠通過平緩的方式釋放記憶體,例如将垃圾回收分解為一系列的小步驟執行,Sun提供的HotSpot JVM就支援這一特性。
下面給出了一個簡單的記憶體洩露的例子。在這個例子中,我們循環申請Object對象,并将所申請的對象放入一個Vector中,如果我們僅僅釋放引 用本身,那麼Vector仍然引用該對象,是以這個對象對GC來說是不可回收的。是以,如果對象加入到Vector後,還必須從Vector中删除,最簡 單的方法就是将Vector對象設定為null。
Vector v=new Vector(10); for (int i=1;i<100; i++) { Object o=new Object(); v.add(o); o=null; } |
//此時,所有的Object對象都沒有被釋放,因為變量v引用這些對象。
幾種典型的記憶體洩漏
我們知道了在Java中确實會存在記憶體洩漏,那麼就讓我們看一看幾種典型的洩漏,并找出他們發生的原因和解決方法。
(1) 全局集合
在大型應用程式中存在各種各樣的全局資料倉庫是很普遍的,比如一個JNDI-tree或者一個session table。在這些情況下,必須注意管理儲存庫的大小。必須有某種機制從儲存庫中移除不再需要的資料。
通常有很多不同的解決形式,其中最常用的是一種周期運作的清除作業。這個作業會驗證倉庫中的資料然後清除一切不需要的資料。
另一種管理儲存庫的方法是使用反向連結(referrer)計數。然後集合負責統計集合中每個入口的反向連結的數目。這要求反向連結告訴集合何時會退出入口。當反向連結數目為零時,該元素就可以從集合中移除了。
(2) 緩存
緩存一種用來快速查找已經執行過的操作結果的資料結構。是以,如果一個操作執行需要比較多的資源并會多次被使用,通常做法是把常用的輸入資料的操作結果進 行緩存,以便在下次調用該操作時使用緩存的資料。緩存通常都是以動态方式實作的,如果緩存設定不正确而大量使用緩存的話則會出現記憶體溢出的後果,是以需要 将所使用的記憶體容量與檢索資料的速度加以平衡。
常用的解決途徑是使用java.lang.ref.SoftReference類堅持将對象放入緩存。這個方法可以保證當虛拟機用完記憶體或者需要更多堆的時候,可以釋放這些對象的引用。
(3) 類裝載器
Java類裝載器的使用為記憶體洩漏提供了許多可乘之機。一般來說類裝載器都具有複雜結構,因為類裝載器不僅僅是隻與"正常"對象引用有關,同時也和對象内 部的引用有關。比如資料變量,方法和各種類。這意味着隻要存在對資料變量,方法,各種類和對象的類裝載器,那麼類裝載器将駐留在JVM中。既然類裝載器可 以同很多的類關聯,同時也可以和靜态資料變量關聯,那麼相當多的記憶體就可能發生洩漏。
如何檢測記憶體洩漏
最後一個重要的問題,就是如何檢測Java的記憶體洩漏。目前,我們通常使用一些工具來檢查Java程式的記憶體洩漏問題。市場上已有幾種專業檢查 Java記憶體洩漏的工具,它們的基本工作原理大同小異,都是通過監測Java程式運作時,所有對象的申請、釋放等動作,将記憶體管理的所有資訊進行統計、分 析、可視化。開發人員将根據這些資訊判斷程式是否有記憶體洩漏問題。這些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
下面,我們将簡單介紹Optimizeit的基本功能和工作原理。
Optimizeit Profiler版本4.11支援Application,Applet,Servlet和Romote Application四類應用,并且可以支援大多數類型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。并且,該軟體是由Java編寫,是以它支援多種作業系統。Optimizeit系列還包 括Thread Debugger和Code Coverage兩個工具,分别用于監測運作時的線程狀态和代碼覆寫面。
當設定好所有的參數了,我們就可以在OptimizeIt環境下運作被測程式,在程式運作過程中,Optimizeit可以監視記憶體的使用曲線(如 下圖),包括JVM申請的堆(heap)的大小,和實際使用的記憶體大小。另外,在運作過程中,我們可以随時暫停程式的運作,甚至強行調用GC,讓GC進行 記憶體回收。通過記憶體使用曲線,我們可以整體了解程式使用記憶體的情況。這種監測對于長期運作的應用程式非常有必要,也很容易發現記憶體洩露。
在運作過程中,我們還可以從不同視角觀查記憶體的使用情況,Optimizeit提供了四種方式:
· 堆視角。 這是一個全面的視角,我們可以了解堆中的所有的對象資訊(數量和種類),并進行統計、排序,過濾。了解相關對象的變化情況。
· 方法視角。通過方法視角,我們可以得知每一種類的對象,都配置設定在哪些方法中,以及它們的數量。
· 對象視角。給定一個對象,通過對象視角,我們可以顯示它的所有出引用和入引用對象,我們可以了解這個對象的所有引用關系。
· 引用圖。 給定一個根,通過引用圖,我們可以顯示從該頂點出發的所有出引用。
在運作過程中,我們可以随時觀察記憶體的使用情況,通過這種方式,我們可以很快找到那些長期不被釋放,并且不再使用的對象。我們通過檢查這些對象的生 存周期,确認其是否為記憶體洩露。在實踐當中,尋找記憶體洩露是一件非常麻煩的事情,它需要程式員對整個程式的代碼比較清楚,并且需要豐富的調試經驗,但是這 個過程對于很多關鍵的Java程式都是十分重要的。
綜上所述,Java也存在記憶體洩露問題,其原因主要是一些對象雖然不再被使用,但它們仍然被引用。為了解決這些問題,我們可以通過軟體工具來檢查記憶體洩露,檢查的主要原理就是暴露出所有堆中的對象,讓程式員尋找那些無用但仍被引用的對象。