1.標識符的注意事項以及命名規範
定義
就是給類,接口方法,變量等起名字的字符序列
組成規則
可以有數字,下劃線,字母,$,拼接而成 不能以數字開頭 不能使用java中已有的關鍵字
注意事項
標識符區分大小寫
A:包名全部小寫
單級包:小寫 列舉:com多級包:小寫,並用.隔開 列舉:cn.oracle.myproject
B:類或者接口
一個單詞:首寫大寫(A) 多個單詞:每個單詞首寫大寫 列舉:TestDemo
C:方法或變量
一個單詞:首寫字母小寫 多個單詞 :從第二個單詞開始 ,每個單詞大寫 列舉:textDemo
D:常量
全部大寫 列舉:PI="3.1415926";
Java中break、continue、return語句的使用區別
break:用於完全結束一個循環,跳出循環體。不管哪種循環,一旦在循環體中遇到break,系統將完全結束循環,開始執行循環之後得代碼。break不僅可以結束其所在的循環,還可以結束其外層的循環。此時需要break後緊跟一個標籤,這個標籤用於標識一個外層循環。java中的標籤就是一個緊跟着英文冒號(:)的標識符。且它必須放在循環語句之前纔有用。public class BreakTest2{ public static void main(String[] args){ // 外層循環,outer作爲標識符 outer: for (int i = 0 ; i < 5 ; i++ ){ // 內層循環 for (int j = 0; j < 3 ; j++ ){ System.out.println("i的值爲:" + i + " j的值爲:" + j); if (j == 1){ // 跳出outer標籤所標識的循環。 break outer; } } } } }continue:中止本次循環,接着開始下次循環。而break則完全中止循環。public class ContinueTest{ public static void main(String[] args){// 一個簡單的for循環 for (int i = 0; i < 3 ; i++ ){ System.out.println("i的值是" + i); if (i == 1){ // 忽略本次循環的剩下語句 continue; } System.out.println("continue後的輸出語句"); } } }return:不是用來跳出循環的,而是用來結束一個方法,一旦在循環內執行到了一個return語句,return語句將會結束該方法,循環自然也隨之結束.與continue和break不同的是,return直接結束的是整個方法,不管這個return處於多少層的循環之內。 public class ReturnTest{ public static void main(String[] args){ // 一個簡單的for循環 for (int i = 0; i < 3 ; i++ ){ System.out.println("i的值是" + i); if (i == 1){ return; } System.out.println("return後的輸出語句"); } } }
Java面向對象
什麼是對象
世界萬物皆爲對象,凡是能看得見摸得着的所以東西都叫對象。對象是由屬性和行爲組成,屬性是對象所具有的特徵,而行爲是對象可以做的動作。
例如生活中常見的事物:汽車。汽車的品牌型號、顏色、軸距、車身大小等等都是汽車的屬性;汽車向前行駛、倒車、開門等動作都叫汽車的行爲。
什麼是類
類:具有相同屬性和行爲一堆對象的集合或者叫抽象。
同學是一種類,是所有來學校上課同學的統稱,集合。
程序員是一種類,是所有參與程序編寫的員工的集合。
類與對象的關係
類是對象的抽象,對象的類的實例。對象的每個屬性被表示爲類中的一個成員變量,對象的每個行爲成爲類中的一個方法。
例如同學是一個類,張三就是同學類的一個實例,是一個對象。張三的姓名、年齡、成績等都是類的成員變量,而張三說話、行走、做作業等行爲就是類的方法。
代碼實例:
public class Student { //定義一個名爲Student的類 //定義成員變量name、age、javaScore String name; int age; double javaScore; public void say(){ //定義一個名爲say的方法 int age = 10; //定義局部變量 System.out.println("該學生姓名是:"+name+"\n該學生年齡是:"+age+"\n該學生成績是:"+javaScore); } }
上述代碼中需要注意以下幾點:
類名首字要大寫,不能是關鍵字;
static儘量少用,不必要的麻煩;
成員變量又叫全局變量,可以不用賦初始值,爲默認值;
局部變量必須賦初始值,另外局部變量只在方法域中起作用。
類的實例化
我們在創建了一個類之後,就需要對類進行實例化,使用類,如下面代碼:
public static void main(String[] args) { Student stu = new Student(); //實例化一個Student類 stu.name = "張三"; //給每一個屬性賦值 stu.age = 18; stu.javaScore = 95; stu.say(); //調用類的方法}
上面代碼運行後的結果是:
該學生姓名是:張三 該學生年齡是:18 該學生成績是:95.0
如果是存儲一組學生信息,則需要用數組對Student類進行實例化,如下面代碼:
public static void main(String[] args) { Scanner s = new Scanner(System.in); Student[] stu = new Student[3]; //實例化長度爲3的Student類數組,存儲3名同學信息 Student student = null; for (int i = 0; i < stu.length; i++) { student = new Student(); System.out.println("請輸入學生姓名:"); String name = s.next(); System.out.println("請輸入學生成績:"); double score = s.nextDouble(); student.name = name; student.javaScore = score; stu[i] = student; } for (Student ss : stu){ System.out.println("學生姓名:" + ss.name + " 成績是:" + ss.javaScore); } }
當我們輸入“張三、100、李四、90、王二、80”運行結果如下:
學生姓名:張三 成績是:100.0學生姓名:李四 成績是:90.0學生姓名:王二 成績是:80.0
棧與堆的區別
棧空間小,堆空間大;
所有的數據都是放在棧與堆裏面的;
棧運行快,對運行慢;
所有的基本類型以及引用變量本身都放在棧中;
引用所指向的對象都放在堆中;
運行方法都放在棧中,其中main方法放在最底層;
入棧的方法都要被執行,並遵行先入後出的原則
構造器
構造器是類中的一個特殊的方法,該方法在對象實例化時被調用。構造器的用途是當對象實例化時,初始化對象中的對象。構造器必須滿足下面兩個屬性:
構造器的名稱必須與類名相同;
構造器不能聲明返回值,也不能返回void。
如下面的代碼:
public class Pet { String name; int age; String colore; public Pet(){ System.out.println("我是構造函數"); //構造函數 } public void show(){ System.out.println("你好我是:"+name+",今年"+age+"歲了,"+colore+"色的!"); } }
構造器與方法類似,但它不是方法。每個類都必須有一個構造器,如果我們沒有給類添加構造器,編譯器會自動給我們添加一個構造器,默認構造器的形式參數列表是空的,如果我們給類添加了構造器,編譯器就不會爲類添加默認構造器。
構造器的使用
一個類可以有多個構造器,這種情況下,要調用哪個構造器就取決於new運算符所使用的實際參數。
繼承(Inheritance)
什麼是繼承
在面向對象編程中,可以通過擴展一個已有的類,並繼承該類的屬性和行爲,來創建一個新的類,這種方式成爲繼承。
已有的類稱爲父類,而新類成爲子類,父類也叫基類、超類。
使用繼承不僅可以重用已有的代碼,從而避免代碼重複,還可以創建一個更容易維護和修改代碼的程序。
使用“is a”關係判斷繼承
“is a”關係用於判斷父類和子類的繼承關係是否正確。當使用繼承時,我們必須能夠說子類“is a(是一個)”父類。如果這個語句是真的,那麼繼承關係就是對的。
Java中繼承的實現
在Java中,一個類使用關鍵字extends繼承其他類。關鍵字extends出現在類聲明時的類名後,extends後面跟着的是要繼承的類的名稱。例如,下面的語句用於聲明Dog類是Pet類的子類:
public class Dog extends Pet{ //extends繼承Pet類 String name; public Dog(){ super(); //調用父類的構造器 System.out.println("我是子類構造函數"); } }
單繼承與多繼承
在Java中,只能有一個父類
所有類的根類Object
Java語言API中包含了一個名爲Object的特殊類,他是整個Java類層次中的根類。Object類在java.lang包中,是每個Java類的父類,要麼是直接的父類,要麼就是間接父類。
在Java中,當引用與一個字符串連接時,JVM將隱式地調用toString()方法。
方法重寫
子類可以重寫從父類繼承的方法,從而允許子類添加或者改變父類中方法的行爲,這稱爲方法重寫,是OOP的特徵之一。當子類重寫父類的方法中,必須遵循如下的規則:
子類的方法的返回值的類型、方法名和形式參數列表,必須和父類是相同的;
訪問修飾符必須不小於父類中的訪問修飾符;
子類中重寫的異常不能拋出比父類更多的異常。
如下面的代碼:
public class Dog extends Pet{ String name; public Dog(){ super(); System.out.println("我是子類構造函數"); } @Override // 方法重寫關鍵字 public void show(){ // 方法重寫 System.out.println("你好我是:"+name+",今年"+age+"歲了,"+colore+"色的!"); System.out.println("`````````汪汪汪`````````\n"); } }
方法重載(Overloading)
如何定義重載。
方法的重寫和重載只有一個字不同,很多初學者認爲這兩者十分相似,其實不然。方法重載是讓類以統一的方式處理不同類型數據的一種手段。調用方法時通過傳遞給它們的不同個數和類型的參數來決定具體使用哪個方法,這就是多態性。
所謂方法重載是指在一個類中,多個方法的方法名相同,但是參數列表不同。參數列表不同指的是參數個數、參數類型或者參數的順序不同。方法的重載在實際應用中也會經常用到。不僅是一般的方法,構造方法也可以重載。下面通過一個實例來分析。
重載的定義和使用方法。
Class Person { { String name; int age; void print(){ System.out.println("姓名:" +name+"年齡:" +age); } void print(String a,int b){ System.out.println("姓名:" +a+"年齡:"+b); void print(String a,int b,intC){ System.out.println("姓名:"+a+"年齡:" +b+"ID號:" +c); } void print(String a,int b,doubleC){ System.out.println("姓名:"+a+"年齡:" +b+"ID號:"+c); } }public class OverLoadExampleOL {publicstaticvoidmain(String args[]) { Personpl=newPerson(); p1.nanle="李明"; p1.age=22; p1.print(); p1.print("王小早",19); p1.print("金波",18,100325); p1.print("婉寧",25,110903); } }
在上面的程序中,可以看到Person類中有多個名爲 void print的方法,這就是方法的重載。執行程序,運行結果如下:
姓名:李明年齡:22 姓名:王小早年齡:l9 姓名:金波年齡:18ID號:10 00325 姓名:婉寧年齡:25ID號:110903
重載要注意以下的幾點:
1.在使用重載時只能通過不同的參數列表,必須具有不同的參數列表。例如,不同的參數類型,不同的參數個數,不同的參數順序。當然,同一方法內的幾個參數類型必須不一樣,例如可以是 fun(int,float),但是不能爲 fun(int,int)。2.不能通過訪問權限、返回類型、拋出的異常進行重載。3.方法的異常類型和數目不會對重載造成影響。.. 4.可以有不同的返回類型,只要參數列表不同就可以了。5.可以有不同的訪問修飾符。6.可以拋出不同的異常。
equals()方法
equals()方法比較兩個對象,測試二者是否相等。
比較運算符“==”用於檢測是否兩個引用指向同一對象。
面向對象三大基本特性,五大基本原則
透切理解面向對象三大基本特性是理解面向對象五大基本原則的基礎.
三大特性是:封裝,繼承,多態
所謂封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。封裝是面向對象的特徵之一,是對象和類概念的主要特性。 簡單的說,一個類就是一個封裝了數據以及操作這些數據的代碼的邏輯實體。在一個對象內部,某些代碼或某些數據可以是私有的,不能被外界訪問。通過這種方式,對象對內部數據提供了不同級別的保護,以防止程序中無關的部分意外的改變或錯誤的使用了對象的私有部分。
所謂繼承是指可以讓某個類型的對象獲得另一個類型的對象的屬性的方法。它支持按級分類的概念。繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。 通過繼承創建的新類稱爲“子類”或“派生類”,被繼承的類稱爲“基類”、“父類”或“超類”。繼承的過程,就是從一般到特殊的過程。要實現繼承,可以通過“繼承”(Inheritance)和“組合”(Composition)來實現。繼承概念的實現方式有二類:實現繼承與接口繼承。實現繼承是指直接使用基類的屬性和方法而無需額外編碼的能力;接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實現的能力;
所謂多態就是指一個類實例的相同方法在不同情形有不同表現形式。多態機制使具有不同內部結構的對象可以共享相同的外部接口。這意味着,雖然針對不同對象的具體操作不同,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以調用。
五大基本原則
1.單一職責原則SRP(Single Responsibility Principle)
是指一個類的功能要單一,不能包羅萬象。如同一個人一樣,分配的工作不能太多,否則一天到晚雖然忙忙碌碌的,但效率卻高不起來。
2.開放封閉原則OCP(Open-Close Principle)
一個模塊在擴展性方面應該是開放的而在更改性方面應該是封閉的。比如:一個網絡模塊,原來只服務端功能,而現在要加入客戶端功能,
那麼應當在不用修改服務端功能代碼的前提下,就能夠增加客戶端功能的實現代碼,這要求在設計之初,就應當將服務端和客戶端分開,公共部分抽象出來。
3.替換原則(the Liskov Substitution Principle LSP)
子類應當可以替換父類並出現在父類能夠出現的任何地方。比如:公司搞年度晚會,所有員工可以參加抽獎,那麼不管是老員工還是新員工,
也不管是總部員工還是外派員工,都應當可以參加抽獎,否則這公司就不和諧了。
4.依賴原則(the Dependency Inversion Principle DIP) 具體依賴抽象,上層依賴下層。假設B是較A低的模塊,但B需要使用到A的功能,
這個時候,B不應當直接使用A中的具體類: 而應當由B定義一抽象接口,並由A來實現這個抽象接口,B只使用這個抽象接口:這樣就達到
了依賴倒置的目的,B也解除了對A的依賴,反過來是A依賴於B定義的抽象接口。通過上層模塊難以避免依賴下層模塊,假如B也直接依賴A的實現,那麼就可能造成循環依賴。一個常見的問題就是編譯A模塊時需要直接包含到B模塊的cpp文件,而編譯B時同樣要直接包含到A的cpp文件。
5.接口分離原則(the Interface Segregation Principle ISP)
模塊間要通過抽象接口隔離開,而不是通過具體的類強耦合起來
this和super的區別?
(1)this代表本類對應的引用。
(2)super代表父類存儲空間的標識(可以理解爲父類引用可以操作父類的成員)
調用(訪問)成員變量:
this.成員變量 調用本類的成員變量
super.成員變量 調用父類的成員變量
調用(訪問)構造方法:
this(...) 調用本類的構造方法
super(...) 調用父類的構造方法
調用(訪問)成員方法:
this.成員方法 調用本類的成員方法
super.成員方法 調用父類的成員方法
super()函數在子類構造函數中調用父類的構造函數時使用,而且必須要在構造函數的第一行
this()函數主要應用於同一類中從某個構造函數調用另一個重載版的構造函數。
this()只能用在構造函數中,並且也只能在第一行。所以在同一個構造函數中this()和super()不能同時出現。
接口和抽象類有什麼區別
你選擇使用接口和抽象類的依據是什麼?
接口和抽象類的概念不一樣。接口是對動作的抽象,抽象類是對根源的抽象。
`抽象類表示的是,這個對象是什麼。接口表示的是,這個對象能做什麼。`
比如,男人,女人,這兩個類(如果是類的話……),他們的抽象類是人。說明,他們都是人。
人可以吃東西,狗也可以吃東西,你可以把“吃東西”定義成一個接口,然後讓這些類去實現它.
所以,在高級語言上,一個類只能繼承一個類(抽象類)(正如人不可能同時是生物和非生物),但是可以實現多個接口(吃飯接口、走路接口)。
第一點. 接口是抽象類的變體,接口中所有的方法都是抽象的。而抽象類是聲明方法的存在而不去實現它的類。 第二點. 接口可以多繼承,抽象類不行 第三點. 接口定義方法,不能實現,而抽象類可以實現部分方法。 第四點. 接口中基本數據類型爲static 而抽類象不是的。
當你關注一個事物的本質的時候,用抽象類;當你關注一個操作的時候,用接口。
抽象類的功能要遠超過接口,但是,定義抽象類的代價高。因爲高級語言來說(從實際設計上來說也是)每個類只能繼承一個類。在這個類中,你必須繼承或編寫出其所有子類的
所有共性。雖然接口在功能上會弱化許多,但是它只是針對一個動作的描述。而且你可以在一個類中同時實現多個接口。在設計階段會降低難度的。
有效處理java異常的三個原則
Java中異常提供了一種識別及響應錯誤情況的一致性機制,有效地異常處理能使程序更加健壯、易於調試。異常之所以是一種強大的調試手段,在於其回答了以下三個問題:
什麼出了錯? 在哪出的錯? 爲什麼出錯?
在有效使用異常的情況下,異常類型回答了“什麼”被拋出,異常堆棧跟蹤回答了“在哪“拋出,異常信息回答了“爲什麼“會拋出,如果你的異常沒有回答以上全部問題,那麼可能你沒有很好地使用它們。有三個原則可以幫助你在調試過程中最大限度地使用好異常,這三個原則是:
具體明確 提早拋出 延遲捕獲
01.異常
1.異常
:是對問題的描述,將問題進行對象的封裝。
2.異常體系:
Throwable |---Error |---Exception |---RuntimeException
3.異常體系的特點:
異常體系中的所有類以及建立的對象都具備可拋性。也就是說可以被throw和throws關鍵字所操作,只有異常體系具備這個特點。
4.throw和throws的用法:
throw定義在函數內,用於拋出異常對象。 throws定義在函數上,用於拋出異常類,可以拋出多個用逗號隔開。
5.當函數內有throw拋出異常對象 ,
並未進行try處理,必須要在函數上聲明,否則編譯失敗。 注意:RuntimeException除外,也就是說,函數內如果拋出的RuntimeException異常, 函數上可以不用聲明。如果函數聲明瞭異常,調用者需要進行處理,處理方法可以throws 也可以try.
6.異常有兩種:
(1)編譯時被檢測異常: <1>該異常在編譯時,如果沒有處理,那麼編譯失敗。 <2>該異常被標識,代表可以被處理。 (2)運行時異常(編譯時不檢測) <1>在編譯時,不需要處理,編譯器不檢查。 <2>該異常的發生,建議不處理,讓程序停止,需要對代碼進行修正。
7.異常處理語句
try { 需要被檢測的代碼; } catch() { 處理異常的代碼; } finally { 一定會執行的代碼; }
8.三和結合格式:(try-catch-finally)
注: finally中定義的通常是關閉資源代碼,因爲資源必須釋放。 finally只有一種情況不會被執行,就是當執行到System.exit(0)時。
9.自定義異常:
(1)定義燈繼承Exception或RuntimeException
<1>爲了讓該自定義類具備可拋性。
<2>讓該類具備操作異常的共性方法。
(2)當要定義自定義異常的信息時,可以使用父類已經定義好的功能,將異常信息傳遞給父類的構造函數。
class MyException extends Exception
{
MyException(String message)
{
super(message);
}
}
自定義異常:按照java的面向對象思想,將程序中出現的特有問題進行封裝。
10異常的好處:
(1)將問題進行封裝。 (2)將正常流程代碼和問題處理代碼相分離,方便於閱讀。
11.異常處理原則:
(1)處理方式有兩種:try或throws. (2)調用到拋出異常的功能時,拋出幾個就處理幾個,一個try可對應多個catch. (3)多個catch時,父類的catch放到最下面。 (4)catch內,需要定義針對性的處理方式,不要簡單的定義printStackTrace,輸出語句,也不要不寫。 (5)當捕捉到的異常,本功能處理不了時,可以繼續在catch中拋出。 try { throw new AException(); } catch(AException e) { throw e; } (6)如果該異常處理不了,但並不屬於該功能出現的異常,可以將異常轉換後,再拋出和該功能相關的異常。 try { throw new AException(); } catch(AException e) { //在拋出B之前,對A先進行處理。 throw new BException(); } 注:或者異常可以處理,當需要將異常產生後和本功能相關的問題提供出去,當調用者知道,並處理 也可以將捕獲異常處理後,轉換新的異常。
12.異常的注意事項:
在子父類覆蓋時: (1)子類拋出的異常必須是父類的異常的子類或子集。 (2)如果父類或者接口沒有異常拋出時,子類覆蓋出現異常,只能try不能拋。
02.包(package)
1.包:
包也是一種封裝形式,可對類文件進行分類管理,給類提供多層命名空間。 寫在程序文件的第一行,類名的全稱是:包名.類名
2.包的好處:
包的出現可以讓類文件和java的源文件相分離,可以很方便地只將類文件提供給別人使用。
3.包與包之間的訪問:
(1)一個包中的類要被訪問,必須要有足夠大的權限,所以被訪問的類要被public修飾。 (2)類公有後,被訪問的成員也要公有纔可以被訪問。 (3)包與包之間進行訪問,被訪問的包中的類以及類中的成員,需要public修飾。 (4)不同包中的子類還可直接訪問父類中被protected權限修飾的成員。 (5)包與包之間可以使用的權限只有兩種:public,protected.
4.java中四種權限
public protected default private
image
5.import
(1)爲了簡化類名的書寫,使用一個關鍵字import,import導入的是包中的類。 (2)import packb.*;爲導入包中所有的類,儘量不要寫通配符*,需要用到哪個類就導入哪個類, 都導進來比較佔內存。 (3)建立定義包名不要重複,可以使用url來完成定義,因爲url是唯一的。 如:package cn.itcast.test (4)包和子包中有重名類時,創建對象必須加包名。 如:packb包和子包haha中都有DemoC,創建包中DemoC對象代碼爲: packb.DemoC c = new packb.DemoC();
6.jar包
(1)java中的jar包是通過工具jar.exe完成的。 (2)java的壓縮包方便項目的攜帶,方便於使用,只要在classpath設置jar路徑即可。 (3)數據庫驅動,SSH框架等都是以jar包體現的。
JAVA多線程實現的四種方式
Java多線程實現方式主要有四種:繼承Thread類、實現Runnable接口、實現Callable接口通過FutureTask包裝器來創建Thread線程、使用ExecutorService、Callable、Future實現有返回結果的多線程。
其中前兩種方式線程執行完後都沒有返回值,後兩種是帶返回值的。
1、繼承Thread類創建線程
Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啓動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啓動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啓動新線程並執行自己定義的run()方法。例如:
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); myThread1.start(); myThread2.start();
2、實現Runnable接口創建線程
如果自己的類已經extends另一個類,就無法直接extends Thread,此時,可以實現一個Runnable接口,如下:
public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("MyThread.run()"); } }
爲了啓動MyThread,需要首先實例化一個Thread,並傳入自己的MyThread實例:
MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start();
事實上,當傳入一個Runnable target參數給Thread後,Thread的run()方法就會調用target.run(),參考JDK源代碼:
public void run() { if (target != null) { target.run(); } }
3、實現Callable接口通過FutureTask包裝器來創建Thread線程
Callable接口(也只有一個方法)定義如下:
public interface Callable<V> { V call() throws Exception; } public class SomeCallable<V> extends OtherClass implements Callable<V> {@Overridepublic V call() throws Exception {// TODO Auto-generated method stubreturn null; } } Callable<V> oneCallable = new SomeCallable<V>(); //由Callable<Integer>創建一個FutureTask<Integer>對象: FutureTask<V> oneTask = new FutureTask<V>(oneCallable); //註釋:FutureTask<Integer>是一個包裝器,它通過接受Callable<Integer>來創建,它同時實現了Future和Runnable接口。 //由FutureTask<Integer>創建一個Thread對象: Thread oneThread = new Thread(oneTask); oneThread.start(); //至此,一個線程就創建完成了。
4、使用ExecutorService、Callable、Future實現有返回結果的線程
ExecutorService、Callable、Future三個接口實際上都是屬於Executor框架。返回結果的線程是在JDK1.5中引入的新特徵,有了這種特徵就不需要再爲了得到返回值而大費周折了。而且自己實現了也可能漏洞百出。
可返回值的任務必須實現Callable接口。類似的,無返回值的任務必須實現Runnable接口。
執行Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。
注意:get方法是阻塞的,即:線程無返回結果,get方法會一直等待。
再結合線程池接口ExecutorService就可以實現傳說中有返回結果的多線程了。
下面提供了一個完整的有返回結果的多線程測試例子,在JDK1.5下驗證過沒問題可以直接使用。代碼如下:
import java.util.concurrent.*; import java.util.Date; import java.util.List; import java.util.ArrayList; /** * 有返回值的線程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize = 5; // 創建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 創建多個有返回值的任務 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 執行任務並獲取Future對象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 關閉線程池 pool.shutdown(); // 獲取所有併發任務的運行結果 for (Future f : list) { // 從Future對象上獲取任務的返回值,並輸出到控制檯 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任務啓動"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任務終止"); return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】"; } }
代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用於創建線程池,返回的線程池都實現了ExecutorService接口。public static ExecutorService newFixedThreadPool(int nThreads)
創建固定數目線程的線程池。public static ExecutorService newCachedThreadPool()
創建一個可緩存的線程池,調用execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。
public static ExecutorService newSingleThreadExecutor()
創建一個單線程化的Executor。public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創建一個支持定時及週期性的任務執行的線程池,多數情況下可用來替代Timer類。
ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor後臺線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。
線程的五大狀態
線程從創建、運行到結束總是處於下面五個狀態之一:新建狀態、就緒狀態、運行狀態、阻塞狀態及死亡狀態.
image
1.新建狀態(New): 當用new操作符創建一個線程時, 例如new Thread(r),線程還沒有開始運行,此時線程處在新建狀態。 當一個線程處於新生狀態時,程序還沒有開始運行線程中的代碼 2.就緒狀態(Runnable) 一個新創建的線程並不自動開始運行,要執行線程,必須調用線程的start()方法。當線程對象調用start()方法即啓動了線程,start()方法創建線程運行的系統資源,並調度線程運行run()方法。當start()方法返回後,線程就處於就緒狀態。 處於就緒狀態的線程並不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間纔可以運行線程。因爲在單CPU的計算機系統中,不可能同時運行多個線程,一個時刻僅有一個線程處於運行狀態。因此此時可能有多個線程處於就緒狀態。對多個處於就緒狀態的線程是由Java運行時系統的線程調度程序(thread scheduler)來調度的。 3.運行狀態(Running) 當線程獲得CPU時間後,它才進入運行狀態,真正開始執行run()方法. 4. 阻塞狀態(Blocked) 線程運行過程中,可能由於各種原因進入阻塞狀態: 1>線程通過調用sleep方法進入睡眠狀態; 2>線程調用一個在I/O上被阻塞的操作,即該操作在輸入輸出操作完成之前不會返回到它的調用者; 3>線程試圖得到一個鎖,而該鎖正被其他線程持有; 4>線程在等待某個觸發條件; ...... 所謂阻塞狀態是正在運行的線程沒有運行結束,暫時讓出CPU,這時其他處於就緒狀態的線程就可以獲得CPU時間,進入運行狀態。 5. 死亡狀態(Dead) 有兩個原因會導致線程死亡: 1) run方法正常退出而自然死亡, 2) 一個未捕獲的異常終止了run方法而使線程猝死。 爲了確定線程在當前是否存活着(就是要麼是可運行的,要麼是被阻塞了),需要使用isAlive方法。如果是可運行或被阻塞,這個方法返回true; 如果線程仍舊是new狀態且不是可運行的, 或者線程死亡了,則返回false.
線程的同步與死鎖
1、同步問題
所謂的同步問題指的是多個線程操作同一資源時所帶來的信息的安全性問題,例如,下面模擬一個簡單的賣票程序,要求有5個線程,賣6張票。
class MyThread1 implements Runnable{ //線程的主體類 private int ticket = 6; @Override public void run() { //線程主方法 for(int x =0; x < 10; x++){ if(this.ticket > 0){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"買票,ticket=" + this.ticket--); } } } }public class TestDemo { public static void main(String[] args){ MyThread1 mt = new MyThread1(); new Thread(mt,"票販子A").start(); new Thread(mt,"票販子B").start(); new Thread(mt,"票販子C").start(); new Thread(mt,"票販子D").start(); new Thread(mt,"票販子E").start(); } } 運行結果: 票販子C買票,ticket=6票販子D買票,ticket=2票販子B買票,ticket=3票販子E買票,ticket=4票販子A買票,ticket=5票販子C買票,ticket=1票販子D買票,ticket=0票販子B買票,ticket=-1票販子E買票,ticket=-2票販子A買票,ticket=-3
這個時候發現操作的數據之中出現了負數,這個就可以理解爲不同步問題。
從運行結果發現,程序中加入了延時操作,所以在運行的最後出現了負數的情況,那麼爲什麼現在會產生這樣的情況呢?
從上面的操作代碼可以發現對於票數的操作如下:
1、判斷票數是否大於0,大於0則表示還有票可以賣。
2、如果票數大於0,則賣票出去。
但是,在上面的操作代碼中,在第一步和第二步之間加入了延時操作,那麼一個線程就有可能在還沒有對票數進行減操作之前,其他進程就已經將票數減少了,這樣一來就會出現票數爲負的情況。
如果說現在只剩下最後一張票了,一個線程判斷條件滿足,但是在它還沒修改票數之後,其他線程也同時通過了if判斷,所以最終修改票數的時候就變成了負數。
問題的解決
如果想解決這樣的問題,就必須使用同步,所謂的同步就是指多個操作在同一個時間段內只能有一個線程進行(鎖住),其他線程要等待此線程完成之後纔可以繼續執行。
如果現在要想增加這個鎖,在程序之中就可以通過兩種方式完成:一種是同步代碼塊,另外一種就是同步方法。
實現一:同步代碼塊,使用synchronized關鍵字定義的代碼塊就稱爲同步代碼塊,但是在進行同步的操作之中必須設置一個要同步的對象,而這個對象應該理解爲當前對象:this。
class MyThread1 implements Runnable{ //線程的主體類 private int ticket = 6; @Override public void run() { //線程主方法 for(int x =0; x < 10; x++){ synchronized (this) { if(this.ticket > 0){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"買票,ticket=" + this.ticket--); } } } } }public class TestDemo { public static void main(String[] args){ MyThread1 mt = new MyThread1(); new Thread(mt,"票販子A").start(); new Thread(mt,"票販子B").start(); new Thread(mt,"票販子C").start(); new Thread(mt,"票販子D").start(); new Thread(mt,"票販子E").start(); } } 運行結果: 票販子A買票,ticket=6票販子E買票,ticket=5票販子E買票,ticket=4票販子C買票,ticket=3票販子C買票,ticket=2票販子D買票,ticket=1
方式二,同步方法
class MyThread1 implements Runnable{ //線程的主體類 private int ticket = 6; @Override public void run() { //線程主方法 for(int x =0; x < 10; x++){ this.sale(); } } public synchronized void sale(){ if(this.ticket > 0){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() +"買票,ticket=" + this.ticket--); } } }public class TestDemo { public static void main(String[] args){ MyThread1 mt = new MyThread1(); new Thread(mt,"票販子A").start(); new Thread(mt,"票販子B").start(); new Thread(mt,"票販子C").start(); new Thread(mt,"票販子D").start(); new Thread(mt,"票販子E").start(); } } 運行結果: 票販子A買票,ticket=6票販子E買票,ticket=5票販子E買票,ticket=4票販子E買票,ticket=3票販子D買票,ticket=2票販子C買票,ticket=1
但是在此處需要說明的一個問題:加入同步之後明顯比不加入同步慢許多,所以同步的代碼性能會很低,但是數據的安全性會高。
2、死鎖
同步就是指一個線程要等待另外一個線程執行完畢纔會繼續執行的一種操作形式,但是如果在一個操作之中都是在互相等着的話,那麼就會出現死鎖問題。
示例:
下面模擬一個死鎖程序的樣式。
class YuShi {public synchronized void say(FuXie f) { System.out.println("玉史:給我30億歐圓,放了你兒子。"); f.get() ; }public synchronized void get() { System.out.println("玉史終於得到了贖金,放了兒子,爲了下次繼續綁架。"); } }class FuXie { public synchronized void say(YuShi y) { System.out.println("付謝:放了我兒子,我給你30億歐圓,不見人不給錢。") ; y.get() ; }public synchronized void get() { System.out.println("付謝救回了自己的兒子,於是開始哭那30億。"); } }public class DeadLock implements Runnable { static YuShi ys = new YuShi() ; static FuXie fx = new FuXie() ;public static void main(String[] args) { new DeadLock() ; }public DeadLock() { new Thread(this).start() ; ys.say(fx) ; }@Overridepublic void run() { fx.say(ys) ; } }
死鎖是在日後多線程程序開發之中經常會遇見的問題,而以上的代碼並沒有任何的實際意義,大概可以理解死鎖的操作形式就可以了,不用去研究程序。
面試題:
請問多個線程操作同一資源的時候要考慮到那些,會帶來那些問題?
答:多個線程訪問同一資源的時候一定要考慮到同步的問題,但是過多的同步會帶來死鎖。
Java集合框架篇Collection—List&Set
Collection
集合類特點
一、只用於存儲對象的引用(地址) 二、存儲長度可變 三、存儲不同類型的對象
在生活中我們有着各種各樣的容器,有玻璃的,有陶瓷的,有帶把手的,有帶隔板的,這些容器有着共同的特性就是一個容器,同時也有着不同的功能差異。在Java中也是如此有着各種各樣的集合,我們可以把它這個大家族稱之爲集合框架。每一個容器對數據的存儲方式都有不同,這些存儲方式我們稱之爲:數據結構。常用集合基本圖如下:
基本集合框架圖
image
public interface Collection<E> extends Iterable<E>
Collection 層次結構 中的根接口。
Collection 表示一組對象,這些對象也稱爲 collection 的元素。
一些 collection 允許有重複的元素,而另一些則不允許。
一些 collection 是有序的,而另一些則是無序的。
JDK 不提供此接口的任何直接 實現:它提供更具體的子接口(如 Set 和 List)實現。此接口通常用來傳遞 collection,並在需要最大普遍性的地方操作這些 collection。
—— jdk api
List
List特點:
元素都是有序的 元素可重複 該集合體繫有索引 持有特有的ListIterator迭代器 所以凡是操作角標的集合方法都是List特有的方法。
public interface List<E> extends Collection<E>
有序的 collection(也稱爲序列)。
此接口的用戶可以對列表中每個元素的插入位置進行精確地控制。用戶可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。
與 set 不同,列表通常允許重複的元素。
—— jdk api
ArrayList
該列表特點:
底層數據結構爲數組結構。因此查改快,增刪較慢
非同步操作
ArrayList()初始容量爲10的空列表,每次new +50%增長,數據Copy
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, SerializableList
接口的大小可變數組的實現。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 接口外,此類還提供一些方法來操作內部用來存儲列表的數組的大小。(此類大致上等同於 Vector 類,除了此類是不同步的。)
—— jdk api
ArrayList 基本用法
import java.util.*;class ListDemo{public static void main(String[] args){ ArrayList a1 = new ArrayList(); a1.add("01"); a1.add("02"); a1.add("03"); print(a1); // for (Iterator it = a1.iterator();it.hasNext() ; ) { // Object obj = it.next(); // if (obj.equals("02")) { // // a1.add("04") // //操作元素的方式不能同時用集合對象和迭代器 //會發生併發修改異常 // //ConcurrentModificationException // it.remove(); // } // } //List集合特有的迭代器ListIterator是Iterator的子接口 //該接口只能通過List的集合的ListIterator獲取 ListIterator li = a1.listIterator(); for (;li.hasNext() ; ) { //迭代次數是固定的並不會因爲在迭代過程中添加或刪除元素就改變 // 變化的是集合而不是迭代器 if (li.next().equals("03")) { li.set("003"); li.add("04"); //注意:如果順序顛倒會報異常 } print(1); } print(a1); //[01, 02, 03] //[01, 02, 003, 04] print(li.hasNext()+" "+li.hasPrevious()); //false true}public static void method(){ ArrayList a1 = new ArrayList(); a1.add("01"); a1.add(02); a1.add(03); print(a1+""+a1.size()); //[01, 2, 3]3 a1.add(1,"08"); print(a1); //[01, 08, 2, 3] a1.remove(2); print(a1); //[01, 08, 3] a1.set(2,"set008"); print(a1); //[01, 08, set008] print(a1.get(1)); //08 for (int x=0; x<a1.size(); x++) { print("for-get:"+a1.get(x)); } for (Iterator it = a1.iterator();it.hasNext() ; ) { print("Iterator:"+it.next()); } /* for-get:01 for-get:08 for-get:set008 Iterator:01 Iterator:08 Iterator:set008 */ print("index="+a1.indexOf("08")); //index=1 List sub = a1.subList(0,2); print("sub"+sub); //sub[01, 08]}public static void print(Object obj){ System.out.println(obj); }
}
ArrayList 基本元素存儲去重實現
import java.util.*;//ArrayList 去重//在迭代時循環中 next 調用一次,就要hasNext判斷一次class ArrayListTest{public static void main(String[] args){ ArrayList al = new ArrayList(); al.add("001"); al.add("002"); al.add("002"); al.add("001"); print(al); al = singleElement(al); print(al); }public static void print(Object obj){ System.out.println(obj); }public static ArrayList singleElement(ArrayList al){ ArrayList newAl = new ArrayList(); for (Iterator it = al.iterator();it.hasNext() ; ) { Object obj = it.next(); if (!newAl.contains(obj)) { //contains的底層原理就是用equals比較 newAl.add(obj); } } return newAl; }
}
ArrayList 對象存儲去重實現
import java.util.*;/* 當ArrayList中是對象時,需要對Object的equals進行規則重寫 因爲在List中數據的比較操作都是通過equals來判斷的 */class ArrayListTest2{public static void main(String[] args){ ArrayList al = new ArrayList(); al.add(new Person("wanger1",32)); al.add(new Person("wanger2",52)); al.add(new Person("wanger3",33)); al.add(new Person("wanger4",31)); al.add(new Person("wanger4",31)); al = singleElement(al); for (Iterator it = al.iterator();it.hasNext() ; ) { Person p = (Person)it.next(); print(p.getName()+":"+p.getAge()); } }public static void print(Object obj){ System.out.println(obj); }public static ArrayList singleElement(ArrayList al){ ArrayList newAl = new ArrayList(); for (Iterator it = al.iterator();it.hasNext() ; ) { Object obj = it.next(); if (!newAl.contains(obj)) { newAl.add(obj); } } return newAl; } }class Person{private String name;private int age; Person(String name,int age){ this.name = name; this.age = age; }public String getName(){ return name; }public int getAge(){ return age; }public boolean equals(Object obj){ if(!(obj instanceof Person)) return false; //instanceof進行類型檢查規則是: //你屬於該類嗎?或者你屬於該類的派生類嗎? Person p = (Person)obj; System.out.println(this.name+"..."+p.name); return this.name.equals(p.name) && this.age == p.age; } }
LinkedList
該列表特點: 底層數據結構爲鏈表結構。因此查改慢,增刪快 也是非同步的 LinkedList() 構造的是一個空列表
爲了更好理解鏈表示意圖如下:
LinkedList操作示意圖
image
由此可以看出LinkedList的增刪都對列表的改動不大,而查改則需要誒個數據進行詢問就很麻煩。
LinkedList 基礎用法
import java.util.*;/* addFirst(); addLast(); getFirst(); getLast(); removeFirst(); removeLast(); 有返回值並刪除當前返回值,沒有元素會出現NoSuchElementException JDK1.6替代方法 offerFiest(); offerLast(); peekFirst(); peeklast(); pollFirst(); pollLast();
以上 獲取元素,沒有時會返回null
*/class LinkedListDemo{public static void main(String[] args){ LinkedList link = new LinkedList(); link.addFirst("001"); link.addFirst("002"); link.addFirst("003"); link.addFirst("004"); print(link); //[004, 003, 002, 001] print(link.getLast()); //001 print(link.getFirst()); //004 print(link.removeFirst()); //001}public static void print(Object obj){ System.out.println(obj); } }
用LinkedList 實現隊列或者堆棧
import java.util.*;//堆棧 : 先進後出 First In Last Out FILO//隊列 : 先進先出 First In First Out FIFOclass LinkedListTest{public static void main(String[] args){ Queue q = new Queue(); q.myAdd("001"); q.myAdd("002"); q.myAdd("003"); while(!q.isNull()){ print(q.myGet()); } }public static void print(Object obj){ System.out.println(obj); } }class Queue{private LinkedList link; Queue(){ link = new LinkedList(); }public void myAdd(Object obj){ link.addFirst(obj); }public Object myGet(){ return link.removeLast(); }public boolean isNull(){ return link.isEmpty(); }
}
總結
在List中還有一種容器結構叫做Vector,功能與ArrayList是一樣的,但是它是同步操作,特有取出方式是枚舉(與Iterator功能一致但名字太長被拋棄),每次增長是100%。
當我們判斷不定用ArrayList還是LinkedList的時候,我們可以看看數據是否龐大並且需要大量的增刪,若是就用LinkedList,否就用ArrayList吧,因爲平時一般大量的都是查看。
Set
Set特點:
元素排列是無序的 不可重複 無索引
public interface Set<E>extends Collection<E>
一個不包含重複元素的 collection。
更確切地講,set 不包含滿足 e1.equals(e2) 的元素對 e1 和 e2,並且最多包含一個 null 元素。
正如其名稱所暗示的,此接口模仿了數學上的 set 抽象。
—— jdk api
HashSet
HashSet特點:
底層數據結構是Hash表(存放一堆Hash值的表) 保證元素唯一性原理:判斷元素hashCode值是否相同,若相同再用equals比較是否爲同一個對象 非同步
我們知道每一個存儲在集合中的對象都會有一個來至Object的hashCode()方法,這個方法就爲我們生成Hash值,所以有時當我們每次用 對象 OR對象.toString() 打印時就會出現類似 Person@xxx @前面的是該對象實例化的類名 後面部分是hashCode值,注:hashCode值與內存地址值可能有關係但不是必然的,只是一種約定,並不強制。
hashCode
public int hashCode()
返回該對象的哈希碼值。支持此方法是爲了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常規協定是:
一、在 Java 應用程序執行期間,在對同一對象多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。從某一應用程序的一次執行到同一應用程序的另一次執行,該整數無需保持一致。 二、如果根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每個對象調用 hashCode 方法都必須生成相同的整數結果。 三、如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不 要求一定生成不同的整數結果。但是,程序員應該意識到,爲不相等的對象生成不同整數結果可以提高哈希表的性能。
toString
public String toString()返回該對象的字符串表示。
通常,toString方法會返回一個“以文本方式表示”此對象的字符串。
結果應是一個簡明但易於讀懂的信息表達式。建議所有子類都重寫此方法。 Object 類的 toString 方法返回一個字符串,該字符串由類名(對象是該類的一個實例)、at標記符“@”和此對象哈希碼的無符號十六進制表示組成。
換句話說,該方法返回一個字符串,它的值等於:
getClass().getName() + '@' + Integer.toHexString(hashCode())
—— jdk api Object
HashSet 中實現存儲自定義類去重
import java.util.*;class HashSetTest{public static void main(String[] args){ HashSet hs = new HashSet(); hs.add(new Person("w1",12)); hs.add(new Person("w2",13)); hs.add(new Person("w3",14)); hs.add(new Person("w1",12)); for (Iterator it = hs.iterator();it.hasNext() ; ) { Person p = (Person)it.next(); print(p.getName()+":"+p.getAge()); } }public static void print(Object obj){ System.out.println(obj); } }class Person{private String name;private int age; Person(String name,int age){ this.name = name; this.age = age; }public int hashCode(){ System.out.println(this.name+"...hashCode..."); return this.name.hashCode() + this.age*37; //對象間比較必須重寫hashCode方法。默認每個new對象是必然不同的 //*37是爲了減小偶然性,注意不要越界了。}public String getName(){ return name; }public int getAge(){ return age; }public boolean equals(Object obj){ if(!(obj instanceof Person)) return false; Person p = (Person)obj; System.out.println(this.name+"::"+p.name); return this.name.equals(p.name) && this.age == p.age; } }
TreeSet
TreeSet特點:
可以對Set集合中元素進行排序(默認ASCII碼大小排序又稱自然排序)
底層用了一個平衡二叉樹(紅黑樹)的數據結構
非同步
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
基於 TreeMap 的 NavigableSet 實現。使用元素的自然順序對元素進行排序,或者根據創建 set 時提供的 Comparator 進行排序,具體取決於使用的構造方法。
TreeSet底層圖解
image
TreeSet 在查找元素的時候就會從根節點開始比較,就會減少一半邊的工作量,但是當元素多了的時候,就會在根節點中取折中值再比較。
既然說到了比較那麼就會引入它的比較原理了。
TreeSet
public TreeSet()構造一個新的空 set,該 set 根據其元素的自然順序進行排序。
插入該 set 的所有元素都必須實現 Comparable 接口。
另外,所有這些元素都必須是可互相比較的:對於 set 中的任意兩個元素 e1 和 e2,執行 e1.compareTo(e2) 都不得拋出 ClassCastException。如果用戶試圖將違反此約束的元素添加到 set(例如,用戶試圖將字符串元素添加到其元素爲整數的set中,則add調用將拋出ClassCastException(類型轉換異常)。
從它的構造方法我們可以得知很重要的信息就是添加的數據間必須具備可比較性
public interface Comparable<T>此接口強行對實現它的每個類的對象進行整體排序。這種排序被稱爲類的自然排序,類的 compareTo 方法被稱爲它的自然比較方法。
int compareTo(T o)比較此對象與指定對象的順序。如果該對象小於、等於或大於指定對象,則分別返回負整數、零或正整數。
所以當我們需要用TreeSet中添加自定義對象時,一定要記着讓對象具備比較性。
import java.util.*;class TreeSetTest{public static void main(String[] args){ TreeSet hs = new TreeSet(new myCompare()); // TreeSet hs = new TreeSet(); hs.add(new Student("w1",15)); hs.add(new Student("w2",13)); hs.add(new Student("w4",14)); hs.add(new Student("w3",14)); hs.add(new Student("w3",14)); for (Iterator it = hs.iterator();it.hasNext() ; ) { Student p = (Student)it.next(); print(p.getName()+":"+p.getAge()); } }public static void print(Object obj){ System.out.println(obj); } }class Student implements Comparable{private String name;private int age; Student(String name,int age){ this.name = name; this.age = age; }public String getName(){ return name; }public int getAge(){ return age; }public int compareTo(Object obj){ if(! (obj instanceof Student)) throw new RuntimeException("Not Student Object"); Student s = (Student)obj; if (this.age == s.age) { return this.name.compareTo(s.name); } return this.age - s.age; //返回值爲0,是會認爲相同對象,就不會存進去 //String類本身就實現了Comparable} }class myCompare implements Comparator{public int compare(Object o1,Object o2){ if(!(o1 instanceof Student) || !(o2 instanceof Student)) throw new RuntimeException("Not Student Object"); Student s1 = (Student)o1; Student s2 = (Student)o2; int num = s1.getName().compareTo(s2.getName()); if(num == 0){ return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge())); } return num; } }
在上述代碼示例中,我同時使用了另一種方法讓元素具備了比較性,那就是在實例化TreeSet()類時傳入一個構造器。
public TreeSet(Comparator<? super E> comparator)
構造一個新的,空的樹集,根據指定的比較器進行排序。
插入到集合中的所有元素必須由指定的比較器相互比較 : comparator.compare(e1, e2)不能爲ClassCastException中的任何元素e1和e2 。
如果用戶嘗試向該集合添加一個違反此約束的元素,則add調用將拋出ClassCastException 。
參數 comparator - 將用於對該集合進行排序的比較器。
Comparator讓集合自身具備比較性,在集合初始化時,就有了比較方式。
可能有人會問這個比較器Comparator和另一個實現Comparable接口,應該在哪裏用? 解答:當我們在項目中寫代碼時,若是你不能改其他人的在自定義類中的已經實現了Comparable接口覆蓋compareTo方法,但是你卻需要用其他的比較方式,這個時候就可以寫一個比較器Comparator覆蓋compare方法,當你使用比較器時,它會具有優先性。其次當你使用的元素不具備比較性或者不具備所需要的比較性的時候,這個時候只需使用比較器讓容器自身具備比較性。/* 練習:按照字符串長度排序 字符串本身具備比較性,但是它的比較方式不是所需要的 這時就只能使用比較器 */import java.util.*;class TreeSetTest2{ public static void main(String[] args){ TreeSet hs = new TreeSet(new strLenComparator()); hs.add("w1"); hs.add("w2"); hs.add("w333"); hs.add("w222"); hs.add("w333"); for (Iterator it = hs.iterator();it.hasNext() ; ) { print((String)it.next()); } } public static void print(Object obj){ System.out.println(obj); } }class strLenComparator implements Comparator{ public int compare(Object o1,Object o2){ if(!(o1 instanceof String) || !(o2 instanceof String)) throw new RuntimeException("Not String Object"); String s1 = (String)o1; String s2 = (String)o2; int num = new Integer(s1.length()).compareTo(s2.length()); if(num == 0) return s1.compareTo(s2); return num; } }
總結
HashSet 允許放入一個nullTreeSet 不允許放入nullHashSet 無序 TreeSet 有序 Hash是用空間換時間 Tree是爲了有序 HashSet 底層是 HashMap TreeSet 底層是TreeMap
Map集合的四種遍歷方式
1import java.util.HashMap; 2 import java.util.Iterator; 3 import java.util.Map; 4 5 public class TestMap { 6 public static void main(String[] args) { 7 Map<Integer, String> map = new HashMap<Integer, String>(); 8 map.put(1, "a"); 9 map.put(2, "b");10 map.put(3, "ab");11 map.put(4, "ab");12 map.put(4, "ab");// 和上面相同 , 會自己篩選13 System.out.println(map.size());14 // 第一種:15 /* 16 * Set<Integer> set = map.keySet(); //得到所有key的集合 17 * 18 * for (Integer in : set) { String str = map.get(in); 19 * System.out.println(in + " " + str); } 20 */21 System.out.println("第一種:通過Map.keySet遍歷key和value:");22 for (Integer in : map.keySet()) {23 //map.keySet()返回的是所有key的值24 String str = map.get(in);//得到每個key多對用value的值25 System.out.println(in + " " + str);26 }27 // 第二種:28 System.out.println("第二種:通過Map.entrySet使用iterator遍歷key和value:");29 Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();30 while (it.hasNext()) {31 Map.Entry<Integer, String> entry = it.next();32 System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());33 }34 // 第三種:推薦,尤其是容量大時35 System.out.println("第三種:通過Map.entrySet遍歷key和value");36 for (Map.Entry<Integer, String> entry : map.entrySet()) {37 //Map.entry<Integer,String> 映射項(鍵-值對) 有幾個方法:用上面的名字entry38 //entry.getKey() ;entry.getValue(); entry.setValue();39 //map.entrySet() 返回此映射中包含的映射關係的 Set視圖。40 System.out.println("key= " + entry.getKey() + " and value= "41 + entry.getValue());42 }43 // 第四種:44 System.out.println("第四種:通過Map.values()遍歷所有的value,但不能遍歷key");45 for (String v : map.values()) {46 System.out.println("value= " + v);47 }48 }49 }
IO流上:概述、字符流、緩衝區(java基礎
一、IO流概述
概述:
IO流簡單來說就是Input和Output流,IO流主要是用來處理設備之間的數據傳輸,Java對於數據的操作都是通過流實現,而java用於操作流的對象都在IO包中。
分類:
按操作數據分爲:字節流和字符流。 如:Reader和InpurStream 按流向分:輸入流和輸出流。如:InputStream和OutputStream
IO流常用的基類:
* InputStream , OutputStream
字符流的抽象基類:
* Reader ,Writer
由上面四個類派生的子類名稱都是以其父類名作爲子類的後綴:
如:FileReader和FileInputStream
二、字符流
字符流簡介:
字符流中的對象融合了編碼表,也就是系統默認的編碼表。我們的系統一般都是GBK編碼。
字符流只用來處理文本數據,字節流用來處理媒體數據。
數據最常見的表現方式是文件,字符流用於操作文件的子類一般是FileReader和FileWriter。
2.字符流讀寫:
注意事項:
* 寫入文件後必須要用flush()刷新。 * 用完流後記得要關閉流 * 使用流對象要拋出IO異常
定義文件路徑時,可以用“/”或者“\”。
在創建一個文件時,如果目錄下有同名文件將被覆蓋。
在讀取文件時,必須保證該文件已存在,否則出異常
示例1:在硬盤上創建一個文件,並寫入一些文字數據
[java] view plain copyclass FireWriterDemo { public static void main(String[] args) throws IOException { //需要對IO異常進行處理 //創建一個FileWriter對象,該對象一被初始化就必須要明確被操作的文件。 //而且該文件會被創建到指定目錄下。如果該目錄有同名文件,那麼該文件將被覆蓋。 FileWriter fw = new FileWriter("F:\\1.txt");//目的是明確數據要存放的目的地。 //調用write的方法將字符串寫到流中 fw.write("hello world!"); //刷新流對象緩衝中的數據,將數據刷到目的地中 fw.flush(); //關閉流資源,但是關閉之前會刷新一次內部緩衝中的數據。當我們結束輸入時候,必須close(); fw.write("first_test"); fw.close(); //flush和close的區別:flush刷新後可以繼續輸入,close刷新後不能繼續輸入。 } }
示例2:FileReader的reade()方法.
要求:用單個字符和字符數組進行分別讀取
[java] view plain copyclass FileReaderDemo { public static void main(String[] args) { characters(); } /*****************字符數組進行讀取*********************/ private static void characters() { try { FileReader fr = new FileReader("Demo.txt"); char [] buf = new char[6]; //將Denmo中的文件讀取到buf數組中。 int num = 0; while((num = fr.read(buf))!=-1) { //String(char[] value , int offest,int count) 分配一個新的String,包含從offest開始的count個字符 sop(new String(buf,0,num)); } sop('\n'); fr.close(); } catch (IOException e) { sop(e.toString()); } } /*****************單個字母讀取*************************/ private static void singleReader() { try { //創建一個文件讀取流對象,和指定名稱的文件關聯。 //要保證文件已經存在,否則會發生異常:FileNotFoundException FileReader fr = new FileReader("Demo.txt"); //如何調用讀取流對象的read方法? //read()方法,一次讀取一個字符,並且自動往下讀。如果到達末尾則返回-1 int ch = 0; while ((ch=fr.read())!=-1) { sop((char)ch); } sop('\n'); fr.close(); /*int ch = fr.read(); sop("ch=" + (char)ch); int ch2 = fr.read(); sop("ch2=" + (char)ch2); //使用結束注意關閉流 fr.close(); */ } catch (IOException e) { sop(e.toString()); } } /**********************Println************************/ private static void sop(Object obj) { System.out.print(obj); } }
示例3:對已有文件的數據進行續寫
[java] view plain copyimport java.io.*; class FileWriterDemo3 { public static void main(String[] args) { try { //傳遞一個參數,代表不覆蓋已有的數據。並在已有數據的末尾進行數據續寫 FileWriter fw = new FileWriter("F:\\java_Demo\\day9_24\\demo.txt",true); fw.write(" is charactor table?"); fw.close(); } catch (IOException e) { sop(e.toString()); } } /**********************Println************************/ private static void sop(Object obj) { System.out.println(obj); } }
練習:
將F盤的一個文件複製到E盤。
思考:其實就是將F盤下的文件數據存儲到D盤的一個文件中。
步驟:
1.在D盤創建一個文件,存儲F盤中文件的數據。
2.定義讀取流和F:盤文件關聯。
3.通過不斷讀寫完成數據存儲。
4.關閉資源。
源碼:
[java] view plain copyimport java.io.*; import java.util.Scanner; class CopyText { public static void main(String[] args) throws IOException { sop("請輸入要拷貝的文件的路徑:"); Scanner in = new Scanner(System.in); String source = in.next(); sop("請輸入需要拷貝到那個位置的路徑以及生成的文件名:"); String destination = in.next(); in.close(); CopyTextDemo(source,destination); } /*****************文件Copy*********************/ private static void CopyTextDemo(String source,String destination) { try { FileWriter fw = new FileWriter(destination); FileReader fr = new FileReader(source); char [] buf = new char[1024]; //將Denmo中的文件讀取到buf數組中。 int num = 0; while((num = fr.read(buf))!=-1) { //String(char[] value , int offest,int count) 分配一個新的String,包含從offest開始的count個字符 fw.write(new String(buf,0,num)); } fr.close(); fw.close(); } catch (IOException e) { sop(e.toString()); } } /**********************Println************************/ private static void sop(Object obj) { System.out.println(obj); }
}
三、緩衝區
1. 字符流的緩衝區:BufferedReader和BufferedWreiter * 緩衝區的出現時爲了提高流的操作效率而出現的. * 需要被提高效率的流作爲參數傳遞給緩衝區的構造函數 * 在緩衝區中封裝了一個數組,存入數據後一次取出
BufferedReader示例:
讀取流緩衝區提供了一個一次讀一行的方法readline,方便對文本數據的獲取。
readline()只返回回車符前面的字符,不返回回車符。如果是複製的話,必須加入newLine(),寫入回車符
newLine()是java提供的多平臺換行符寫入方法。
[java] view plain copyimport java.io.*; class BufferedReaderDemo { public static void main(String[] args) throws IOException { //創建一個字符讀取流流對象,和文件關聯 FileReader rw = new FileReader("buf.txt"); //只要將需要被提高效率的流作爲參數傳遞給緩衝區的構造函數即可 BufferedReader brw = new BufferedReader(rw); for(;;) { String s = brw.readLine(); if(s==null) break; System.out.println(s); } brw.close();//關閉輸入流對象 } }
BufferedWriter示例:
[java] view plain copyimport java.io.*; class BufferedWriterDemo { public static void main(String[] args) throws IOException { //創建一個字符寫入流對象 FileWriter fw = new FileWriter("buf.txt"); //爲了提高字符寫入效率,加入了緩衝技術。 //只要將需要被提高效率的流作爲參數傳遞給緩衝區的構造函數即可 BufferedWriter bfw = new BufferedWriter(fw); //bfw.write("abc\r\nde"); //bfw.newLine();這行代碼等價於bfw.write("\r\n"),相當於一個跨平臺的換行符 //用到緩衝區就必須要刷新 for(int x = 1; x < 5; x++) { bfw.write("abc"); bfw.newLine(); //java提供了一個跨平臺的換行符newLine(); bfw.flush(); } bfw.flush(); //刷新緩衝區 bfw.close(); //關閉緩衝區,但是必須要先刷新 //注意,關閉緩衝區就是在關閉緩衝中的流對象 fw.close(); //關閉輸入流對象 } }
2.裝飾設計模式
裝飾設計模式::::
要求:自定義一些Reader類,讀取不同的數據(裝飾和繼承的區別)
MyReader //專門用於讀取數據的類
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
|--MyDataReader
|--MyBufferDataReader
如果將他們抽取出來,設計一個MyBufferReader,可以根據傳入的類型進行增強
class MyBufferReader {
MyBufferReader (MyTextReader text) {} MyBufferReader (MyMediaReader media) {} MyBufferReader (MyDataReader data) {} }
但是上面的類拓展性很差。找到其參數的共同類型,通過多態的形式,可以提高拓展性
class MyBufferReader extends MyReader{ private MyReader r; //從繼承變爲了組成模式 裝飾設計模式 MyBufferReader(MyReader r) {} }
優化後的體系:
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferReader//增強上面三個。裝飾模式比繼承靈活,避免繼承體系的臃腫。降低類與類之間的耦合性
裝飾類只能增強已有的對象,具備的功能是相同的。所以裝飾類和被裝飾類屬於同一個體系
MyBuffereReader類: 自己寫一個MyBuffereReader類,功能與BuffereReader相同
[java] view plain copyclass MyBufferedReader1 extends Reader{ private Reader r; MyBufferedReader1(Reader r){ this.r = r; } //一次讀一行數據的方法 public String myReaderline() throws IOException { //定義一個臨時容器,原BufferReader封裝的是字符數組。 //爲了演示方便。定義一個StringBuilder容器。最終要將數據變成字符串 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch = r.read()) != -1) { if(ch == '\r') continue; if(ch == '\n') //遇到換行符\n,返回字符串 return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0) //當最後一行不是以\n結束時候,這裏需要判斷 return sb.toString(); return null; } /* 需要覆蓋Reader中的抽象方法close(),read(); */ public void close()throws IOException { r.close(); } public int read(char[] cbuf,int off, int len)throws IOException { //覆蓋read方法 return r.read(cbuf,off,len); } public void myClose() throws IOException{ r.close(); } }
一、字節流
1.概述:
1、字節流和字符流的基本操作是相同的,但是要想操作媒體流就需要用到字節流。
2、字節流因爲操作的是字節,所以可以用來操作媒體文件。(媒體文件也是以字節存儲的)
3、讀寫字節流:InputStream 輸入流(讀)和OutputStream 輸出流(寫)
4、字節流操作可以不用刷新流操作。
5、InputStream特有方法:
int available();//返回文件中的字節個數
注:可以利用此方法來指定讀取方式中傳入數組的長度,從而省去循環判斷。但是如果文件較大,而虛擬機啓動分配的默認內存一般爲64M。當文件過大時,此數組長度所佔內存空間就會溢出。所以,此方法慎用,當文件不大時,可以使用。
練習:
需求:複製一張圖片F:\java_Demo\day9_28\1.BMP到F:\java_Demo\day9_28\2.bmp
[java] view plain copyimport java.io.*; class CopyPic { public static void main(String[] args){ copyBmp(); System.out.println("複製完成"); } public static void copyBmp() { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream("F:\\java_Demo\\day9_28\\1.bmp"); //寫入流關聯文件 fos = new FileOutputStream("F:\\java_Demo\\day9_28\\2.bmp"); //讀取流關聯文件 byte[] copy = new byte[1024]; int len = 0; while((len=fis.read(copy))!=-1) { fos.write(copy,0,len); } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("複製文件異常"); } finally { try { if(fis!=null) fis.close(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("讀取流"); } } } }
2. 字節流緩衝區
字節流緩衝區跟字符流緩衝區一樣,也是爲了提高效率。
注意事項:
1. read():會將字節byte()提升爲int型值2. write():會將int類型轉換爲byte()類型,保留最後的8位。
練習:
1.複製MP3文件 1.MP3 --> 2.MP3
2.自己寫一個MyBufferedInputStream緩衝類,提升複製速度
代碼:
[java] view plain copyimport java.io.*; //自己的BufferedInputStream class MyBufferedInputStream { private InputStream in; //定義一個流對象 private byte [] buf = new byte[1024*4]; private int count = 0,pos = 0; public MyBufferedInputStream(InputStream in){ this.in = in; } public int MyRead() throws IOException{ if(count==0) { //當數組裏的數據爲空時候,讀入數據 count = in.read(buf); pos = 0; byte b = buf[pos]; count--; pos++; return b&255; //提升爲int類型,在前面三個字節補充0。避免1111 1111 1111 1111 } else if(count > 0) { byte b = buf[pos]; pos++; count--; return b&0xff; //提升爲int類型,在前面三個字節補充0。避免1111 1111 1111 1111 } return -1; } public void myClose() throws IOException{ in.close(); } } class BufferedCopyDemo { public static void main(String[] args) { long start = System.currentTimeMillis(); copy(); long end = System.currentTimeMillis(); System.out.println("時間:"+(end-start)+"ms"); start = System.currentTimeMillis(); copy1(); end = System.currentTimeMillis(); System.out.println("時間:"+(end-start)+"ms"); } public static void copy1() { //應用自己的緩衝區緩衝數據 MyBufferedInputStream bis = null; BufferedOutputStream bos = null; try { bis = new MyBufferedInputStream(new FileInputStream("馬旭東-入戲太深.mp3"));//匿名類,傳入一個InputStream流對象 bos = new BufferedOutputStream(new FileOutputStream("3.mp3")); int buf = 0; while((buf=bis.MyRead())!=-1) { bos.write(buf); } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("複製失敗"); } finally { try { if(bis!=null) { bis.myClose(); bos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }
二、流操作規律
1. 鍵盤讀取,控制檯打印 ##。
System.out: 對應的標準輸出設備:控制檯 //它是PrintStream對象,(PrintStream:打印流。OutputStream的子類)System.in: 對應的標準輸入設備:鍵盤 //它是InputStream對象
示例:
[java] view plain copy/*================從鍵盤錄入流,打印到控制檯上================*/ public static void InOutDemo(){ //鍵盤的最常見的寫法 BufferedReader bufr = null; BufferedWriter bufw = null; try { /*InputStream ips = System.in;//從鍵盤讀入輸入字節流 InputStreamReader fr = new InputStreamReader(ips);//將字節流轉成字符流 bufr = new BufferedReader(fr); *///將字符流加強,提升效率 bufr = new BufferedReader(new InputStreamReader(System.in)); //匿名類InputSteamReader:讀取字節並將其解碼爲字符 bufw = new BufferedWriter(new OutputStreamWriter(System.out)); //OutputStreamWriter:要寫入流中的字符編碼成字節 String line = null; while((line = bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase()); //打印 bufw.newLine(); //爲了兼容,使用newLine()寫入換行符 bufw.flush(); //必須要刷新。不然不會顯示 } if(bufw!=null) { bufr.close(); bufw.close(); } } catch (IOException e) { e.printStackTrace(); } }
}
2. 整行錄入
1.從鍵盤錄入數據,並存儲到文件中。
我們在鍵盤錄入的是時候,read()方法是一個一個錄入的,能不能整行的錄入呢?這時候我們想到了BufferedReader中ReadLine()方法。
3. 轉換流
爲了讓字節流可以使用字符流中的方法,我們需要轉換流。
InputStreamReader:字節流轉向字符流;
a、獲取鍵盤錄入對象。
InputStream in=System.in;
b、將字節流對象轉成字符流對象,使用轉換流。
InputStreamReaderisr=new InputStreamReader(in);
c、爲了提高效率,將字符串進行緩衝區技術高效操作。使用BufferedReader
BufferedReaderbr=new BufferedReader(isr);
//鍵盤錄入最常見寫法
BufferedReaderin=new BufferedReader(new InputStreamReader(System.in));
2.OutputStreamWriter:字符流通向字節流
示例:
[java] view plain copy/*================把鍵盤錄入的數據存到一個文件中==============*/ public static void inToFile() { //鍵盤的最常見的寫法 BufferedReader bufr = null; BufferedWriter bufw = null; try { /*InputStream ips = System.in; //從鍵盤讀入輸入字節流 InputStreamReader fr = new InputStreamReader(ips); //將字節流轉成字符流 bufr = new BufferedReader(fr); *///將字符流加強,提升效率 bufr = new BufferedReader(new InputStreamReader(System.in)); //匿名類。InputSteamReader:讀取字節並將其解碼爲字符 bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("out.txt"))); //OutputStreamWriter:要寫入流中的字符編碼成字節 String line = null; while((line = bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase()); //打印 bufw.newLine(); //爲了兼容,使用newLine()寫入換行符 bufw.flush(); //必須要刷新。不然不會顯示 } if(bufw!=null) { bufr.close(); bufw.close(); } } catch (IOException e) { e.printStackTrace(); } }
4. 流操作基本規律
爲了控制格式我將其寫入了Java代碼段中,如下:
示例1:文本 ~ 文本
[java] view plain copy /* 流操作的基本規律。 一、兩個明確:(明確體系) #1. 明確源和目的 源:輸入流 InputStream Reader 目的:輸出流 OutputStream Writer 2. 操作的數據是否是純文本 是: 字符流 否: 字節流 二、明確體系後要明確具體使用的對象 # 通過設備區分:內存,硬盤,鍵盤 目的設備:內存,硬盤,控制檯
示例1:將一個文本文件中的數據存儲到另一個文件中: 複製文件
一、明確體系
源:文件-->讀取流-->(InputStream和Reader)
是否是文本:是-->Reader
目的:文件-->寫入流-->(OutputStream Writer) 是否純文本:是-->Writer 二、 明確設備 源:Reader 設備:硬盤上一個文本文件 --> 子類對象爲:FileReader FileReader fr = new FileReader("Goods.txt"); 是否提高效率:是-->加入Reader中的緩衝區:BufferedReader BufferedReader bufr = new BufferedReader(fr); 目的:Writer 設備:鍵盤上一個文本文件 --> 子類對象:FileWriter FileWriter fw = new FileWriter("goods1.txt"); 是否提高效率:是-->加入Writer的緩衝區:BufferedWriter BufferedWriter bufw = new BufferedWriter(fw);
示例2:將一個圖片文件數據複製到另一個文件中:複製文件
一、明確體系
源:文件-->讀取流-->(InputStream和Reader)
是否是文本:否-->InputStream
目的:文件-->寫入流-->(OutputStream Writer) 是否純文本:否-->OutputStream 二、 明確設備 源:InputStream 設備:硬盤上一個媒體文件 --> 子類對象爲:FileInputStream FileInputStream fis = new FileInputStream("Goods.txt"); 是否提高效率:是-->加入InputStream中的緩衝區:BufferedInputStream BufferedInputStream bufi = new BufferedInputStream(fis); 目的:OutputStream 設備:鍵盤上一個媒體文件 --> 子類對象:FileOutputStream FileOutputStream fos = new FileOutputStream("goods1.txt"); 是否提高效率:是-->加入OutputStream的緩衝區:BufferedOutputStream BufferedOutputStream bufo = new BufferedOutputStream(fw);
示例3:將鍵盤錄入的數據保存到一個文本文件中
一、明確體系
源:鍵盤-->讀取流-->(InputStream和Reader)
是否是文本:是-->Reader
目的:文件-->寫入流-->(OutputStream Writer) 是否純文本:是-->Writer 二、 明確設備 源:InputStream 設備:鍵盤 --> 對用對象爲:System.in --> InputStream 爲了操作方便,轉成字符流Reader --> 使用Reader中的轉換流:InputStreamReader InputStreamReader isr = new InputStreamReader(System.in); 是否提高效率:是-->加入Reader中的緩衝區:BufferedReader BufferedReader bufr = new BufferedReader(isr); 目的:Writer 設備:鍵盤上一個文本文件 --> 子類對象:FileWriter FileWriter fw = new FileWriter("goods1.txt"); 是否提高效率:是-->加入Writer的緩衝區:BufferedWriter BufferedWriter bufw = new BufferedWriter(fw);
5.指定編碼表(轉換流可以指定編碼表 ##)
要求:用UTF-8編碼存儲一個文本文件
[java] view plain copyimport java.io.*; public class IOStreamLaw { /** * @param args */ public static void main(String[] args) throws IOException { //鍵盤的最常見寫法 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("goods1.txt"),"UTF-8")); String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); } bufr.close(); } }