1. 程式人生 > >整理Java面向物件程式設計的筆記

整理Java面向物件程式設計的筆記

(Tips:章節標題大32,中標題中18)

第三章:資料型別和變數

基本資料型別

資料型別在記憶體佔用位元組數預設值

boolean佔1個位元組flase

byte 佔1個位元組 0

short2個位元組0

int4個位元組 0

long8個位元組 0

char2個位元組 "\u0000"

float4個位元組0.0F

double8個位元組0.0D

String字串,陣列,類型別變數也佔用記憶體,大小取決於虛擬機器的實現

new關鍵字建立物件的過程

1:為物件分配記憶體空間,將物件的例項變數自動初始化為期變數型別的預設值

2:如果例項變數在宣告時顯示初始化,那就把初始化值賦給 例項變數

3:呼叫構造方法

4:返回物件引用

變數的作用域和生命週期

作用域

成員變數:在類中宣告,作用域是整個類

區域性變數:在方法內部或者方法的程式碼塊的內部宣告。如果在方法內部宣告,作用域是整個方法,如果是方法的某個程式碼塊,也就是{ }裡面的內容,作用域也侷限在{ }

方法引數:方法或者構造方法的引數,作用域是方法引數的( )裡

類的成員變數有兩種:靜態變數和例項變數

類的靜態變數在記憶體中只有一個,java虛擬機器在載入類的過程中為靜態變數分配記憶體,它位於方法區,被類的所有例項共享,可以直接用類名 . 直接訪問,生命週期取決於類

類的例項變數,每次建立一個類的例項,Java虛擬機器就會為例項變數分配一次記憶體,例項變數位於堆中。例項變數的生命週期取決於例項的生命週期

區域性變數的宣告週期:當呼叫一個方法時,會為這個方法中的區域性變數分配記憶體,當結束呼叫時,會結束這個方法中的區域性變數的宣告週期。(區域性變數必須顯示初始化)

區域性變數不能被public private protect  static等修飾,也不能通過類名和引用變數名來訪問。

關於記憶體圖去看書97頁

this關鍵字:當一個物件建立好後,java虛擬機器會分配一個引用自身的指標:this,    

this是指當前物件自己

第六章:繼承

Sub類和Base類位於同一包下:Sub繼承Base類中public,protected,和預設級別的成員變數和成員方法

Sub類和Base類位於不同包下:Sub繼承Base類中public,protected的成員變數和成員方法

方法過載:

方法名相同

方法引數的型別,個數,順序至少有一個不同

方法返回型別可以不同

方法修飾符可以不同

方法覆蓋

子類方法的名稱,引數,和返回型別必須與父類方法相同

子類方法不能縮小父類方法的訪問許可權

子類方法不能丟擲比父類方法更多的異常

方法覆蓋只存在於子類和父類

子類可以定義與父類的靜態方法同名的靜態方法

父類的非靜態方法不可以被子類覆蓋為靜態方法

父類的私有方法不能被子類覆蓋

父類的抽象方法可以被子類通過兩種途徑覆蓋:一是子類實現父類的抽象方法;二是子類重新宣告父類的抽象方法

super

super的使用場景

在子類的構造方法中呼叫父類構造方法,

在子類中使用被父類的被遮蔽的方法和屬性。

(Tips:只能在構造方法和例項方法中使用使用super關鍵字,而在靜態方法和靜態程式碼塊中不能使用super關鍵字)

繼承的利弊和使用原則

可以提高程式程式碼的可重用性,提高可擴充套件性。

繼承樹的層次不可太多,繼承樹的上層為抽象層,

繼承的最大弱點:打破封裝

!!!!!!!!!!!!!!!

精心設計用於被繼承的類

由於繼承關係會打破封裝,因此隨意繼承物件模型中的任意一個類是不安全的做法。

在建立物件模型時,應該充分考慮軟體系統中哪些地方需要擴充套件,為這些地方提供擴充套件點。

(1)對這些類提供良好的文件說明,使得建立該類的子類的開發人員指導如何正確安全的擴充套件它。

對於那些允許子類覆蓋的方法,應該詳細的描述該方法的自用型,以及子類覆蓋此方法可能帶來的影響。

比如父類有一個speak()方法,此方法裡面又呼叫了change()方法,這樣在子類覆蓋父類的speak()方法時,change()也會受到影響。

(2)儘可能封裝父類的實現細節,也就是把代表實現細節的屬性和方法定義為private型別。如果這些實現細節必須被子類訪問,可以在父類中把包含

這種實現細節的方法定義為protected型別。當子類僅僅呼叫父類的protected型別的方法,而不覆蓋它時,可以把這種protected型別的方法看做父類

向子類但不對外公開的介面。

(3) 把不允許子類覆蓋的方法定義為final型別。

(4)父類的構造方法中不可以呼叫不能被子類覆蓋的方法,因為子類的構造方法中預設super()呼叫父類構造方法,這樣會導致不可預料的錯誤。

(5)如果某些類不是專門為了繼承而設計,那麼隨意繼承它時不安全的。

那麼可以把這這個類定義為final型別,

或者把這個類的所有構造方法宣告為priavte型別,然後通過一些靜態方法來負責構建自身的例項。

組合關係和繼承關係的比較

組合關係 繼承關係

1:不破壞封裝,整體類於區域性類之間鬆耦合,彼此相對獨立破壞封裝,子類與父類之間緊密耦合,子類依賴父類的實現,子類缺乏獨立性

2:具有良好的可擴充套件性支援擴充套件,但是增加系統結構的複雜程度

3:支援動態組合,在執行時,整體物件可以選擇不同型別的區域性物件 不支援動態繼承,在執行時,子類無法選擇不同的父類

4:整體類可以對區域性類進行包裝,封裝區域性類的介面,提供新的介面子類不能改變父類的介面

5:整體類不能自動獲得和區域性類相同的介面子類能自動繼承父類的介面

6:建立整體類的物件時,需要建立所有區域性類的物件建立子類的物件時,無須建立父類的物件

組合關係1234是優點,繼承關係有56的優點

第7章 修飾符

訪問修飾符:

public:公開的

private:私有的,只有類本身可以訪問,不對外公開

預設修飾符:向同一包內的類公開

protected:   只向子類和同一包的類公開

abstract:抽象

(1)抽象類中可以沒有抽象方法,但包含了抽象方法的類必須是抽象類。如果子類沒有實現父類所有的抽象方法,那麼子類也必須是抽象類

(2)沒有抽象構造方法,也沒有抽象靜態方法。

(3)抽象類中可以有非抽象的構造方法,建立子類的例項時可能會呼叫這些構造方法

抽象類不能例項化,但是可以建立一個引用變數,其型別是個抽象類,並讓它指向子類的一個例項。

(4)抽象類及抽象方法不能被final修飾符修飾。

abstract與final,private,static連線是無意義的

final修飾符

用final修飾的類不能被繼承

用final修飾的方法不能被子類覆蓋

private型別的方法都預設是final的方法,因為不能被子類的方法覆蓋

用final修飾的變量表示常量,只能被賦值一次

final類   (例如String類不可以被繼承)

繼承關係的弱點是打破封裝,子類能夠訪問父類的實現細節,而且能以方法覆蓋的方式修改實現細節。

如果不是專門為繼承而設計的類,類本身的方法之間有複雜的地排程關係,假如隨意建立這些類的子類,可能會錯誤的修改父類的實現細節

出於安全的原因,類的實現細節不允許有任何改動

在建立物件模型時,確信這個類不會再被擴充套件

final方法(例如getClass()方法不可以被覆蓋)

出於安全的考慮,父類不允許子類覆蓋某個方法,此時可以把這個方法宣告為final型別

final變數

final修飾符可以修飾靜態變數,例項變數和區域性變數分別為靜態常量,例項常量和區域性常量

靜態常量一般以大寫字母命名,單詞之間用_分開

final修飾的成員變數必須顯示初始化,否則會導致編譯錯誤

對於final型別的例項變數,可以在定義變數時,或者在構造方法中進行初始化,對於final型別的靜態變數,可以在定義變數時進行初始化,或者在靜態程式碼塊中初始化

final變數只能被賦值一次

final修飾的變數的作用

提高程式的安全性,

提高程式程式碼的可維護性

提高程式程式碼的可讀性

static修飾符

static修飾符可以用來修飾類的成員變數,成員方法和程式碼塊

用static修飾的成員變量表示靜態變數,可以直接通過類名訪問

用static修飾的成員方法表示靜態方法,可以直接通過類名訪問

用static修飾的程式程式碼塊表示靜態程式碼塊,當Java虛擬機器載入類時,就會執行該程式碼塊

類的成員變數有兩種,一個是靜態變數,另一個是例項變數

靜態變數在記憶體中只有一個拷貝,執行時java虛擬機器只為靜態變數分配一次記憶體,在載入類的過程中完成靜態變數的記憶體分配,可以直接通過類名訪問。

被類的所有例項共享,可以作為例項之間的共享資料,如果類的所有例項都包含一個相同的常量屬性,可以把這個屬性定義為靜態常量,從而節省記憶體空間

例項變數,每次建立一個例項,就會為例項變數分配一次記憶體,例項變數可以在記憶體中有多個拷貝,互不影響

static方法

靜態方法和靜態變數一樣,直接用類名可以訪問

靜態方法中不能使用this關鍵字,也不能直接訪問所屬類的例項變數和例項方法,只能訪問所屬類的靜態變數和靜態方法

靜態方法中也不能使用super關鍵字

在例項方法中可以直接訪問所屬類的例項變數例項方法,靜態變數靜態方法

靜態方法不能被定義成抽象方法

例項方法和靜態方法,它們的位元組碼都位於方法區。Java編譯器把Java方法的源程式程式碼編譯成二進位制的編碼,成為位元組碼,java虛擬機器的解析器能夠解析這種位元組碼

第八章 介面

介面中的成員變數預設都是public,static,final型別的,必須被顯式初始化

介面中的方法預設都是public,abstract型別的

介面中只能包含public,static,final型別的成員變數和public,abstract型別的成員方法

介面沒有構造方法,不能被例項化

一個介面不能實現另一個介面,但是可以繼承另一個介面

介面必須通過類來實現它的抽象方法,類實現介面的關鍵字為implements

與子類繼承抽象父類相似,當類實現了某個介面時,它必須實現介面中所有的抽象方法,否則這個類必須被定義為抽象類

不允許建立介面的例項,但允許定義介面型別的引用變數,該變數引用實現了這個介面的例項

一個類只能繼承一個直接的父類,但能實現多個介面

比較抽象類和介面

代表系統的抽象層

都不能被例項化

都能包含抽象方法,這些抽象方法用於描述系統能提供哪些服務,但不必提供具體的實現

抽象類與介面主要有兩大區別

1

在抽象類中可以為部分方法提供預設的實現,從而避免在子類中重複實現它們,提高程式碼的可重用性,這是抽象類的優勢所在;

而介面中只能包含抽象方法,如果為介面增加了一個的方法,那麼所有實現它的類都要去實現這個方法,不利於擴充套件。

2

一個類只能繼承一個父類,但是能實現多個介面,這個是介面的優勢

(Tips:為什麼Java語言不允許一個類繼承多個直接的父類呢?     因為當子類覆蓋父類的例項方法,或者隱藏父類的成員變數及靜態方法,或者成員變數和靜態方法時,會使Java虛擬機器繫結規則更加複雜。

而介面中只有抽象方法,沒有例項變數和靜態方法,只有介面的實現類才會實現介面的抽象方法。因此,一個類即使實現了多個介面,也不會增加Java虛擬機器進行動態繫結的複雜度,因為虛擬機器永遠不會把方法與介面繫結,只會把方法與它的實現類繫結

對於已經存在的繼承樹,可以方便地從類中抽象出新的介面,但是從類中抽象出新的抽象類卻不那麼容易,因此介面更有利於軟體系統的維護與重構。)

第十章 類的生命週期

類的載入,連線,初始化

1:載入:查詢並載入類的二進位制資料

2:包括驗證,準備,解析類的二進位制資料

驗證:確保被載入類的正確性   

②準備:為類的靜態變數分配記憶體,並將其初始化為預設值

③解析:把類中的符號引用轉換為直接引用

3:初始化:給類的靜態變數賦予正確的初始值

類的載入

類的載入是指把類的.class檔案中的二進位制資料讀入到記憶體中,把它存放在執行時資料區的方法區內,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構

Java虛擬機器能夠從多種來源載入類的二進位制資料

從本地檔案系統中載入類的.class檔案,這是最常見的載入方法。

通過網路下載類的 .class檔案。

從ZIP,JAR或者其他型別的歸檔檔案中提取.class檔案。

從一個專有資料庫中提取.class檔案

把一個Java原始檔動態編譯為.class檔案

類的載入的最終產品是位於執行時資料區的堆區的Class物件。Class物件封裝了這個類在方法區內的資料結構,並且向java程式提供了訪問類在方法區的資料結構的介面。

如圖:書284頁

類的初始化

在初始化階段,Java虛擬機器執行類的初始化語句,為類的靜態變數賦予初始值。

靜態的宣告語句,以及靜態程式碼塊都被看做類的初始化語句,Java虛擬機器會按照初始化語句在類檔案中的先後順序來依次執行它們。

1:假如這個類還沒有被載入和連線,那就先進行載入和連線。

2:假如類存在直接的父類,並且這個父類還沒有被初始化,那就先初始化直接的父類

3:假如類中存在初始化語句,那就依次執行這些初始化語句。

類的初始化時機

Java虛擬機器只有在程式首次主動使用一個類或介面時才會初始化它

下面6中被視為程式對類或者介面的主動使用

1:建立類的例項。  建立類的例項的途徑包括:用new語句建立例項,或者通過反射,克隆及反序列化等方式來建立例項。

2:呼叫類的靜態方法

3:訪問某個類或者介面的靜態變數,或者對該靜態變數賦值。

4:呼叫Java API中的某些反射方法,比如Class.forName("Person");

5:初始化一個類的子類。    對子類的初始化,可以看做是對它父類的主動使用,因此會先初始化父類。

6:Java虛擬機器啟動時被標明為啟動類的類。

除了上述6種情形,其他使用java類的方式都被看做是被動使用,都不會導致類的初始化。

1對於final型別靜態變數,如果在編譯時就能計算出變數的取值,那麼這種變數被看做編譯時常量。Java程式中對類的編譯時常量的使用,被看做對類的被動使用,不會初始化

2對於final型別的靜態變數,如果在編譯時不能計算出變數的取值,那麼程式對類的這種使用,被看做是對類的主動使用,會導致初始化。

3當java虛擬機器初始化一個類時,要求它的所有父類都已經被初始化,但是這條規則並不適用於介面。

(在初始化一個類時,並不會先初始化它所實現的介面;在初始化一個介面時,並不會先初始化它的父介面)

4:只有當程式訪問的靜態變數或者靜態方法的確在當前類或介面中定義時,才可看做是對類或介面的主動使用。

5:呼叫ClassLoader類的loadClass()方法載入一個類,並不是對類的主動使用,不會導致類的初始化。

第十一章  物件的宣告週期

建立物件的方式

1:用new語句建立物件,這是最常用的建立物件的方式

2:運用反射手段,newInstance()方法

3:呼叫物件的clone()方法

4:運用反序列化方式

建立物件的步驟

1:給物件分配記憶體

2:將物件的例項變數自動初始化為其變數型別的預設值。

3:初始化物件,給例項變數賦予正確的初始值。

構造方法

構造方法負責物件的初始化工作,為例項變數賦予合適的初始值

語法規則:

1:方法名必須與類名相同

2:不要宣告返回型別

3:不能被static,final,synchronized和native修飾。構造方法不能被子類繼承,所以用final和abstract修飾沒有意義。構造方法用於初始化一個新建的物件,所以用static修飾沒有意義,多個執行緒不會同時建立記憶體地址相同的同一個物件,所以用synccheronized修飾沒有必要。

實現單例類的兩種方式

1:

public static final Person o=new Person();

private Person(){

}

2:

private static final Person o=new Person();

private Person(){

}

public static Person getInstance(){

return p;

}

(Tips:約定俗成的規則:獲取物件的方法為getInstance();     獲取變數的方法為getValue(),為了提高可閱讀性)

略過物件宣告週期和類宣告週期,後面再補

內部類

內部類分為成員內部類,區域性內部類,而成員內部類又分為例項內部類和靜態內部類(不管是什麼型別的內部類,都應該保證內部類和外部類不重名)

如果不希望客戶端程式訪問成員內部類,外部類可以把成員內部類定義為private型別

最外層的類成為頂層類,頂層類只能處於public 和預設訪問級別,而成員內部類可以處於public,protected,private和預設這四種訪問級別

例項內部類

例項內部類是成員內部類的一種,沒有static修飾。具有的特點如下

1:在建立例項內部類的例項時,外部類的例項必須已經存在。例如Outer.innerinner=new Outer().new Inner(); 

2:例項內部類的例項自動持有外部類的引用。在內部類中,可以直接訪問外部類的所有成員,包括成員變數和成員方法。

3:外部類例項與內部類例項之間是一對多的關係,一個內部類例項只會引用一個外部類例項,而一個外部類例項對應零個或多個內部類例項。

4:在例項內部類中不能定義靜態成員,而只能定義例項成員。(也就是這個例項內部類的裡面不能有static的內部類和static的變數或方法)

5:如果例項內部類B與外部類A包含同名的成員(變數 i ),那麼在類B中,this.i表示類B的成員,A.this.i表示類A的成員。

靜態內部類

靜態內部類是成員內部類的一種,用static修飾。具有的特點如下

1:靜態內部類的例項不會自動持有外部類的特定例項的引用,在建立內部類的例項時,不必建立外部類的例項。  例如   Person.A  a=new  Person.A();

public class MyInterface {
public static class A{
public   class B{
int i=2;
}
}
}

建立B物件的方法如下

public class Test{
public static void main(String[] args){
MyInterface.A.B  b=new MyInterface.A().new B();
System.out.println(b.i);
}
}

2:區域性內部類和例項內部類一樣,不能包含靜態成員。

3:在區域性內部類中定義的內部類也不能被public,protected,和private這些訪問修飾符修飾。

4:區域性內部類和例項內部類一樣,可以訪問外部類的所有成員,此外,區域性內部類還可以訪問所在方法中的final型別的引數和變數。

內部類的繼承

一個類Person繼承了另一個類Outer的內部類Inner(public class Person extends Outer.Inner{   }),那麼在Person的構造方法裡必須引用這個外部類Outer的例項

public class Personextends Outer.Inner{

person(Outer o){

o.super();

}

}

public class Outer{

class Inner{

}

}

(解釋:因為在直接構造例項內部類的例項的時候,java虛擬機器會自動使內部類例項引用它的外部類例項。在Person類中,繼承了內部類Inner,所以必須要持有外部類的引用,才能讓Java虛擬機器找到內部類,所以Java虛擬機器要求Person類的構造方法必須通過引數傳遞一個Outer例項的引用,然後在構造方法中呼叫super語句來建立Person例項與Outer例項的關聯關係。)

匿名內部類:如下

A  a=new A(){

void run(){

}

};

a.run();

可以看做是

class   Sub   extends  A{

void run(){

}

}

A  a2=new  SubA();

a2.run();

匿名類具有以下特點:

1:匿名類本身沒有構造方法,但是會呼叫父類的構造方法

2:匿名類儘管沒有構造方法,但是可以在匿名類中提供一段例項初始化程式碼,Java虛擬機器會在呼叫了父類的構造方法後,執行這段程式碼。

3:除了可以在外部類的方法內定義匿名類以外,還可以在宣告一個成員變數時定義匿名類。

4:匿名類除了可以繼承類以外,還可以實現介面。

5:匿名類和區域性內部類一樣,可以訪問外部類的所有成員,如果匿名類位於一個方法中,還能訪問所在方法的final型別的變數和引數。

6:區域性內部類的名字在方法外是不可見的,因此與匿名類一樣,能夠起到封裝型別名字的作用。

內部類的回撥詳細在書360頁。

第十三章 多執行緒

在Java虛擬機器程序中,執行程式程式碼的任務是由執行緒來完成的。每個執行緒都有一個獨立的程式計數器和方法呼叫棧。

程式計數器:也稱為PC暫存器,當執行緒執行一個方法時,程式計數器指向方法區中下一條要執行的位元組碼指令。

方法呼叫棧:簡稱方法棧,用來跟蹤執行緒執行中一系列的方法呼叫過程,棧中的元素稱為棧幀。每當執行緒呼叫一個方法的時候,就會向方法棧壓入一個新幀。幀用    來儲存方法的引數,區域性變數和運算過程中的臨時資料。

棧幀由三部分組成:

區域性變數區:存放區域性變數和方法引數。

運算元棧:是執行緒的工作區,用來存放運算過程中生成的臨時資料。

棧資料區:為執行緒執行指令提供相關的資訊,包括如何定位到位於堆區和方法區的特定資料,以及如何正常退出方法或者異常中斷的方法。

方法區存放了執行緒所執行的位元組碼指令,堆區存放了執行緒所操縱的資料(以物件的形式存放),java棧區是執行緒的工作區,儲存執行緒的執行狀態。

另外,計算機中機器指令的真正執行者的CPU,執行緒必須獲得CPU的使用權,才能執行一條指令。

執行緒的建立和啟動

public class MyThread extends Thread{

@override

public void run(){

}

}

MyThread  myThread=new MyThread(); 

my.start();

Thread  thread=Thread.currentThread();     //返回當前正在執行這行程式碼的執行緒的引用

String   name=thread.getName();     //獲取執行緒的名字

主執行緒預設的名字為main,使用者建立第一個執行緒的名字預設為Thread-0,第二個執行緒預設的名字是Thread-1;

不要隨意覆蓋Thread類的start()方法,比如

public class MyThread extendsThread{

public void run(){

}

public void start(){

run(){}

}

}

public  class Test{

public static void main(String args[]){

MyThread  myThread =new MyThread();

myThread.start();

}

}

如上只是普通的方法呼叫,因為已經覆蓋了start()方法,並且方法裡面又呼叫了自己覆蓋的的run()方法,並沒有啟動執行緒,只是方法呼叫。

如果一定要覆蓋start()方法,那麼在方法體裡的第一行呼叫super.start()就可以了。

實現Runnable介面

public class MyRunnable implements Runnable{

int  i=0;

public void run(){

i++;

}

}

public  class Test{

public static void main(String args[]){

MyRunnable myRunnable=new MyRunnable();

Thread thread1=new Thread(myRunnable);

Thread thread2=new Thread(myRunnable);

thread1.start();

thread2.start();

}

}

thread1和thread2操控的是同一個變數i;

修改Test類

public  class Test2{
public static void main(String args[]){
MyRunnable myRunnable=new MyRunnable();
MyRunnable myRunnable2=new MyRunnable();
Thread thread1=new Thread(myRunnable);
Thread thread2=new Thread(myRunnable2);
thread1.start();
thread2.start();

}
} 這樣的情況下thread1和thread2操控的是各自的變數i

執行緒的狀態

1:新建狀態

用new語句建立的執行緒處於新建狀態,此時它和其他java物件一樣,僅僅在堆區被分配了記憶體

2:就緒狀態

當一個執行緒物件建立後,其他執行緒呼叫它的start()方法,該執行緒就進入就緒狀態,Java虛擬機器會為它建立方法呼叫棧和程式計數器。處於這個狀態的執行緒位於可執行池中,等待CPU的使用權。

3:執行狀態

處於這個狀態的執行緒佔用CPU,執行程式程式碼。在併發執行環境中,如果計算機只有一個CPU,那麼任何時刻只有一個執行緒處於這個狀態。如果計算機有多個CPU,那麼同一時刻可以讓幾個執行緒佔用不同的CPU,使它們都處於執行狀態。只有處於就緒狀態的執行緒才有機會轉到執行狀態。

4:阻塞狀態

阻塞狀態是指執行緒因為某些原因放棄CPU,暫時停止執行。當執行緒處於阻塞狀態時,JAVA虛擬機器不會給執行緒分配CPU,直到執行緒重新進入就緒狀態,它才有機會轉到執行狀態

阻塞狀態可分為3種

1:位於物件等待池中的阻塞狀態:當執行緒處於執行狀態時,如果執行了某個物件的wait()方法,Java虛擬機器就會把執行緒放到這個物件的等待池中。

2:位於物件鎖池中的阻塞狀態:當執行緒處於執行狀態,試圖獲得某個物件的同步鎖時,如果該物件的同步鎖已經被其他執行緒佔用,Java虛擬機器就會把這個執行緒放到這個對   象的鎖池中。

3:其他阻塞狀態:當前執行緒執行了sleep()方法,或者呼叫了其他執行緒的join()方法,或者放出了I/O請求時,就會進入這個狀態。

5:死亡狀態

當執行緒退出run()方法時,就進入死亡狀態,該執行緒結束生命週期。執行緒有可能是正常執行完run()方法退出的,也有可能是遇到異常退出的,都不會對其他執行緒造成影響。

Thread類的isAlive()方法判斷一個執行緒是否活著,當執行緒處於死亡狀態或者新建狀態時,該方法返回false,其他情況下返回true。

Java虛擬機器採用搶佔式排程模型,是指優先讓可執行池中優先順序高的執行緒佔用CPU,如果可執行池中的執行緒的優先順序相同,那麼久隨機選擇一個執行緒,使其佔用CPU。處於執行狀態的執行緒會一直執行,直至它不得不放棄CPU。一個執行緒會因為以下原因放棄CPU:

1:JAVA虛擬機器讓當前執行緒暫時放棄CPU,轉到就緒狀態,使其他執行緒獲得執行機會。

2:當前執行緒因為某些原因而進入阻塞狀態。

3:執行緒執行結束。

如果希望明確地讓一個執行緒給另外一個執行緒執行的機會,可以採用以下辦法:

1:調整執行緒的優先順序

2:讓處於執行狀態的執行緒呼叫Thread.sleep()方法

3:讓處於執行狀態的執行緒呼叫Thread.yield()方法

4:讓處於執行狀態的執行緒呼叫另一個執行緒的join()方法

調整各個執行緒的優先順序

Thread類的setPriority(int i);   設定優先順序

Thread類的getPriority()     獲取優先順序

Thread類又三個優先順序的常量

MAX_PRIORITY:取值為10,表示最高優先順序

MIN_PRIORITY:取值為1,表示最低優先順序

NORM_PRIORITY:取值為5,表示預設的優先順序

在不同的作業系統中,選用常量來設定優先順序比較合適

執行緒睡眠

Thread.sleep(int i);使執行緒睡眠,引數為毫秒,當一個執行緒在執行中執行了sleep()方法,它就會放棄CPU,轉到阻塞狀態。

Thread.interrupt();中斷睡眠的方法。

執行緒讓步

Thread.yield();當執行緒在執行中執行了Thread類的yield()靜態方法,如果此時具有相同優先順序的其他執行緒處於就緒狀態,那麼yield()方法將把當前執行的執行緒放到可執行池中並使另一個執行緒執行,如果沒有相同優先順序的可執行執行緒,則yield()方法什麼都不做。

sleep()和yield()的比較:他們都是Thread的靜態方法,都會使當前處於執行狀態的執行緒放棄CPU,把執行機會讓給其他執行緒

    他們的區別在於:sleep()方法會給其他執行緒執行的機會,而不考慮優先順序,因此可能會給較低優先順序的執行緒執行的機會

    yield()方法只會給相同優先順序或者更高優先順序的執行緒執行的機會。

     執行sleep()方法後,將轉到阻塞狀態,執行yield()方法後,將轉到就緒狀態。

     sleep()方法宣告丟擲InterruptException異常,而yield()方法沒有宣告丟擲任何異常。

     sleep()方法比yield()方法具有更好的可移植性。不能依靠yield()方法來提高程式的併發效能。

join()方法:當前執行的執行緒可以呼叫另一個執行緒的join(),當前執行的執行緒將轉到阻塞狀態,直到另一個執行緒執行結束,它才會恢復執行。

join方法有兩種過載形式:join();等待搶佔的那個執行緒執行完畢,才會恢復執行。

       join(long timeout);   比如此執行緒阻塞100毫秒時間後,會恢復執行;或者其他執行緒執行完畢,此執行緒會直接恢復執行。

synchronized

執行緒同步的特徵:

1:如果一個同步程式碼塊和非同步程式碼塊同時操縱共享資源,仍然會造成對共享資源的競爭。因為當一個執行緒執行一個物件的同步程式碼塊時,其他執行緒仍然可以執行物件的非同步程式碼塊。

2:每個物件都有唯一的同步鎖。

3:在靜態方法前面也可以使用synchronized修飾符。那麼鎖的是這個類,而不是一個類的物件。

4:當一個執行緒開始執行同步程式碼塊時,並不意味著必須以不中斷的方式執行。進入同步程式碼塊的執行緒的執行緒池也可以執行Thread.sleep();或者執行Thread.yield();方法,此時它並沒有釋放鎖,只是把執行機會讓給了其他的執行緒。

5:synchronized宣告不會被繼承。如果一個用synchronized修飾的方法被子類覆蓋,那麼子類中這個方法不再保持同步,除非也用synchronized修飾。

釋放物件的鎖

可以釋放鎖的情況

1:執行完同步程式碼塊,就會釋放鎖。

2:在執行同步程式碼快的過程中,遇到異常而導致執行緒終止,鎖也會被釋放。

3:在執行同步程式碼塊的過程中,執行了鎖物件的wait()方法,這個執行緒會釋放鎖,進入物件的等待池。

不會釋放鎖的情況

1:在執行同步程式碼塊的過程中,執行了Thread.sleep()方法,當前執行緒放棄CPU,開始睡眠,在睡眠中不會釋放鎖。

2:在執行同步程式碼塊的過程中,執行了Thread.yield()方法,當前執行緒放棄CPU,但不會釋放鎖。

執行緒執行流程的例子

public class Machine extends Thread {
private int a=1;
public synchronized void print(){
System.out.println(a);
}
public void run(){
synchronized (this) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a=1/0;
a++;
}
}
public static void main(String[] args) {
Machine machine=new Machine();
machine.start();
Thread.yield();
machine.print();
}
}

1:主執行緒執行Thread.yield()方法,把CPU的使用權讓給machine執行緒。

2:machine執行緒獲得machine物件的鎖,開始執行run()方法中的同步程式碼塊。

3:machine執行緒執行Thread.sleep(2000); ,machine執行緒放棄CPU,開始睡眠,但不會釋放machine物件的鎖。

4:主執行緒獲得CPU,試圖執行machine.print()方法,由於主執行緒無法獲得machine物件的鎖,因此進入machine物件的鎖池,等待machine執行緒釋放鎖。

5:machine執行緒睡眠結束,執行 a=1/0  的操作,導致ArithmeticException異常,machine執行緒釋放鎖,並且異常終止。

6:主執行緒獲得鎖及CPU,執行machine.print()方法。

synchronized的死鎖:在多執行緒環境中,死鎖意味著兩個或多個執行緒一直阻塞,等待其他執行緒釋放鎖

如下是一個簡單的死鎖例子

public class DeadlockSample {
private final Object obj1 = new Object();
private final Object obj2 = new Object();


public static void main(String[] args) {
DeadlockSample test = new DeadlockSample();
test.testDeadlock();
}
//這個方法建立了兩個執行緒t1,t2,並分別啟動了執行緒,t1執行了calLock12(),t2執行了calLock21();
private void testDeadlock() {
Thread t1 = new Thread(new Runnable() {
public void run() {
calLock12();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
calLock21();
}
});
t1.start();
t2.start();


}


private void calLock12() {
synchronized (obj1) {
sleep(100);
synchronized (obj2) {
sleep(100);
}
}
}


private void calLock21() {
synchronized (obj2) {
sleep(100);
synchronized (obj1) {
sleep(100);
}
}
}


private void sleep(int i) {
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//如下這個方法建立了兩個執行緒t1,t2,並分別啟動了執行緒,t1執行了calLock12(),它先執行持有obj1物件的同步程式碼塊,休眠後再去執行持有obj2的程式碼塊

//    t2執行了calLock21();  它先執行持有obj2物件的同步程式碼塊,休眠後再去執行持有obj1的程式碼塊。

//因為他們首先持有的是不同的物件,所以可以t1持有的boj1和t2持有的obj2的程式碼塊可以同步執行,但是t1睡眠後去拿obj2物件的同步鎖,此時t2休眠後

//      也在等待obj1的鎖,所以造成了死鎖。

死鎖例子2:

public class Machine extends Thread {
private Counter counter;
public Machine(Counter counter){
this.counter=counter;
start();
}
public void run(){
for(int i=0;i<1000;i++){
counter.add();
}
}
public static void main(String[] args) {
Counter counter1=new Counter();
Counter counter2=new Counter();
counter1.setFriend(counter2);
counter2.setFriend(counter1);

Machine machine1=new Machine(counter1);
Machine machine2=new Machine(counter2);
}
}
class Counter{
private int a;
private Counter friend;
public void setFriend(Counter friend){
this.friend=friend;
}
public synchronized void add(){
a++;
Thread.yield();
friend.delete();System.out.println(Thread.currentThread().getName()+": add");
}
public synchronized void delete(){
a--;
System.out.println(Thread.currentThread().getName()+": delete");
}
}


1:machine1執行緒獲得CPU和counter1物件的鎖,開始執行counter1.add()方法。
2:machine1執行緒執行完   a++ 操作,然後執行Thread.yield()方法,machine1執行緒放棄CPU,但是不會釋放counter1物件的鎖。
3:machine2執行緒獲得CPU和counter2物件的鎖,開始執行counter2.add()方法。
4:machine2執行緒執行完   a++ 操作,然後執行Thread.yield()方法,machine2執行緒放棄CPU,但是不會釋放counter2物件的鎖。
5:machine1執行緒獲得CPU,試圖執行counter2.delete()方法。由於counter2物件的鎖已經被佔用,machine1執行緒放棄CPU,
進入counter2物件的鎖池,等待machine2執行緒釋放counter2物件的鎖。
6:machineer2執行緒獲得CPU,試圖執行counter1.delete()方法。由於counter1物件的鎖已經被佔用,machine2執行緒放棄CPU,
進入counter1物件的鎖池,等待machine1執行緒釋放counter1物件的鎖。

執行緒通訊

java.lang.Object類中提供了兩個用於執行緒通訊的方法。

wait();執行該方法的執行緒釋放物件的鎖,Java虛擬機器把該執行緒放到該物件的等待池中。該執行緒等待其他執行緒將它喚醒。

notify();執行該方法的執行緒喚醒在物件的等待池中等待的一個執行緒。Java虛擬機器從物件的等待池中隨機選擇一個執行緒,把它轉到物件的鎖池中。

等待補充

第十四章   陣列

建立陣列物件

int [ ] sourse=new int [100];

在堆區中為陣列分配記憶體空間,以上的程式碼建立了一個包含100個元素的int陣列。每個元素都是int型別,佔用4個位元組,因此整個陣列物件在記憶體中佔用400位元組。

陣列的每個元素都是int型別,所以預設值是0。陣列的下標是從0開始的,所以對於以上的陣列,是從sourse[0]到sourse[99]的。

用new語句建立陣列物件時,需要指定陣列長度。陣列長度表示陣列中包含的元素數目。

陣列物件建立後,它的長度是固定的。陣列物件的長度是無法改變的,但是陣列變數可以改變所引用的陣列物件。

例如:

int [ ]  x=new int[3];

int [ ]  y=x;

x=new  int[4];

陣列的length屬性表示陣列元素的數目。每個陣列都有一個length屬性,public  final  length,因此在程式中可以讀取陣列的length屬性,但是不能修改這一屬性。 

(Tips:String類的toCharArray( )方法能夠返回包含字串中所有字元的陣列。)

注意如下的程式碼:(說明有待補充)

StringBuffer sb=new StringBuffer("a");
StringBuffer[] sbs=new StringBuffer[]{sb,null};
sb.append("b");
System.out.println(sb+"    "+sbs[0]);//輸出abab
sb=null;
System.out.println(sb+"    "+sbs[0]);//輸出nullab

第十五章 Java集合

set(集):集合中的物件不按特定方式排序,並且沒有重複物件。它的有些實現類能對集合中的物件按特定方式排序。

List(列表):集合中的物件按照索引位置排序,可以有重複物件,允許按照物件在集合中的索引位置檢索物件。List與陣列有些相似。

Map(對映):集合中的每一個元素包含一對鍵物件和值物件,集合中沒有重複的鍵物件,值物件可以重複。它的有些實現類能對集合中的鍵物件進行排序。

Collection和Iterator介面

Collection介面中聲明瞭適用於Java集合(只包括 Set和List)的通用方法。

boolean add(Object o)向集合中假如一個物件的引用

void clear()刪除集合中的所有物件,即不再持有這些物件的引用

boolean contains(Object o)判斷在集合中是否持有特定物件的引用

boolean isEmpty()判斷集合是否為空

Iterator iterator()返回一個Iteritor物件,可用它來遍歷集合中的元素

boolean remove(Object o)從集合中刪除一個物件的引用

int size() 返回集合中元素的數目

Object [ ]  toArray()返回一個數組,該陣列包含集合中的所有元素

Collection介面的iterator()和toArray()方法都用於獲得集合中的所有元素,前者返回一個Iteritor物件,後者返回一個包含集合中所有元素的陣列。

Iterator介面隱藏底層集合的資料結構,向客戶程式提供了遍歷各種型別的集合的統一介面。

Iterator介面中聲明瞭如下方法:

hasNext() 判斷集合中的元素是否遍歷完畢,如果沒有,就返回true。

next() 返回下一個元素。

remove() 從集合中刪除上一個由next()方法返回的元素

(Tips:如果集合中的元素沒有排序,Iterator遍歷集合中的元素的順序是任意的,並不一定與向集合中加入元素的順序一致)

當通過Collection集合的iterator()方法得到一個Iterator物件後,如果當前執行緒或其他執行緒接著又通過Collection集合的一些方法對集合進行了修改操作(呼叫當前Iterator物件的remove()方法來修改集合除外),接下來訪問這個Iterator物件的next()方法會導致java.util.ConcurrentModificationException執行時異常。

Iterator物件運用了快速失敗機制(fail-fast),一但檢測到集合已被修改(有可能是被其他執行緒修改的),就丟擲ConcurrentModificationException執行時異常,而不是顯示修改後的集合的當前內容,這樣可以避免潛在的由於共享資源競爭而導致的併發問題。

Set(集)

Set是最簡單的一種集合,集合中的物件不按特定方式排序,並且沒有重複物件。

Set介面主要有兩個實現類:HashSet和TreeSet

HashSet類按照雜湊演算法來存取集合中的物件,存取速度比較快。HashSet類還有一個子類LinkedHashSet類,它不僅實現了雜湊演算法,而且實現了連結串列資料結構,連結串列資料結構能提高插入和刪除元素的效能。

TreeSet類實現了SortedSet介面,具有排序功能。

Set<String>set=new HashSet<String>();

String s1=new String("hello");

String s2=new String("hello");

set.add(s1);

set.add(s2);

System.out.println(set.size());//列印結果為1.

雖然變數s1和s2實際上引用的是兩個記憶體地址不同的String物件,但是由於s2.equals(s1)的比較結果為true,因此Set認為他們是相等的物件。當第二次呼叫Set的add()方法時,add()方法不會把s2引用的String物件加入到集合中。

HashSet類

HashSet類按照雜湊演算法來存取集合中的物件,具有很好的存取和查詢效能。當向集合中加入一個物件時,HashSet會呼叫物件的hashCode()方法來獲得雜湊碼,然後根據這個雜湊碼進一步計算出物件在集合中的存放位置。

在Object類中定義了hashCode()和equals()方法,Object類的equals()方法按照記憶體地址比較物件是否相等,因此如果object1.equals(object2)為true,則表明object1變數和object2變數實際上引用同一個物件,那麼object1和object2的雜湊碼也肯定相同。

為了保證HashSet正常工作,如果Person類覆蓋了equals()方法,也應該覆蓋hashCode()方法,並且保證兩個相等的Person物件的雜湊碼也一樣。

(Tips:如果兩個物件用equals( )方法比較不相等,HashSet或HashMap並不要求這兩個物件的雜湊碼也必須不相等。但是儘量保證用equals()方法比較不相等的兩個物件有不同的雜湊碼,這樣可以減少雜湊衝突。)

TreeSet類

TreeSet類實現了SortedSet介面,能夠對集合中的物件進行排序。

當TreeSet向集合中加入一個物件時,會把它插入到有序的物件序列中。那麼TreeSet是如何對物件進行排序的呢?TreeSet支援兩種排序方式:自然排序和客戶化排序。

1:自然排序

在JDK類庫中,有一部分類實現了Comparable介面,如Integer,Double和String等。Comparable介面有一個compareTo(Object  o)方法,它返回整數型別。

對於表示式x.compareTo(y);如果返回值為0,則表示x和y相等,如果返回值大於0,則表示x大於y,如果返回值小於0,則表示x小於y。

TreeSet呼叫物件的compareTo()方法比較集合中物件的大小,然後進行升序排列,這種排序方式稱為自然排序。

使用自然排序時,只能向TreeSet集合中加入同類型的物件,並且這些物件的類必須實現Compareble介面,為了保證TreeSet能正確地排序,要求Person類的

compareTo()方法與equals()方法按相同的規則比較兩個Person物件是否相等。也就是說person1.equals(person2)為true,那麼person1.compareTo(person2)為0.

值得注意的是,對於TreeSet中已經存在的Person物件,如果修改了它們的name屬性或者age屬性,則TreeSet不會對集合進行重新排序。

在實際應用中,最適合用TreeSet排序的是不可變類。

Set<Object>  set=new TreeSet<Object>();

set.add(new Integer(8));

set.add(new String("9"));//當第二次呼叫TreeSet的add()方法時會丟擲ClassCastException。

List(列表)

List的主要特徵是其元素以線性方式儲存,集合中允許存放重複物件。

List介面主要的實現類包括

ArrayList:代表長度可變的陣列。允許對元素進行快速的隨機訪問,但是向ArrayList中插入與刪除元素的速度較慢。

LinkedList:在實現中採用連結串列資料結構。對順序訪問進行了優化,向List中插入和刪除元素的速度較快,隨機訪問速度則相對較慢。隨機訪問的是指檢索位於特定索引位置的元素。LinkedLsit單獨具有addFirst(),addLast(),getFirst(),getLast(),removeFirst(),removeLast()方法,這些方法使得LinkedList可以作為堆疊,佇列和雙向佇列來使用。

為列表排序

List只能對集合中的物件按索引位置排序,如果希望對List中的物件按其他特定方式排序,可以藉助Comparator介面和Collections類。Collections類是Java集合類庫中的輔助類,它提供了操縱集合的各種靜態方法,其中sort( )方法用於對List中的物件進行排序。

sort(List   list):對List中的物件進行自然排序。

ListIterator介面

List的listIterator( )方法返回一個ListIterator物件,ListIterator介面繼承了Iterator介面,此外還提供了專門操縱列表的方法如下。

add( ) 向列表中插入一個元素

hasNext( ) 判斷列表中是否還有下一個元素

hasPrevious( )判斷列表中是否還有上一個元素

next( ) 返回列表中的下一個元素

previous( )返回列表中的上一個元素

獲得固定長度的List物件

Java.util.Arrays類的asList( )方法能夠把一個Java陣列包裝為一個List物件,這個List物件代表固定長度的陣列。所有對List物件的操作都會被作用到底層的Java陣列。由於陣列的長度不能改變,因此不能呼叫這種List物件的add( )和remove( )方法,否則會丟擲java.lang.UnsupportedOperationException執行時異常。(不支援操作異常)

String[ ]   s={"Tom","Mike","Jack"};

List<String>list=Arrays.asList(s);//把字串陣列轉化為一個List列表

list.set(0,"Jane");//把列表中下標為0的物件改為"Jane"

System.out.println(Arrays.toString(s));//輸出s陣列

list.remove("Mike");//非法,因為移除後,陣列長度會改變。但是規定陣列長度是不可改變的,所以會丟擲java.lang.UnsupportedOperationException

list.add("Mary");//非法,原因同上,陣列長度不可改變。

Java陣列進行隨機訪問和迭代操作具有最快的速度(陣列再訪問和迭代操作具有最快的速度,但是陣列長度不可改變,不能進行插入操作和刪除操作)

LinkedList進行插入和刪除操作具有最快的速度

ArrayList進行隨機訪問速度最快

Map(對映)

Map(對映)是一種把鍵物件和值物件進行對映的集合,它的每一個元素都包含一對鍵物件和值物件,而值物件仍可以是Map型別,依次類推,這樣就形成了多級對映。

向Map集合中加入元素時,必須提供一對鍵物件和值物件,從Map集合中檢索元素時,只要給出鍵物件,就會返回對應的值物件。

Map集合中的鍵物件不允許重複,也就是說,任意兩個鍵物件通過equals( )方法比較的結果都是false。對於值物件則沒有唯一性要求,可以將任意多個鍵物件對映到同一個值物件上。

Map<String,String>map=new HashMap<String,String>();

map.put("1","Mon");

map.put("1","Monday");//因為已經存在了鍵為 "1"的鍵,所以這行程式碼新增的資料會覆蓋前面的

map.put("one","Monday");//這行程式碼不會覆蓋,因為只有鍵不能重複,重複就會覆蓋

Iterator<Map.Entry<String,String>>it = map.entrySet().iterator();//建立一個Iterator迭代器物件

while(it.hasNext()){//判斷是否有下一個

Map.Entry    entry = it.next();//獲取下一個元素   ,    Map.Entry代表Map中的一對鍵與值。

System.out.println( entry.getKey + “:”+entry.getValue() );//輸出這個元素的鍵和值分別用Map.Entry物件的getKey( )方法和getValue( )方法。

}

Map的entrySet( );方法返回一個Set集合,在這個集合中存放了Map.Entry型別的元素,每個Map.Entry物件代表Map中的一對鍵與值。

Map有兩種常見的實現:HashMap和TreeSet();

HashMap按照雜湊演算法來存取鍵物件,有很好的存取效能,為了保證HashMap能正常工作,和HashSet一樣,要求當兩個鍵物件通過equals( )方法比較為true時,這兩個鍵物件的hashCode( )方法返回的雜湊碼也一樣。

TreeMap實現了SortedMap介面,能對鍵物件進行排序。和TreeSet一樣,TreeMap也支援自然排序和客戶排序兩種方式。

Java遍歷集合的方法

public void print(Set<String>  set){

Iterator<String>it = set.iterator();

while( it.hasNext() ){

String   s=it.next();

System.out.println(s);

}

}

簡便的寫法

public void print(Set<String>  set){

for(String s : set){String代表集合內元素的型別,s引用每次從集合中取出的當前元素,set表示要遍歷的集合

System.out.println(s);

}

}

集合實用類

實用類java.util.Collections
以下是Collections的方法 適用於List型別集合
copy(List dest,List src)把一個List中的元素拷貝到另一個List中。
fill(List list,Object o)向列表中填充元素。
sort(List list)對List中的元素進行自然排序
binarySearch(List list,Object key)查詢List中與給定物件Key相同的元素。在呼叫該方法時,必須保證List中的元素已經自然排序,這樣才能得到正確的結果
binarySearch(List list,Object key,Comparator c)查詢List中與給定物件Key相同的元素,Comparator型別的引數指定比較規則。在呼叫該方法時,必須保證List中的元素已經按照Comparator型別的引數的比較規則排序,這樣才能得到正確的結果
shuffle(); 對List中的元素進行隨機排列。

Collections的以下方法使用於Collection型別或Map型別集合
Object max(Collection coll)返回集合中的最大元素,採用自然排序的比較規則
Object max(Collection coll,Comparatorcomp)返回集合中的最大元素Comparator型別的引數指定比較規則
Object min(Collection coll)返回集合中的最小元素,採用自然排序的比較規則
Object min(Collection coll,Comparatorcomp)返回集合中的最大元素Comparator型別的引數指定比較規則
set singletonList(Object o)返回一個不可改變的Set集合,它只包含一個引數指定的物件。
List singletonList(Object o)返回一個不可改變的List集合,它只包含一個引數指定的物件。
Map singletonMap(Object Key,Object value)返回一個不可改變的Map集合,它只包含引數指定的一對鍵值對。
Collection synchorizedCollection(Collection c)在原來集合的基礎上,返回支援同步的(即執行緒安全的)集合
List synchorizedList(List list)在原來List集合的基礎上,返回支援同步的(即執行緒安全的)List集合
Map synchorizedMap(Map m)在原來Map集合的基礎上,返回支援同步的(即執行緒安全的)Map集合
Set synchorizedMap(Set s)在原來Set集合的基礎上,返回支援同步的(即執行緒安全的)Set集合
Collection unmodifiableCollection(Collection c)在原來集合的基礎上,返回不允許修改的集合檢視
List  unmodifiableList(List list)在原來集合的基礎上,返回不允許修改的集合檢視
Map  unmodifiableMap(Map m)在原來集合的基礎上,返回不允許修改的集合檢視
Set  unmodifiableSet(Set s)在原來集合的基礎上,返回不允許修改的集合檢視

關於集合的多執行緒和單執行緒

Java集合框架中,Set,List和Map的實現類(比如HashSet,ArrayList和HashMap等)都沒有采取同步機制。在單執行緒環境中
這種實現方式會提高操作集合的效率,Java虛擬機器不必因為管理同步鎖而產生額外的開銷。在多執行緒環境中,可能會有多個執行緒同時操作
同一個集合,比如一個執行緒在為集合排序,而另一個執行緒向集合中加入新的元素。為了避免併發問題,可以採取以下幾種解決措施
1:在程式中對可能導致併發問題的程式碼塊進行同步
2:利用Collections的synchorizedXXX()方法獲得原始集合的同步版本。所有執行緒只對這個採取了同步措施的集合物件操作
3:如果集合只包含單個元素並且不允許被修改,可以用Collections的singletonXXX()方法來構造這樣的集合,這可以避免集
合被執行緒錯誤的修改,而且由於不必採取同步措施,可以提高併發效能
4:如果集合的元素不允許被修改,可以用Collections的unmodifiableXXX()方法來生成原始的集合檢視,讓執行緒只訪問這個集合
檢視,這也可以避免集合被執行緒錯誤的修改,而且由於不必採用同步措施,可以提高併發效能。

I/O流

程式的主要任務是操縱資料。在執行時,這些資料都必須位於記憶體中,並且屬於特定的型別,程式才能操縱它們。

Java  I/O系統負責處理程式的輸入和輸出,I/O類庫位於java.io包中,它對各種常見的輸入流和輸出流進行了抽象。

如果資料流中最小的資料單元是位元組,那麼稱這種流為位元組流:

如果資料流中最小的資料單元是字元,那麼稱這種流為字元流。

InputStream和OutputStream分別表示位元組輸入流合位元組輸出流

Reader和Writer分別表示字元輸入流和字元輸出流

InputStream類提供了一些讀取資料有關的方法

int   read( )       :從輸入流讀取資料,有如下三種過載形式

1:int  read( ) 從輸入流讀取一個8位的位元組,把它轉換為0-255之間的整數,並返回這一整數。例如,如果讀取到位元組為9,則返回9,如果讀到的位元組為-9,則返回 247.

 如果遇到輸入流的結尾,則返回-1

2:int  read(byte[ ]  b)     從輸入流讀取若干個位元組,把它們儲存到引數b指定的位元組陣列中。返回的整數表示讀取的位元組數。如果遇到輸入流的結尾,則返回-1

3:int  read(byte[ ]  b,int  off ,int  len)從輸入流讀取若干個位元組,把它們儲存到引數b指定的位元組陣列中。引數off指定在位元組陣列中開始儲存資料的起始下標,引數len指定讀取的位元組數目。返回的整數表示實際讀取的位元組數。如果遇到輸入流的結尾,則返回-1

以上第一個read方法從輸入流讀取一個位元組,而其餘兩個read方法從輸入流批量讀取若干位元組。在從檔案或硬碟度資料時,採用後面兩個read方法可以減少進行物理讀檔案或硬碟的次數,因此能提高I/O操作的效率

void  close( )    :關閉輸入流。當完成所有的讀操作後,應該關閉輸入流。InputStream類本身的close( )方法不執行任何操作,它的一些子類覆蓋了 close( )方法,在close( )    方法中釋放和流有關的系統資源。

int    available( ):返回可以從輸入流中讀取的位元組數目。

skip( long n ) :從輸入流中跳過引數n指定數目的位元組

boolean markSupported( ),void  mark(int  readLimit),void reset():這三個方法用來實現重複讀取流中某一段資料。     

    先用markSupported( )方法來判斷這個流是否支援重複讀入資料,如果返回true,則表明可以在留上    設定標記

     接下來呼叫mark(int  readLimit)方法從流的當前位置開始設定標記,readLimit引數指定在流上做了     標記的位元組數。然後用read( )方法讀取在標記範圍內的位元組。

     最後呼叫reset( )方法,該方法使輸入流重新定位到剛才做了標記的其實位置。這樣可以重複讀取做      過標記的資料了

OutputStream類提供了一些列與資料有關的方法

void  write(int  b):向輸出流寫入資料,有如下三種過載形式

1:void  write(int  b):向輸出流寫入一個位元組

2:void  write(byte [ ] b):把引數b指定的位元組陣列中的所有位元組寫到輸出流

3:void  write(byte [ ] b,int  off,int  len):把引數b指定的位元組陣列中的若干位元組寫到輸出流,引數off指定位元組陣列的起始下標,從這個位置開始輸出由引數len指定數    目的位元組

以上第一個write方法向輸出流寫入一個位元組,而其餘兩個write方法向輸出流批量寫入若干位元組。在向檔案或控制檯寫資料時,採用後面兩個write方法