Java相關課程系列筆記之一Java學習筆記

qqqqsssss發表於2015-11-27
目 錄
一、 Java技術基礎 1
1.1程式語言 1
1.2 Java的特點 1
1.3 Java開發環境 1
1.4 Java開發環境配置 2
1.5 Linux命令與相關知識 2
1.6 Eclipse/Myeclipse程式結構 3
二、 Java語言基礎 4
2.1基礎語言要素 4
2.2八種基本資料型別 4
2.3常量和變數 5
2.4運算子與表示式 5
2.5程式設計風格 7
2.6流程控制語句 7
2.7陣列 8
2.8字串 9
2.9方法三要素 9
2.10插入排序 9
2.11氣泡排序 10
2.12氣泡排序:輕氣泡上浮的方式 10
2.13二分法查詢 10
2.14 Java系統API方法呼叫 11
2.15二進位制基礎 11
2.16 Java基礎其他注意事項 12
三、 物件導向 13
3.1類 13
3.2物件 13
3.3包 14
3.4方法及其呼叫 14
3.5引用 14
3.6訪問控制(封裝) 14
3.7構造器 14
3.8 super()、super.和 this()、this. 15
3.9過載和重寫 16
3.10繼承 17
3.11 static 20
3.12 final 21
3.13多型 22
3.14抽象類 23
3.15介面 23
3.16內部類 24
3.17匿名類 25
3.18二維陣列和物件陣列 26
3.19其他注意事項 26
四、 Java SE核心I 27
4.1 Object類 27
4.2 String類 28
4.3 StringUtils類 30
4.4 StringBuilder類 30
4.5正規表示式 31
4.6 Date類 32
4.7 Calendar類 32
4.8 SimpleDateFormat類 33
4.9 DateFormat類 34
4.10包裝類 34
4.11 BigDecimal類 35
4.12 BigInteger類 35
4.13 Collection集合框架 35
4.14 List集合的實現類ArrayList和LinkedList 36
4.15 Iterator迭代器 39
4.16泛型 40
4.17增強型for迴圈 40
4.18 List高階-資料結構:Queue佇列 41
4.19 List高階-資料結構:Deque棧 41
4.20 Set集合的實現類HashSet 42
4.21 Map集合的實現類HashMap 43
4.22單例模式和模版方法模式 45
五、 Java SE核心II 47
5.1 Java異常處理機制 47
5.2 File檔案類 49
5.3 RandomAccessFile類 51
5.4基本流:FIS和FOS 53
5.5緩衝位元組高階流:BIS和BOS 54
5.6基本資料型別高階流:DIS和DOS 54
5.7字元高階流:ISR和OSW 55
5.8緩衝字元高階流:BR和BW 56
5.9檔案字元高階流:FR和FW 57
5.10 PrintWriter 58
5.11物件序列化 58
5.12 Thread執行緒類及多執行緒 59
5.13 Socket網路程式設計 63
5.14執行緒池 65
5.15雙緩衝佇列 66


一、Java技術基礎
1.1程式語言
機器語言:0 1 在硬體直接執行
組合語言:助記符
高階語言: (Java執行比C/C++慢)
1)程式導向的高階語言:程式設計的基本單位為函式,如:C/C++語言。
2)物件導向的高階語言:程式設計的基本單位為類,如:Java、C#。

1.2 Java的特點
平臺無關性、簡單性、物件導向、健壯性、多執行緒、自動記憶體管理。
平臺無關性:指Java語言平臺無關,而Java的虛擬機器卻不是,需要下載對應平臺JVM虛擬機器的。
自動記憶體管理:對臨時儲存的資料自動進行回收,釋放記憶體。如:引用型別的變數沒有指向時,被回收;程式執行完後,區域性變數被回收。
1.3 Java開發環境
Java Developement Kit——Java開發工具包,簡稱JDK,是由Sun公司提供的一個免費的Java開發工具,程式設計人員和終端使用者可以利用這個工具來編譯、執行Java程式。目前版本有JDK1.0、JDK1.1、JDK1.2、JDK1.3、JDK1.4、JDK1.5(J2SE5.0)、JDK1.6(J2SE6.0)、JDK1.7(J2SE7.0)。
JDK結構:JDK
|--開發工具(Tools)命令:java、javac、jar、rmic ...
|-- JRE(Java基本執行環境)
|--系統API庫,系統類庫
| 系統帶來的標準程式庫,標準API
|-- J VM java虛擬機器
java 語言的執行環境
1.4 Java開發環境配置
安裝完JDK之後,不能立刻使用,需要設定環境變數:
1)設定PATH:D:\Java\jdk1.6.0\bin(指向JDK中bin資料夾,有各種編譯命令)。
2)CLASSPATH:告訴Java程式去哪裡查詢第三方和自定義類,如果 .class檔案和類原始檔在同一資料夾內,則不需要配置classpath,後續有包,則需要。
A.Windows:在命令列執行
set CLASSPATH=E:\workspace\1304\bin (臨時環境配置)
java day02.Demo1
 注意事項:
 E:\ set classpath = c:\ (不加分號就不找當前路徑)
= . ; c:\ ; d:\ ;(先找classpath,若無,再找當前路徑)
 C、D兩盤有同名 . class 檔案,classpath設定為D盤,而命令列視窗當前碟符為C盤,則JVM現找classpath路徑,後找當前路徑。
B.Linux:在控制檯執行
①設定CLASSPATH環境變數,指向package所在的目錄,一般是專案資料夾中的bin目錄。
②執行java package.ClassName (包名必須寫)。
export CLASSPATH=/home/soft01/workspace/1304/bin (臨時環境配置)
java day01.HelloWorld
java -cp /home/soft01/workspace/1304/bin day01.HelloWorld(二合一)
 注意事項:
 Windows根目錄是反斜線:\
 Linux根目錄是斜線:/
1.5 Linux命令與相關知識
1)Linux無碟符,只有一個根目錄(root)
2)終端 == 控制檯 == 命令列視窗
3)pwd:列印當前工作目錄,顯示當前工作目錄的位置
4)ls:列表顯示目錄內容,預設顯示當前目錄內容
5)cd:改變當前工作目錄;cd後不加引數=返回home資料夾;cd ~:返回home;
cd /:切換到根目錄;cd .. :返回上一層目錄(相對的);
6)mkdir:建立資料夾(目錄) 注意:目錄 == 資料夾
7)rm:刪除檔案;rm xx xx:可刪多個檔案;
rm –rf xx:-為減號,r表遞迴,f表強制
8)cat xx:顯示文字檔案內容
9)啟動Java開發工具:cd/opt/eclipse à ./eclipse . 表當前目錄下
10)絕對路徑: /home (以 / 開始為絕對路徑,相對於根目錄)
相對路徑:home (相對於當前工作目錄)
11)home(使用者主目錄,使用者的家):/home/username 如:/home/soft01
12)主目錄(home):有最大訪問許可權:什麼都能幹,增刪改查、建目錄等
其他地方:一般只能檢視,不能增刪改查、建立目錄等
1.6 Eclipse/Myeclipse程式結構
Project專案檔案
|-- src原始檔
| |-- Package包
| |-- .java原始檔
|-- bin
|-- Package包
|-- .class位元組碼程式
 注意事項:
 Myeclipse5.5消耗少,Myeclipse6.5最穩定

二、Java語言基礎
2.1基礎語言要素
1)識別符號:給類、方法、變數起的名字
A.必須以字母或下劃線或 $ 符號開始,其餘字元可以是字母、數字、$ 符號和下劃線。
B.只能包含兩個特殊字元,即下劃線 _ 和美元符號 $ 。不允許有任何其他特殊字元。
C.識別符號不能包含空格。
D.區分大小寫。
2)關鍵字:只有系統才能用的識別符號
 注意事項:
 true、false、null不是關鍵字!是字面量。
 main不是關鍵字!但是是一個特殊單詞,可以被JVM識別,主函式是固定格式,作為程式的入口。
3)註釋:單行註釋:// 多行註釋:/* ……*/ 文件註釋:/**……*/
 注意事項:開發中類前、屬性前、方法前,必須有文件注視。
2.2八種基本資料型別
1)四種整數型別(byte、short、int、long):
byte:8位,用於表示最小資料單位,如檔案中資料,-128~127
short:16位,很少用,-32768 ~ 32767
int:32位、最常用,-2^31-1~2^31 (21億)
long:64位、次常用
 注意事項:
 int i=5; // 5叫直接量(或字面量),即直接寫出的常數。
 整數字面量預設都為int型別,所以在定義的long型資料後面加L或l。
 小於32位數的變數,都按int結果計算。
 強轉符比數學運算子優先順序高。見常量與變數中的例子。
2)兩種浮點數型別(float、double):
float:32位,字尾F或f,1位符號位,8位指數,23位有效尾數。
double:64位,最常用,字尾D或d,1位符號位,11位指數,52位有效尾數。
 注意事項:
 二進位制浮點數:1010100010=101010001.0*2=10101000.10*2^10(2次方)=1010100.010*2^11(3次方)= . 1010100010*2^1010(10次方)
 尾數: . 1010100010 指數:1010 基數:2
 浮點數字面量預設都為double型別,所以在定義的float型資料後面加F或f;double型別可不寫字尾,但在小數計算中一定要寫D或X.X。
 float 的精度沒有long高,有效位數(尾數)短。
 float 的範圍大於long 指數可以很大。
 浮點數是不精確的,不能對浮點數進行精確比較。
3)一種字元型別(char):
char:16位,是整數型別,用單引號括起來的1個字元(可以是一箇中文字元),使用Unicode碼代表字元,0~2^16-1(65535)。
 注意事項:
 不能為0個字元。
 轉義字元:\n 換行 \r 回車 \t Tab字元 \" 雙引號 \\ 表示一個\
 兩字元char中間用“+”連線,內部先把字元轉成int型別,再進行加法運算,char本質就是個數!二進位制的,顯示的時候,經過“處理”顯示為字元。
4)一種布林型別(boolean):true真 和false假。
5)型別轉換: char-->
自動轉換:byte-->short-->int-->long-->float-->double
強制轉換:①會損失精度,產生誤差,小數點以後的數字全部捨棄。
②容易超過取值範圍。
2.3常量和變數
變數:記憶體中一塊儲存空間,可儲存當前資料。在程式執行過程中,其值是可以改變的量。
1)必須宣告並且初始化以後使用(在同一個作用域中不能重複宣告變數)!
2)變數必須有明確型別(Java是強型別語言)。
3)變數有作用域(變數在宣告的地方開始,到塊{}結束)。變數作用域越小越好。
4)區域性變數在使用前一定要初始化!成員變數在物件被建立後有預設值,可直接用。
5)在方法中定義的區域性變數在該方法被載入時建立。
常量:在程式執行過程中,其值不可以改變的量。
 注意事項:
 字面量、常量和變數的運算機制不同,字面量、常量由編譯器計算,變數由運算器處理,目的是為了提高效率。
eg:小於32位數的字面量處理
byte b1 = 1; byte b2 = 3;
//byte b3 = b1+b2;//編譯錯誤,按照int結果,需要強制轉換
byte b3 = (byte)(b1+b2);
//byte b3 = (byte)b1+(byte)b2;//編譯錯誤!兩個byte、short、char相加還是按int算
System.out.println(b3); //選擇結果:A編譯錯誤B執行異常 C 4 D b3
byte b4 = 1+3;//字面量運算,編譯期間替換為4,字面量4
//byte b4 = 4; 不超過byte就可以賦值
 不管是常量還是變數,必須先定義,才能夠使用。即先在記憶體中開闢儲存空間,才能夠往裡面放入資料。
 不管是常量還是變數,其儲存空間是有資料型別的差別的,即有些變數的儲存空間用於儲存整數,有些變數的儲存空間用於儲存小數。
2.4運算子與表示式
1)數學運算:+ - * / % ++ --
 注意事項:
 + - * / 兩端的變數必須是同種型別,並返回同種型別。
 % 取餘運算,負數的餘數符號與被模數符號相同, - 1 % 5 = - 1,1 % - 5 = 1;Num % n,n>0,結果範圍[0,n),是周期函式。
 注意整除問題:1 / 2 = 0(整數的除法是整除)1.0 / 2 = 0.5 1D / 2 = 0.5
 單獨的前、後自增或自減是沒區別的,有了賦值語句或返回值,則值不同!
eg1:自增自減
int a = 1; a = a++; System.out.println("a的值:"+a);
第1步:後++,先確定表示式a++的值(當前a的值) a++ ---->1
第2步:++,給a加1 a ---->2
第3步:最後賦值運算,把a++整個表示式的值賦值給a a ---->1
a被賦值兩次,第1次a = 2,第2次把1賦值給1
eg2:自增自減
x,y,z分別為5,6,7 計算z + = -- y * z++ ;// x = 5,y = 5,z = 42
z = z + -- y * z++ à 42 = 7 + 5 * 7 從左到右入棧,入的是值
eg3:取出數字的每一位
d = num%10;//獲取num的最後一位數 num/=10; //消除num的最後一位
2)位運算:& | ~(取反) ^(異或)>> << >>>
 注意事項:
 一個數異或同一個數兩次,結果還是那個數。
 |:上下對齊,有1個1則為1;&:上下對齊,有1個0則為0;(都為二進位制)
 &相當於乘法,| 相當於加法;&:有0則為0,| :有1則為1,^:兩數相同為0,不同為1。
3)關係運算子:> < >= <= == !=
4)邏輯運算子:&& ||(短路) ! & |
eg:短路運算:&&:前為flase,則後面不計算;|| :前為true,則後面不計算
int x=1,y=1,z=1;
if(x--==1 && y++==1 || z++==1) // || 短路運算後面的不執行了!
System.out.println(“x=”+x+”,y=”+y+”,z=”+z);// 0 , 2, 1
5)賦值運算子:= += -= *= /= %=
eg:正負1交替
int flag= -1; System.out.println(flag *= -1); ……
6)條件(三目)運算子:表示式1 ? 表示式2 :表示式3
 注意事項:
 右結合性:a > b ? a : i > j ? i : j 相當於 a > b ? a : ( i > j ? i : j )
 三目運算子中:第二個表示式和第三個表示式中如果都為基本資料型別,整個表示式的運算結果由容量高的決定。如:int x = 4; x > 4 ? 99.9 : 9;
99.9是double型別,而9是int型別,double容量高,所以最後結果為9.9。
7) 運算子優先順序:括號 > 自增自減 > ~ ! > 算數運算子 > 位移運算 > 關係運算 > 邏輯運算 > 條件運算 > 賦值運算
2.5程式設計風格
MyEclipse/Eclipse中出現的紅色叉叉:編譯錯誤
編譯錯誤:java編譯器在將Java原始碼編譯為class檔案的過程出現的錯誤,一般是語法使用錯誤!當有編譯錯誤時候,是沒有class檔案產生,也就不能執行程式。
Java 程式結構:

2.6流程控制語句
1)選擇控制語句
if語句:if 、if-else、if-else-if:可以處理一切分支判斷。
格式:if(判斷){…}、if(判斷){…}else{…}、if(判斷){…}else if(判斷){…}
switch語句:switch(必須為int型別){case 常量1:…; case 常量2:… ; ….}
 注意事項:
 int型別指:byte、short、int,不能寫long型別,要寫也必須強轉成int型別;而byte、short為自動轉換成int。
 swtich-case:若case中無符合的數,並且default寫在最前(無break時), 則為順序執行,有break或 } 則退出。
 swtich-case:若case中無符合的數,並且default寫在最後,則執行default。
 swtich-case:若case中有符合的數,並且default寫在最後,並且default前面的case沒有break時,default也會執行。
2)迴圈控制語句
①for:最常用,用在與次數有關的迴圈處理,甚至只用for可以解決任何迴圈問題。
 注意事項:for中定義的用於控制次數的迴圈變數,只在for中有效,for結束則迴圈變數被釋放(回收)。
②while:很常用,用在迴圈時候要先檢查迴圈條件再處理迴圈體,用在與次數無關的情況。如果不能明確結束條件的時候,先使用while(true),在適當條件使用if語句加break結束迴圈。
③do-while:在迴圈最後判斷是否結束的迴圈。如:使用while(true) 實現迴圈的時候,結束條件break在while迴圈體的最後,就可以使用 do-while 。do-while 的結束條件經常是“否定邏輯條件”,不便於思考業務邏輯,使用的時候需要注意。可以利用while(true)+ break 替換。
④迴圈三要素:A.迴圈變數初值 B.迴圈條件 C.迴圈變數增量(是迴圈趨於結束的表示式)
⑤for和while迴圈體中僅一條語句,也要補全{ },當有多條語句,且不寫{ }時,它們只執行緊跟著的第一條語句。
⑥迴圈的替換:
while(布林表示式){} 等價 for(;布林表示式;){}
while(true){} 等價 for(;;)
while(true){} + break 替換 do{}while(布林表示式);
for(;;) + break 替換 do{}while(布林表示式);
3)跳轉控制語句
continue:退出本次迴圈,直接執行下一次迴圈
break:退出所有迴圈
2.7陣列
型別一致的一組資料,相當於集合概念,在軟體中解決一組,一堆XX資料時候使用陣列。
1)陣列變數:是引用型別變數(不是基本變數)引用變數通過陣列的記憶體地址位置引用了一個陣列(陣列物件),即栓到陣列物件的繩子。
eg:陣列變數的賦值
int[] ary = new int[3];// ary----->{0,0,0}<----ary1
int[] ary1 = ary;// ary 的地址賦值給ary1,ary 與 ary1 繫結了同一個陣列
//ary[1] 與 ary1[1] 是同一個元素,陣列變數不是陣列(陣列物件)
2)陣列(陣列物件)有3種建立(初始化)方式:①new int[10000] 給元素數量,適合不知道具體元素,或元素數量較多時 ②new int[]{3,4,5} 不需要給出數量,直接初始化具體元素適合知道陣列的元素。③ {2,3,4} 靜態初始化,是②簡化版,只能用在宣告陣列變數的時候直接初始化,不能用於賦值等情況。
eg:陣列初始化
int[] ary1 = new int[]{2,3,4};//建立陣列時候直接初始化元素
int[] ary2 = {2,3,4};//陣列靜態初始化,只能在宣告變數的同時直接賦值
//ary2 = {4,5,6};//編譯錯誤,不能用於賦值等情況
ary2 = new int[]{4,5,6};
3)陣列元素的訪問:①陣列長度:長度使用屬性訪問,ary.length 獲取陣列下標。②陣列下標:範圍:0 ~ length-1就是[0,length),超範圍訪問會出現下標越界異常。③使用[index] 訪問陣列元素:ary[2]。④迭代(遍歷):就是將陣列元素逐一處理一遍的方法。
4)陣列預設初始化值:根據陣列型別的不同,預設初始化值為:0(整數)、0.0(浮點數)、false(布林型別)、\u0000(char字元型別,顯示無效果,相當於空格,編碼為0的字元,是控制字元,強轉為int時顯示0)、null(string型別,什麼都沒有,空值的意思)。
5)陣列的複製:陣列變數的賦值,是並不會複製陣列物件,是兩個變數引用了同一個陣列物件。陣列複製的本質是建立了新陣列,將原陣列的內容複製過來。
6)陣列的擴容:建立新陣列,新陣列容量大於原陣列,將原陣列內容複製到新陣列,並且丟棄原陣列,簡單說:就是更換更大的陣列物件。System.arraycopy() 用於複製陣列內容,簡化版的陣列複製方法:Arrays.copyOf()方法,但需JKD1.5+。
2.8字串
字串(string):永遠用“”雙引號(英文狀態下),用字串連線任何資料(整數),都會預設的轉化為字串型別。
字串與基本資料型別連結的問題:如果第一個是字串那麼後續就都按字串處理,如System.out.println("(Result)"+6 + 6 );那麼結果就是(Result)66,如果第一個和第二個…第n個都是基本資料,第n+1是字串型別,那麼前n個都按加法計算出結果在與字串連線。如下例中的System.out.println(1+2+”java”+3+4);結果為3java34。
eg:字串前後的“+”都是連線符!不是加法運算子!
System.out.println("A"+'B');//AB
System.out.println('A'+'B');//131
System.out.println(1+2+”java”+3+4);//3java34
 注意事項:比較字串是否相等必須使用equals方法!不能使用==。"1".equals(cmd) 比cmd.equals("1") 要好。
2.9方法三要素
方法:method(函式function = 功能) y=f(x)
1)方法的主要三要素:方法名、引數列表、返回值。
2)什麼是方法:一個演算法邏輯功能的封裝,是一般完成一個業務功能,如:登入系統,建立聯絡人,簡單說:方法是動作,是動詞。
3)方法名:一般按照方法實現的功能定名,一般使用動詞定義,一般使用小寫字母開頭,第二個單詞開始,單詞首字母大寫。如:createContact() 。
4)引數列表:是方法的前提條件,是方法執行依據,是資料。如:
login(String id, String pwd) ,引數的傳遞看定義的型別及順序,不看引數名。
5)方法返回值:功能執行的結果,方法必須定義返回值,並且方法中必須使用return語句返回資料;如果無返回值則定義為void,此時return語句可寫可不寫;返回結果只能有一個,若返回多個結果,要用陣列返回(返回多個值)。
 注意事項:遞迴呼叫:方法中呼叫了方法本身,用遞迴解決問題比較簡練,只需考慮一層邏輯即可!但是需要有經驗。一定要有結束條件!如:f(1)=1; 遞迴層次不能太深。總之:慎用遞迴!
2.10插入排序
將陣列中每個元素與第一個元素比較,如果這個元素小於第一個元素,則交換這兩個元素迴圈第1條規則,找出最小元素,放於第1個位置經過n-1輪比較完成排序。
for(int i = 1; i < arr.length; i++) {
int k = arr[i];// 取出待插入元素
int j;// 找到插入位置
for (j = i - 1; j >= 0 && k < arr[j]; j--) {
arr[j + 1] = arr[j];// 移動元素
}
arr[j + 1] = k;// 插入元素
System.out.println(Arrays.toString(arr));
}
2.11氣泡排序
比較相鄰的元素,將小的放到前面。
for(int i = 0; i < arr.length - 1; i++) {
boolean isSwap = false;
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
isSwap = true;
}
}
if (!isSwap){ break; }
System.out.println(Arrays.toString(arr));
}
2.12氣泡排序:輕氣泡上浮的方式
氣泡排序法可以使用大氣泡沉底的方式,也可以使用輕氣泡上浮的方式實現。如下為使用輕氣泡上浮的方式實現氣泡排序演算法。
for (int i = 0; i < arr.length - 1; i++) {
boolean isSwap = false;
for (int j = arr.length - 1; j > i; j--) {
if (arr[j] < arr[j - 1]) {
int t = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = t;
sSwap = true;
}
}
if (!isSwap){ break; }
System.out.println(Arrays.toString(arr));
}
2.13二分法查詢
intlow = 0; inthigh = arr.length - 1; intmid = -1;
while(low <= high) {
mid = (low + high) / 2;
if (arr[mid] < value){ low = mid + 1; }
else if (arr[mid] > value){ high = mid - 1; }
else{ break;}
}
if (low <= high) { System.out.println("可以找到:index = " + mid + "。");
} else { System.out.println("無法找到!"); }
二分法思想是取中,比較 :
1)求有序序列arr的中間位置mid。 2)k為要查詢的數字。
若arr[mid] == k,查詢成功;
若arr[mid] > k,在前半段中繼續進行二分查詢;
若arr[mid] < k,則在後半段中繼續進行二分查詢。
假如有一組數為3、12、24、36、55、68、75、88要查給定的值k=24。可設三個變數low、mid、high分別指向資料的上界,中間和下界,mid=(low+high)/2.
1)開始令low=0(指向3),high=7(指向88),則mid=3(指向36)。因為k<mid,故應在前半段中查詢。
2)令新的high=mid-1=2(指向24),而low=0(指向3)不變,則新的mid=1(指向12)。此時k>mid,故確定應在後半段中查詢。
3)令新的low=mid+1=2(指向24),而high=2(指向24)不變,則新的mid=2,此時k=arr[mid],查詢成功。
如果要查詢的數不是數列中的數,例如k=25,當第四次判斷時,k>mid[2],在後邊半段查詢,令low=mid+1,即low=3(指向36),high=2(指向24)出現low>high的情況,表示查詢不成功。
2.14 Java系統API方法呼叫
Arrays類,是陣列的工具類,包含很多陣列有關的工具方法。如:
1)toString() 連線陣列元素為字串,方便陣列內容輸出。
2)equals 比較兩個陣列序列是否相等。
3)sort() 對陣列進行排序,小到大排序。
4)binarySearch(names, "Tom") 二分查詢,必須在有序序列上使用。
2.15二進位制基礎
1)計算機中一切資料都是2進位制的! 基本型別,物件,音訊,視訊。
2)10進位制是人類習慣,計算按照人類習慣利用演算法輸入輸出。
"10" -演算法轉化-> 1010(2) 1010 -演算法轉化-> "10"
3)16進位制是2進位制的簡寫,16進位制就是2進位制!
4)計算機硬體不支援正負號,為了解決符號問題,使用補碼演算法,補碼規定高位為1則為負數,每位都為1則為-1,如1111 1111 = -1 = 0xff
5)二進位制數右移>>:相當於數學 / 2(基數),且正數高位補0,負數高位補1;二進位制字左移<<:相當於數學 * 2(基數),且低位補0;二進位制數無符號右移>>>:相當於數學 / 2(基數),且不論正負,高位都補0。
6)注意掩碼運算:把擴充套件後前面為1的情況除去,與0xff做與運算。
eg1:二進位制計算
int max = 0x7fffffff; long l = max + max + 2; System.out.println( l );// 0
eg2:二進位制運算(拼接與拆分)
int b1 = 192; int b2 = 168; int b3 = 1; int b4 = 10; int color = 0xD87455;
int ip = (b1<<24) + (b2<<16) + (b3<<8) + b4; // 或者ip = (b1<<24) | (b2<<16) | (b3<<8) | b4;
int b = color&0xff; // 85 int g = (color >>> 8)&0xff; // 116 int r = (color >>> 16)&0xff;// 216
2.16 Java基礎其他注意事項
 Java程式嚴格區分大小寫。
 類名,每個單詞首字母必須大寫(公司規範!)。
 一個Java應用程式,有且只有一個main方法,作為程式的入口點。
 每一條Java語句必須以分號結束。
 類定義關鍵字class前面可以有修飾符(如public),如果前面的修飾符是public,該類的類名必須要與這個類所在的原始檔名稱相同。
 注意程式的縮排。
 double a[] = new double[2]; //語法可以,但企業中一定不要這麼寫,Java中[]建議放前面。
 Java中所有範圍引數都是包含0,不包含結束,如int n = random.nextInt(26); //生成0到26範圍內的隨機數,不包括26。
 任何資料在記憶體中都是2進位制的資料,記憶體中沒有10進位制16進位制。
 int n = Integer.parseInt(str);//將字串--> int 整數。
 System.out.println(Long.toBinaryString(maxL)); Long型別用Long.XXXX 。
 程式:資料+演算法 資料即為變數,演算法為資料的操作步驟,如:順序、選擇、迴圈。
 字串按編碼大小排序。

三、物件導向
Object:物件,東西,一切皆物件 = = 啥都是東西
物件導向核心:封裝、繼承、多型。
3.1類
1)是同型別東西的概念,是對現實生活中事物的描述,對映到Java中描述就是class定義的類。類是物件的模板、圖紙,是物件的資料結構定義。簡單說就是“名詞”。
2)其實定義類,就是在描述事物,就是在定義屬性(變數)和方法(函式)。
3)類中可以宣告:屬性,方法,構造器;屬性(變數)分為:例項變數,區域性變數;例項變數:用於宣告物件的結構的,在建立物件時候分配記憶體,每個物件有一份!例項變數(物件屬性)在堆中分配,並作用於整個類中,例項變數有預設值,不初始化也能參與運算;區域性變數在棧中分配,作用於方法或語句中,必須初始化,有值才能運算。
4)類與類之間的關係:①關聯:一個類作為另一個類的成員變數:需要另一個類來共同完成。class A { pulic B b } class B {} ②繼承:class B extends A {} class A {} ③依賴:個別方法和另一個類相關。class A { public void f(B b) {}//引數裡有B public B g() {}//返回值裡有B } class B {}

5)null與空指標異常:引用型別變數用於存放物件的地址,可以給引用型別賦值為null,表示不指向任何物件。當某個引用型別變數為null時無法對物件實施訪問(因為它沒有指向任何物件)。此時,如果通過引用訪問成員變數或呼叫方法,會產生NullPointerException空指標異常。
 注意事項:除了8中基本型別,其他都是引用型別變數(也叫控制程式碼)。
3.2物件
是這類事物實實在在存在的個體!利用類做為模板建立的個體例項,本質是資料。
匿名物件:使用方式一:當對物件的方法只呼叫一次時,可用匿名物件來完成,這樣比較簡化。如果對一個物件進行多個成員呼叫,則必須給這個物件起個名字。
使用方式二:可以將匿名物件作為實際引數進行傳遞。
3.3包
1)包名必須是小寫,多個單詞用“.”隔開。在同一個包中,不能有同名的類!
2)只要在同一個包中,則可直接用extends(型別互知道在哪),若不在同一個包中,則用import匯入。
3.4方法及其呼叫
是用於對當前物件資料進行演算法計算,實現業務功能。方法是物件的功能,物件的動作,物件的行為。總之是動詞!方法名沒有規定,建議首單詞為小寫動詞,其他單詞首字母大寫。必須定義返回值!可有無引數方法。方法呼叫只有兩種方式:①物件引用呼叫②類名呼叫(即靜態類時)。
3.5引用
是對個體的標識名稱。
1)是代詞,是物件的引用,就像拴著物件的繩子。
2)引用本身不是物件!引用指代了物件!
3)引用的值是物件的地址值(或叫控制程式碼),通過地址值引用了物件。
4)引用的值不是物件!
 注意事項:“.”叫取成員運算,可以理解為“的”。
3.6訪問控制(封裝)
封裝:將資料封裝到類的內部,將演算法封裝到方法中。
1)封裝原則:將不需要對外提供的內容都隱藏起來,把屬性都隱藏,提供公共方法對其訪問,通常有兩種訪問方式:set 設定,get 獲取。
2)封裝結果:存在但是不可見。
3)public:任何位置可見,可以修飾:類、成員屬性、成員方法、內部類、跨包訪問類(需要使用import語句匯入),成員屬性 = = 成員變數。
4)protected:當前包中可見,子類中可見。可以修飾:成員屬性、成員方法、內部類(只能在類體中使用,不能修飾類)。
5)預設的:當前包內部可見,就是沒有任何修飾詞,可以修飾:類、成員屬性、成員方法、內部類,但在實際專案中很少使用。預設類(包內類)的訪問範圍:當前包內部可見,不能在其他包中訪問類,訪問受限!main方法若定在預設類中JVM將找不到,無法執行,因此必定在public類中。
6)private:僅僅在類內部可見。可以修飾:成員屬性、成員方法、內部類(只能在類體中使用,不能修飾類)。私有的方法不能繼承,也不能重寫。
 注意事項:在企業專案中建議:所有類都是公用類。封裝的類使用內部類!
3.7構造器
用於建立物件並初始化物件屬性的方法,叫“構造方法”,也叫“構造器”;構造器在類中定義。
1)構造器的名稱必須與類名同名,包括大小寫。
2)構造器沒有返回值,但也不能寫void,也不能寫return。
3)構造器的引數:一般是初始化物件的前提條件。
4)用new呼叫!且物件一建立,構造器就執行且僅執行一次。一般方法可被呼叫多次。
5)類一定有構造器!這是真的,不需要質疑!
6)如果類沒有宣告(定義)任何的構造器,Java編譯器會自動插入預設構造器!
7)預設構造是無引數,方法體是空的構造器,且預設構造器的訪問許可權隨著所屬類的訪問許可權變化而變化。如,若類被public修飾,則預設構造器也帶public修飾符。
8)預設構造器是看不到的,一旦自己寫上構造器則預設構造器就沒有了,自己寫的叫自定義構造器,即便自己寫的是空引數的構造器,也是自定義構造器,而不是預設構造器。
9)如果類宣告瞭構造器,Java編譯器將不再提供預設構造器。若沒手動寫出無參構造器,但卻呼叫了無參構造器,將會報錯!
eg:預設構造器
public class Demo { public static void main(String[] args) {
Foo foo = new Foo();//呼叫了javac自動新增的預設構造器!
//Koo koo = new Koo();//編譯錯誤,沒有Koo()構造器
Koo koo = new Koo(8); } }
class Foo { } //Foo有構造器,有無引數的預設構造器!
class Koo { public Koo(int a) { //宣告瞭有引數構造器
System.out.println("Call Koo(int)"); } }

10)構造器是可以過載的,過載的目的是為了使用方便,過載規則與方法過載規則相同。
11)構造器是不能繼承的!雖說是叫構造方法,但實際上它不是常說的一般方法。
12)子類繼承父類,那麼子型別構造器預設呼叫父型別的無引數構造器。
13)子類構造器一定要呼叫父類構造器,如果父類沒有無引數構造器,則必須使用super(有引數的),來呼叫父類有參的構造器。 那麼,為什麼子類一定要訪問父類的構造器?
因為父類中的資料子類可以直接獲取。所以子類物件在建立時,需要先檢視父類是如何對這些資料進行初始化的,所以子類在物件初始化時,要先訪問一下父類中的構造器。
總之,子類中至少會有一個構造器會訪問父類中的構造器,且子類中每一個建構函式內的第一行都有一句隱式super()。
3.8 super()、super.和 this()、this.
1)this:在執行期間,哪個物件在呼叫this所在的方法,this就代表哪個物件,隱含繫結到當前“這個物件”。
2)super():呼叫父類無參構造器,一定在子類構造器第一行使用!如果沒有則是預設存在super()的!這是Java預設新增的super()。
3)super.是訪問父類物件,父類物件的引用,與this.用法一致
4)this():呼叫本類的其他構造器,按照引數呼叫構造器,必須在構造器中使用,必須在第一行使用,this() 與 super() 互斥,不能同時存在
5)this.是訪問當前物件,本類物件的引用,在能區別例項變數和區域性變數時,this可省略,否則一定不能省!
6)如果子父類中出現非私有的同名成員變數時,子類要訪問本類中的變數用this. ;子類要訪問父類中的同名變數用super. 。
eg1:方法引數傳遞原理 與 this關鍵字

eg2:this. 和 this()
Cell c = new Cell(); System.out.println(c.x + ","+c.y);
class Cell { int x; int y;
public Cell() { this(1,1);//呼叫本類的其他構造器 }
public Cell( int x, int y) { this.x = x ; this.y = y; } }
eg3:super()
class Xoo{ public Xoo(int s) { System.out.println("Call Xoo(int)"); } } //super()用於在子類構造器中呼叫父類的構造器
class Yoo extends Xoo{
//public Yoo() {}//編譯錯誤,子類呼叫不到父型別無引數構造器 public Yoo(){//super();//編譯錯誤,子類呼叫不到父型別無引數構造器
super(100);//super(100) 呼叫了父類 Xoo(int) 構造器 } }
3.9過載和重寫
1)重寫:通過類的繼承關係,由於父類中的方法不能滿足新的要求,因此需要在子類中修改從父類中繼承的方法叫重寫(覆蓋)。
①方法名、引數列表、返回值型別與父類的一模一樣,但方法的實現不同。若方法名、引數列表相同,但返回值型別不同會有變異錯誤!若方法名、返回值型別相同,引數列表不同,則不叫重寫了。
②子類若繼承了抽象類或實現了介面,則必須重寫全部的抽象方法。若沒有全部實現抽象方法,則子類仍是一個抽象類!
③子類重寫抽象類中的抽象方法或介面的方法時,訪問許可權修飾符一定要大於或等於被重寫的抽象方法的訪問許可權修飾符!
④靜態方法只能重寫靜態方法!
2)過載:方法名一樣,引數列表不同的方法構成過載的方法(多型的一種形式)。
①呼叫方法:根據引數列表和方法名呼叫不同方法。
②與返回值型別無關。
③過載遵循所謂“編譯期繫結”,即在編譯時根據引數變數的型別判斷應呼叫哪個方法。 eg:過載
int[] ary1 = {'A','B','C'}; char[] ary2 = {'A', 'B', 'C'};
System.out.println(ary1);//println(Object)
//按物件呼叫,結果為地址值,沒有println(int[])
System.out.println(ary2);//println(char[]) ABC
System.out.println('中');//println(char) 中
System.out.println((int)'中');//println(int) 20013
3.10繼承
父子概念的繼承:圓繼承於圖形,圓是子概念(子型別 Sub class)圖形是父型別(Super Class也叫超類),繼承在語法方面的好處:子類共享了父類的屬性和方法的定義,子類複用了父類的屬性和方法,節省了程式碼。
1)繼承是is a :“是”我中的一種,一種所屬關係。
2)子型別物件可以賦值給父型別變數(多型的一種形式),變數是代詞,父型別代詞可以引用子型別東西。
3)繼承只能是單繼承,即直接繼承,而非間接繼承。因為多繼承容易帶來安全隱患,當多個父類中定義了相同功能,當功能內容不同時,子類無法確定要執行哪一個。
4)父類不能強轉成子類,會造型異常!子類向父類轉化是隱式的。
5)只有變數的型別定義的屬性和方法才能被訪問!見下例。
6)重寫遵循所謂“執行期繫結”,即在執行的時候根據引用變數指向的實際物件型別呼叫方法。
eg:Shape s,s只能訪問Shape上宣告的屬性和方法
Circle c = new Circle(3,4,5);
Shape s = c;//父型別變數s引用了子型別例項
//s 和 c引用了同一個物件new Circle(3,4,5)
s.up(); System.out.println(c.r);
System.out.println(c.area());
//System.out.println(s.area());//編譯錯誤
//System.out.println(s.r);//在Shape上沒有定義r屬性!
7)引用型別變數的型別轉換 instanceof
public static void main(String[] args) {
Circle c = new Circle(3,4,5); Rect r = new Rect(3,4,5,6);
Shape s = c; Shape s1 = r;
//Circle x = s;//編譯錯誤,父型別變數不能賦值給子型別
Circle x = (Circle)s;//正常執行
//Circle y = (Circle)s1;//執行異常,型別轉換異常
//instaceof instace:例項 of:的
//instaceof 運算 檢查變數引用的物件的型別是否相容
//s引用的是圓物件,s instanceof Circle 檢查s引用的物件是否是Circle型別的例項!
System.out.println(s instanceof Circle);//true
System.out.println(s1 instanceof Circle);//false
test(c); test(r); }
public static void test(Shape s){//多型的引數
//if(s instanceof Circle)保護了(Circle)s不會出現異常
if(s instanceof Circle){//實現了安全的型別轉換
Circle c = (Circle) s; System.out.println("這是一個圓, 面積"+c.area()); }
if(s instanceof Rect){
Rect r = (Rect) s; System.out.println("這是一個矩形, 面積"+r.area()); } }
8)繼承時候物件的建立過程
①Java首先遞迴載入所有類搭配方法區。②分配父子型別的記憶體(例項變數)。③遞迴呼叫構造器。

9)重寫方法與過載方法的呼叫規則

10)屬性繫結到變數的型別,由變數型別決定訪問哪個屬性;方法動態繫結到物件,由物件的型別決定訪問哪個方法。(強轉對方法動態繫結到物件無影響,因為強轉的是父類的引用,而例項是沒變的,只是把例項當作另一個狀態去看而已。但是強轉對屬性動態繫結到變數型別有影響。)其他解釋請看多型部分!

eg1:方法動態繫結到執行期間物件的方法 例項1

eg2:方法動態繫結到執行期間物件的方法 例項2

11)為何查閱父類功能,建立子類物件使用功能?
Java中支援多層繼承,也就是一個繼承體系。想要使用體系,先查閱父類的描述,因為父類中定義的是該體系中共性的功能,通過了共性功能,就可以知道該體系的基本功能,那麼這個體系已經可以基本使用了,然而在具體呼叫時,要建立最(低)子類的物件,原因如下:①因為父類有可能不能建立物件②建立子類物件,可以使用更多的功能,包括基本的也包括特有的。
12)屬性無繼承概念,所以你有你的,我有我的,各自呼叫各自的,不影響,即使子父類中有同名屬性也無影響。
eg:子父類同名屬性無影響
class Base { public static final String FOO="foo"; public static void main(String[] args){
Base b=new Base(); Sub s=new Sub();
Base.FOO;//foo b.Foo; //foo Sub.Foo;//bar s.Foo;//bar
( (Base) s . Foo);// foo } }
class Sub extends Base{ public static final String FOO="bar"; }
3.11 static
靜態的,只能在類內部使用,可以修飾:屬性,方法,內部類。在類載入期間初始化,存在方法區中。
1)靜態成員隨著類的載入而載入,載入於方法區中,且優先於物件存在。
2)靜態修飾的成員:屬於類級別的成員,是全體類例項(所有物件)所共享。
3)靜態屬性:只有一份(而例項變數是每個物件有一份),全體例項共享,類似於全域性變數。
4)使用類名訪問靜態變數,以及類名直接呼叫方法,不需要建立物件。
5)靜態方法只能訪問靜態成員(靜態屬性和靜態方法),非靜態方法既可訪問靜態,也可訪問非靜態。
6)靜態方法中沒有隱含引數this,因此不能訪問當前物件資源。也不能定義this和super關鍵字,因為靜態優於物件先存在。
7)非靜態方法省略的是this,靜態方法省略的是類名(在同一類中),即直接使用屬性和方法。
8)靜態方法一般用於與當前物件無關工具方法,工廠方法。如:Math.sqrt() Arrays.sort()
9)靜態程式碼塊:隨著類的載入而執行(用到類的內容才叫載入,只有引用是不載入的),且只執行一次,且優先於主函式,用於給類初始化。
10)程式碼塊(構造程式碼塊):給所有物件進行統一初始化,且優先於構造器執行;而構造器是給對應的物件進行初始化。
eg:靜態程式碼塊與程式碼塊(構造程式碼塊)
class Goo{
{//程式碼塊(構造程式碼塊),在建立物件時候執行!類似於構造器的作用
System.out.println("HI");
}
static{//靜態程式碼塊,在類的載入期間執行,只執行一次
System.out.println("Loading Goo.class");
}
}
public static void main(String[] args) {
Point p1 = new Point(3,4); Point p2 = new Point(6,8);
//在物件上呼叫方法,當前物件隱含傳遞給隱含引數this
System.out.println(p1.distance(p2));//distance(p1,p2)
double d = Point.distance(p1, p2); System.out.println(d); //5
//靜態方法呼叫時候不傳遞隱含的當前物件引數 }
class Point{ int x; int y;
public Point(int x, int y) { this.x = x; this.y = y; }
//靜態方法中沒有隱含引數this!在靜態方法中不能訪問this的屬性和方法!
public static double distance(Point p1, Point p2){
int a = p1.x -p2.x; int b = p1.y -p2.y; return Math.sqrt(a*a + b*b); }
/** 計算當前點(this)到另外一個點(other)的距離 */
public double distance(/*Point this*/ Point other){
int a = this.x - other.x; int b = this.y - other.y;
double c = Math.sqrt(a*a + b*b); return c; } }
11)物件的建立過程及順序:
Person P = new Person( “chang” , 23) ; 這句話都做了什麼事情?
①因為new用到了Person.class,所以會先找到Person.class檔案載入到記憶體中。
②執行該類中的static程式碼塊(如果有的話),給Person類.class類進行初始化。
③在堆記憶體中開闢空間,分配記憶體地址,棧記憶體中開闢空間也就有了。
④在堆記憶體中建立物件的特有屬性,並進行預設(隱式)初始化。
⑤對屬性進行顯式初始化。
⑥對物件進行構造程式碼塊初始化。
⑦對物件進行對應的構造器初始化。
⑧將記憶體地址賦給棧記憶體中的P變數。
3.12 final
最終的,可以修飾:類、方法、變數(成員變數和區域性變數)。
1)final 修飾的類:不能再繼承。
2)final修飾的方法:不能再重寫。
3)final 的方法和類,阻止了動態代理模式!動態代理模式廣泛的應用在: Spring Hibernate Struts2
4)企業程式設計規範:不允許使用final 的方法和類!
5)final的變數:final變數只能初始化一次(賦值一次,且方法中不能有給final變數賦值的語句!因為方法可被呼叫多次!),不能再修改!也可在方法的引數列表中新增final。
eg1:final的區域性變數
final int a; a = 5;//第一次叫初始化!不是賦值 //a = 8;//編譯錯誤
public static void test(final int a, int b){
//a++;//編譯錯誤,不能再修改
System.out.println(a); }
eg2:final的陣列
final String[] ary={"A","B"}; //ary:陣列變數,ary[0]陣列元素
ary[0]="Tom";//陣列元素可以修改
//ary=null;//陣列變數不能修改
eg3:final的例項變數
public static void main(String[] args) {
Dog d1 = new Dog(); Dog d2 = new Dog();
//d1.id = 8;//每個例項的id 不可以再修改
System.out.println(d1.id+","+d2.id+","+Dog.numOfDogs); }
class Dog{ final int id;//例項變數,每個物件一份,不能再次修改
static int numOfDogs=0;//靜態,只有一份
public Dog() { id = numOfDogs++; } }
6)static final 共同修飾的叫常量,常量:public static final double PI = 3.14; PI 是直接數的代名詞,是名字。字面量(==直接量):直接寫出數值 3.1415926535897 巨集觀說:字面量和常量都稱為常量!
3.13多型
繼承體現了多型:父型別變數可以引用各種各樣的子型別例項,也可接收子類物件。
個體的多型:父型別的子型別例項是多種多樣的。
行為的多型:父型別定義方法被子類重寫為多種多樣的,過載也是多型的方法。
1)千萬不能出現將父類物件轉成子類型別,會造型異常!
2)多型前提:必須是類與類之間有關係。要麼繼承,要麼實現。通常還有一個前提:存在覆蓋。
3)多型的好處:多型的出現大大的提高程式的擴充套件性。
4)多型的弊端:雖然提高了擴充套件性,但是隻能使用父類的引用訪問父類中的成員。
5)在多型中成員函式的特點:
①在編譯時期:參閱引用型變數所屬的類中是否有呼叫的方法。如果有,編譯通過,如果沒有編譯失敗。
②在執行時期:參閱物件所屬的類中是否有呼叫的方法。
③簡單總結就是:成員方法在多型呼叫時,編譯看左邊,執行看右邊。
6)在多型中,成員變數的特點:無論編譯和執行,都參考左邊(引用型變數所屬的類)。
7)在多型中,靜態成員方法和屬性的特點:無論編譯和執行,都參考做左邊。
8)父類引用指向子類物件,當父類想使用子類中特有屬性、方法時,要向下轉型。
3.14抽象類
抽象就是將擁有共同方法和屬性的物件提取出來,提取後,重新設計一個更加通用、更加大眾化的類,就叫抽象類。
1)abstract關鍵字可以修飾類、方法,即抽象類和抽象方法。
2)抽象類可以有具體的方法,或者全部都是具體方法,但一個類中只要有一個抽象方法,那麼這個類就是抽象類,並且必須用abstract修飾類。
3)抽象類可以被繼承,則子類必須實現抽象類中的全部抽象方法,否則子類也將是抽象類。抽象類也可主動繼承實體類。
4)抽象類不能例項化,即不能用new生成例項。
5)可以宣告一個抽象型別的變數並指向具體子類的物件。
6)抽象類可以實現介面中的方法。
7)抽象類中可以不定義抽象方法,這樣做僅僅是不讓該類建立物件。
3.15介面
interface 差不多 = = abstract class
1)介面是like a :“像”我中的一種,是繼承體系之外的,用於功能擴充套件!想擴充套件就實現,不想就不用實現。
2)介面中只能宣告抽象方法和常量且宣告格式都是固定的,只不過可以省略。
eg:介面中宣告常量和抽象方法的格式是固定的
interface Runner {
/*public abstract final*/int SPEED=100;//宣告常量
/*public abstract 省略了,寫也對*/void run();//宣告抽象方法
}
3)介面中的成員不寫修飾符時,預設都是public。
4)介面不能有構造器,因為不能例項化何以初始化,介面只能被“實現”。
5)具體類實現了一個介面,則必須實現全部的抽象方法,若沒有全部實現,則該類為抽象類。所以說,介面約定了具體類的方法,約定了類的外部行為。
6)具體類可以同時實現多個介面,就是多繼承現象。
7)多重繼承:class Cat implements Hunter , Runner Cat 即是Hunter 也是 Runner。
8)介面用 implements 表實現,實際是繼承關係,可有多個介面(實現),繼承用 extends 只能有一個繼承關係。
9)一個類既可以繼承的同時,又“實現”介面:class A extends B implements C , D
10)類與類之間是繼承關係,類與介面之間是實現關係,介面與介面之間是繼承關係,且只有介面之間可以多繼承,即:interface A{},interface B{},interface C extends A , B 但介面多繼承時要注意,要避免A、B介面中有方法名相同、引數列表相同,但返回值型別不相同的情況,因為被具體類實現時,不確定呼叫哪個方法。
11)abstract class和interface有什麼區別。
①從語法角度:abstract class方法中可以有自己的資料成員,也可以有非abstract的成員方法,並賦予方法的預設行為,而在interface方式中一般不定義成員資料變數,所有的方法都是abstract,方法不能擁有預設的行為。
②從程式設計的角度:abstract class在java語言中表示的是一種繼承關係,一個類只能使用一次繼承關係。而一個類可以實現多個interface。
③從問題域角度:abstract class在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在"is a"關係,即父類和派生類在概念本質上應該是相同的。對於interface 來說則不然,並不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已。
3.16內部類
當描述事物時,事物的內部還有事物,該事物用內部類來描述。因為內部事物在使用外部事物的內容。
在類內部定義的類為成員內部類,在方法裡定義的類為區域性內部類,被static修飾的為靜態內部類。一個類中可有多個內部類。
1)內部類主要用於,封裝一個類的宣告在類的內部,減少類的暴露。
2)內部類的例項化:例項化時不需要出寫物件,非要寫的話為:new 外部類名.內部類名();而不是外部類名.new 內部類名()。
3)內部類的訪問規則:內部類可以直接訪問外部類中的成員,包括私有。之所以可以直接訪問外部類中的成員,是因為內部類中持有了一個外部類的引用,格式:外部類名.This即下面第4條。外部類要訪問內部類,必須建立內部類物件。
4)當內部類定義在外部類的成員位置上,而且非私有,則在外部其他類中可以直接建立內部類物件。格式:外部類名.內部類名 變數名 = 外部類物件.內部類物件;
Outer.Inner in = new Outer().new Inner();
5)當內部類在成員位置上,就可以被成員修飾符所修飾。比如private:將內部類在外部類中進行封裝。
6)靜態內部類:被static修飾後就具備了靜態的特性。當內部類被static修飾後,只能直接訪問外部類中的static成員,出現了訪問侷限。
①在外部其他類中,如何直接訪問static內部類的非靜態成員呢?
new Outer.Inner().function();
②在外部其他類中,如何直接訪問static內部類的靜態成員呢?
Outer.Inner.function();
 注意事項:
 當內部類中定義了靜態成員,該內部類必須是static的。
 當外部類中的靜態方法訪問內部類時,內部類也必須是static的。
7)內部類想呼叫外部類的成員,需要使用:外部類名.this.成員,即OutterClassName.this表示外部類的物件。如果寫this.成員= =成員,呼叫的還是內部類的成員(屬性或方法)。
8)Timer 和 TimerTask:繼承TimerTask 重寫run()方法,再用Timer類中的schedule方法定時呼叫,就能自動啟用run()(不像以前似的要用 .XXX 呼叫)。
eg:內部類
class Xoo{ Timer timer = new Timer();
public void start(){
timer.schedule(new MyTask(), 0, 1000);//0表示立即開始,無延遲
timer.schedule(new StopTask(), 1000*10);//在10秒以後執行一次 }
class StopTask extends TimerTask{
public void run() { timer.cancel(); }//取消timer上的任務 }
class MyTask extends TimerTask {
int i=10; public void run() { System.out.println(i--); } }
3.17匿名類
匿名內部類==匿名類
1)匿名內部類的格式: new 父類或者介面(){定義子類的內容};如new Uoo(){}就叫匿名內部類!是繼承於Uoo類的子類或實現Uoo介面的子類,並且同時建立了子型別例項,其中{}是子類的類體,可以寫類體中的成員。
2)定義匿名內部類的前提:內部類必須是繼承一個類或者實現介面。
3)匿名內部類沒有類名,其實匿名內部類就是一個匿名子類物件。而且這個物件有點胖。可以理解為帶內容的物件。
4)在匿名內部類中只能訪問final區域性變數。
5)匿名內部類中定義的方法最好不要超過3個。


eg1:匿名內部類的建立
public static void main(String[] args) {
Uoo u = new Uoo();//建立Uoo例項Uoo u1 = new Uoo(){};//建立匿名內部類例項
Uoo u2 = new Uoo(){
public void test() {//方法的重寫 System.out.println("u2.test()"); } };
u2.test();//呼叫在匿名內部類中重寫的方法。
// new Doo();編譯錯誤,不能建立介面例項
Doo doo = new Doo(){//實現介面,建立匿名內部類例項
public void test() {//實現介面中宣告的抽象方法
System.out.println("實現test"); } };
doo.test();//呼叫方法
}
interface Doo{ void test(); }
class Uoo{ public void test(){} }
eg2:匿名內部類中只能訪問final區域性變數
final Timer timer=new Timer();
timer.schedule(new TimerTask(){
public void run(){ timer.cancel();//在匿名內部類中只能訪問final區域性變數
}
}, 1000*10);
6)nonymous Inner Class (匿名內部類) 是否可以extends(繼承)其它類?是否可以implements(實現)interface(介面)?
匿名內部類是可以繼承其它類,同樣也可以去實現介面的,用法為:
這樣的用法在swing程式設計中是經常使用的,就是因為它需要用到註冊監聽器機制,而該監聽類如果只服務於一個元件,那麼將該類設定成內部類/匿名類是最方便的。
3.18二維陣列和物件陣列
二維陣列(假二維陣列),Java中沒有真正的二維陣列!Java二維陣列是元素為陣列的陣列。
物件陣列:元素是物件(元素是物件的引用)的陣列。
Point[] ary;// 宣告瞭陣列變數ary
ary = new Point[3];// 建立了陣列物件
// new Point[3]實際情況:{null,null,null}
// 陣列元素自動初始化為null,並不建立元素物件!
System.out.println(ary[1]);// null
ary[0] = new Point(3, 4); ary[1] = new Point(5, 6); ary[2] = new Point(1, 2);
System.out.println(ary[1]);// 預設呼叫了物件的toString()
System.out.println(ary[1].toString());//結果上面的一樣
//toString是Object類定義,子類繼承的方法
//在輸出列印物件的時候,會預設呼叫,重寫這個方法可以列印的更好看!
System.out.println(Arrays.toString(ary));//輸出3個物件
System.out.println(ary.toString());//地址值
int[] c={1,3,5,7};
System.out.println(c[1]);
//System.out.println(c[1].toString());//錯誤,不能在int型別呼叫toString()
3.19其他注意事項
1)Java 檔案規則:
一個Java原始檔中可以有多個類,但只能有一個公有類!其他類只能是預設類(包中類)而且Java的資料夾一定與公有類類名一致!如果沒有公有類,可以和任何一個檔名一致。
 一般建議:一個檔案一個公有類!一般不在一個檔案中寫多個類
2)JVM記憶體結構堆、棧和方法區分別儲存的內容:
JVM會在其記憶體空間中開闢一個稱為“堆”的儲存空間,這部分空間用於儲存使用new關鍵字建立的物件。
棧用於存放程式執行過程當中所有的區域性變數。一個執行的Java程式從開始到結束會有多次方法的呼叫。JVM會為每一個方法的呼叫在棧中分配一個對應的空間,這個空間稱為該方法的棧幀。一個棧幀對應一個正在呼叫中的方法,棧幀中儲存了該方法的引數、區域性變數等資料。當某一個方法呼叫完成後,其對應的棧幀將被清除。
方法區該空間用於存放類的資訊。Java程式執行時,首先會通過類裝載器載入類檔案的位元組碼資訊,經過解析後將其裝入方法區。類的各種資訊都在方法區儲存。

四、Java SE核心I
4.1 Object類
在Java繼承體系中,java.lang.Object類位於頂端(是所有物件的直接或間接父類)。如果一個類沒有寫extends關鍵字宣告其父類,則該類預設繼承java.lang.Object類。Object類定義了“物件”的基本行為,被子類預設繼承。
1)toString方法:返回一個可以表示該物件屬性內容的字串。
MyObject obj=new MyObject(); String info=obj.toString(); System.out.println(info);
A.上例為什麼我有toString方法?
因為所有的類都繼承自Object,而toString方法是Ojbect定義的,我們直接繼承了這個方法。Object的toString方法幫我們返回一個字串,這個字串的格式是固定的:型別@hashcode,這個hashcode是一串數字,在java中叫控制程式碼,或叫地址(但不是真實的實體地址,是java自己的一套虛擬地址,防止直接操作記憶體的)。
public String toString(){//只能用public,重寫的方法訪問許可權要大於等於父類中方法的許可權
return "這個是我們自己定義的toString方法的返回值MyObject!"; }
B.上例為什麼要重寫toString方法?
toString定義的原意是返回能夠描述當前這個類的例項的一串文字,我們看一串hashcode沒意義,所以幾乎是要重寫的。
public static void main(String[] args){ //System.out.println(toString());//不行!編譯錯誤!
Point p=new Point(1,2); System.out.println(p);//輸出p物件的toString方法返回值 }
C.上例為何有編譯錯誤?
不能直接使用toString方法,因為該方法不是靜態的。ava語法規定:靜態方法中不能直接引用非靜態的屬性和方法,想引用必需建立物件。非靜態方法中可以直接引用靜態屬性和方法。
2)equals方法:用於物件的“相等”邏輯。
A.在Object中的定義: public boolean equals(Object obj){ return (this==obj); }
由此可見,this==obj與直接的==(雙等於)效果一樣,僅僅是根據物件的地址(控制程式碼,那個hashcode值)來判斷物件是否相等。因此想比較物件與給定物件內容是否一致,則必須重寫equals方法。
B.“==”與equals的區別:
用“==”比較物件時,描述的是兩個物件是否為同一個物件!根據地址值判斷。而equals方法力圖去描述兩個物件的內容是否相等,內容相等取決於業務邏輯需要,可以自行定義比較規則。
C.equals方法的重寫:如,判斷兩點是否相等。
public boolean equals(Object obj){//注意引數
/**若給定的物件obj的地址和當前物件地址一致,那麼他們是同一個物件,equals方法中有大量的內容比較邏輯時,加上這個判斷會節省效能的開銷!*/
if(this == obj){ return true; }
/** equals比較前要進行安全驗證,確保給定的物件不是null!若obj是null,說明該引用變數沒有指向任何物件,那麼就不能引用ojb所指象的物件(因為物件不存在)的屬性和方法!若這麼做就會引發NullPointException,空指標異常!*/
if(obj == null){ return false; }
/**直接將Object轉為子類是存在風險的!我們不能保證Object和我們要比較的物件是同一個型別的這會引發ClassCastException!我們稱為:類造型異常。*/
/**重寫equals時第一件要做的事情就是判斷給定的物件是否和當前物件為同一個型別,不是同型別直接返回false,因為不具備可比性!*/
if(!(obj instanceof Point)){ return false; }
Point p=(Point)obj;
/**不能隨便把父類轉成子類,因為Object是所有類的父類,任何型別都可以傳給它所以不能保證obj傳進來的就是相同型別(點型別)*/
return this.x==p.x && this.y==p.y;//內容比較邏輯定義 }
4.2 String類
是字串型別,是引用型別,是“不可變”字串,無執行緒安全問題。在java.lang.String中。
 注意事項:String str =“abc”;和String str=new String(“abc”);的區別!
1)String在設計之初,虛擬機器就對他做了特殊的優化,將字串儲存在虛擬機器內部的字串常量池中。一旦我們要建立一個字串,虛擬機器先去常量池中檢查是否建立過這個字串,如有則直接引用。String物件因為有了上述的優化,就要保證該物件的內容自建立開始就不能改變!所以對字串的任何變化都會建立新的物件,而不是影響以前的物件!
2)String的equals方法:兩個字串進行比較的時候,我們通常使用equals方法進行比較,字串重寫了Object的equals方法,用於比較字串內容是否一致。雖然java虛擬機器對字串進行了優化,但是我們不能保證任何時候“==”都成立!
3)程式設計習慣:當一個字串變數和一個字面量進行比較的時候,用字面量.equals方法去和變數進行比較,即:if("Hello".equals(str))因為這樣不會產生空指標異常。而反過來用,即:if(str.equals("Hello"))則我們不能保證變數不是null,若變數是null,我們在呼叫其equals方法時會引發空指標異常,導致程式退出。若都為變數則if(str!=null&&str.equals(str1))也可。
4)String另一個特有的equals方法:euqalsIgnoreCase,該方法的作用是忽略大小寫比較字串內容,常用環境:驗證碼。if("hello".equalsIgnoreCase(str))。
5)String的基本方法:
①String toLowerCase():返回字串的小寫形式。如:str.toLowerCase()
②String toUpperCase():返回字串的大寫形式。如:str.toUpperCase()
③String trim():去掉字串兩邊的空白(空格\t\n\r),中間的不去。如:str.trim()
④boolean startsWith():判斷字串是否以引數字串開頭。如:str.startsWith("s")
⑤boolean endsWith():判斷字串是否以引數字串結尾。如:str.endsWith("s")
⑥int length():返回字串字元序列的長度。如:str.length()
eg:如何讓HelloWorld這個字串以hello開頭成立
if(str.toLowerCase().startsWith("hello"))//有返回值的才能繼續 . 先轉成小寫再判斷
6)indexOf方法(檢索):位置都是從0開始的。
①int indexOf(String str):在給定的字串中檢索str,返回其第一次出現的位置, 找不到則返回-1。
②int indexOf(String str,int from):在給定的字串中從from位置開始檢索str,返 回其第一次出現的位置,找不到則返回-1(包含from位置,from之前的不看)。
eg:查詢Think in Java中in後第一個i的位置
index=str.indexOf("in"); index=str.indexOf("i",index+"in".length());
//這裡對from引數加in的長度的目的是 從in之後的位置開始查詢
③int lastIndexOf(String str):在給定的字串中檢索str,返回其最後一次 出現的 位置,找不到則返回-1(也可認為從右往左找,第一次出現的位置)。
④int lastIndexOf(String str,int from):在給定的字串中從from位置開始檢索str, 返回其最後一次出現的位置,找不到則返回-1(包含from位置,from之後的不看)。
7)charAt方法:char charAt(int index):返回字串指定位置(index)的字元。
eg:判斷是否是迴文:上海自來水來自海上
boolean tf=true; for(int i=0;i<str2.length()/2;i++){
//char first=str2.charAt(i); //char last=str2.charAt(str2.length()-i-1);
if(str2.charAt(i)!=str2.charAt(str2.length()-i-1)){//優化
tf = false; break;//已經不是迴文了,就沒有必要再繼續檢查了 } }
8)substring方法(子串):字串的擷取,下標從0開始的。
①String substring(int start,int end):返回下標從start開始(包含)到end結束的字 符串(不包含)。
②String substring(int start):返回下標從start開始(包含)到結尾的字串。
9)getBytes方法(編碼):將字串轉換為相應的位元組。
①byte[] getBytes():以當前系統預設的字串編碼集,返回字串所對應的二進位制 序列。如:byte[] array=str.getBytes(); System.out.println(Arrays.toString(array));
②byte[] getBytes(String charsetName):以指定的字串編碼集,返回字串所對應 的二進位制序列。這個過載方法需要捕獲異常,這裡可能引發沒有這個編碼集的異常, UnsupportedEncodingException,如:str="常"; byte[] bs=info.getBytes("UTF-8");
 注意事項:
 Windows的預設編碼集GBK:英文用1個位元組描述,漢字用2個位元組描述;ISO-8859-1歐洲常用編碼集:漢字用3個位元組描述;GBK國標;GB2312國標;UTF-8編碼集是最常用的:漢字用3個位元組描述。
 編碼:將資料以特定格式轉換為位元組;解碼:將位元組以特定格式轉換為資料。
 String(byte[] bytes, String charsetName)
:通過使用指定的charset解碼指定的byte陣列,構造一個新的String。如:String str=new String(bs,"UTF-8");
10)split方法(拆分):字串的拆分。
String[] split(String regex):引數regex為正規表示式,以regex所表示的字串為 分隔符,將字串拆分成字串陣列。其中,regex所表示的字串不被保留,即 不會存到字串陣列中,可理解為被一刀切,消失!
eg:對圖片名重新定義,保留圖片原來字尾
String name="me.jpg"; String[] nameArray=name.split("\\.");
//以正規表示式拆分 .有特殊含義,所以用\\. 轉義
System.out.println("陣列長度:"+nameArray.length);//如果不用\\.則長度為0
System.out.println(Arrays.toString(nameArray));//任意字元都切一刀,都被切沒了
String newName="123497643."+nameArray[1];
System.out.println("新圖片名:"+newName);
 注意事項:分隔符放前、中都沒事,放最後將把無效內容都忽略。
String str="123,456,789,456,,,";
String[] array=str.split(",");//分隔符放前、中都沒事,放最後將把無效內容都忽略
System.out.println(Arrays.toString(array));//[123, 456, 789, 456]

11)replace方法:字串的替換。
String replaceAll(String regex,String replacement):將字串中匹配正規表示式regex 的字串替換成replacement。如:String str1=str.replaceAll("[0-9]+", "chang");
12)String.valueOf()方法:過載的靜態方法,用於返回各型別的字串形式。
String.valueOf(1);//整數,返回字串1 String.valueOf(2.1);//浮點數,返回字串1.2
4.3 StringUtils類
針對字串操作的工具類,提供了一系列靜態方法,在Apache阿帕奇Commons-lang包下中,需下載。
StringUtils常用方法:
1)String repeat(String str,int repeat):重複字串repeat次後返回。
2)String join(Object[] array,String):將一個陣列中的元素連線成字串。
3)String leftPad(String str,int size,char padChar):向左邊填充指定字元padChar,以達到指定長度size。
4)String rightPad(String str,int size,char padChar):向右邊填充指定字元padChar,以達到指定長度size。
4.4 StringBuilder類
與String物件不同,StringBuilder封裝“可變”的字串,有執行緒安全問題。物件建立後,可通過呼叫方法改變其封裝的字元序列。
StringBuilder常用方法:
1)追加字串:StringBuilder append(String str):
2)插入字串:StringBuilder insert(int index,String str):插入後,原內容依次後移
3)刪除字串:StringBuilder delete(int start,int end):
4)替換字串:StringBuilder replace(int start,int end,String str):含頭不含尾
5)字串反轉:StringBuilder reverse():
eg:各類操作
StringBuilder builder=new StringBuilder();
builder.append("大家好!") .append("好好學習") .append("天天向上");
//返回的還是自己:builder,所以可以再 .
System.out.println(builder.toString());
builder.insert(4, "!"); System.out.println(builder.toString());
builder.replace(5,9,"Good Good Study!"); System.out.println(builder.toString());
builder.delete(9, builder.length()); System.out.println(builder.toString());
 注意事項:
 該類用於對某個字串頻繁的編輯操作,使用StringBuilder可以在大規模修改字串時,不開闢新的字串物件,從而節約記憶體資源,所以,對有著大量操作字串的邏輯中,不應使用String而應該使用StringBuilder。
 append是有返回值的,返回型別是StringBuilder,而返回的StringBuilder其實就是自己(this),append方法的最後一句是return this;
 StringBuilder與StringBuffer區別:效果是一樣的。
StringBuilder是執行緒不安全的,效率高,需JDK1.5+。
StringBuffer是執行緒安全的,效率低,“可變”字串。
在多執行緒操作的情況下應使用StringBuffer,因為StringBuffer是執行緒安全 的,他難免要顧及安全問題,而進行必要的安全驗證操作。所以效率上要 比StringBuilder低,根據實際情況選擇。
4.5正規表示式
實際開發中,經常需要對字串資料進行一些複雜的匹配、查詢、替換等操作,通過正規表示式,可以方便的實現字串的複雜操作。
正規表示式是一串特定字元,組成一個“規則字串”,這個“規則字串”是描述文字規則的工具,正規表示式就是記錄文字規則的程式碼。
[] 表示一個字元
[abc] 表示a、b、c中任意一個字元
[^abc] 除了a、b、c的任意一個字元
[a-z] 表示a到z中的任意一個字元
[a-zA-Z0-9_] 表示a到z、A到Z、0到9以及下滑線中的任意一個字元
[a-z&&[^bc]] 表示a到z中除了b、c之外的任意一個字元,&&表示“與”的關係
. 表示任意一個字元
\d 任意一個數字字元,相當於[0-9]
\D 任意一個非數字字元,相當於[^0-9]
\s 空白字元,相當於[\t\n\f\r\x0B]
\S 非空白字元,相當於[^\s]
\w 任意一個單詞字元,相當於[a-zA-Z0-9_]
\W 任意一個非單詞字元,相當於[^\w]
^ 表示字串必須以其後面約束的內容開始
$ 表示字串必須以其前面約束的內容結尾
? 表示前面的內容出現0到1次
* 表示前面的內容出現0到多次
+ 表示前面的內容出現1到多次
{n} 表示前面的字元重複n次
{n,} 表示前面的字元至少重複n次
{n,m} 表示前面的字元至少重複n次,並且小於m次 X>=n && X<m
 注意事項:
 郵箱格式的正規表示式 @無特殊含義,可直接寫,也可[@]
 使用Java字串去描述正規表示式的時候,會出現一個衝突,即如何正確描述正規表示式的“.”。
起因:在正規表示式中我們想描述一個“.”,但“.”在正規表示式中有特殊含義,他代表任意字元,所以我們在正規表示式中想描述“.”的願義就要寫成“\.”但是我們用java字串去描述正規表示式的時候,因為“.”在java字串中沒有特殊意義,所以java認為我們書寫String s="\.";是有語法錯誤的,因為“.”不需要轉義,這就產生了衝突。
處理:我們實際的目的很簡單,就是要讓java的字串描述"\."又因為在java中"\"是有特殊含義的,代表轉義字元我們只需要將"\"轉義為單純的斜槓,即可描述"\."了所以我們用java描述“\.”的正確寫法是String s="\\.";
 若正規表示式不書寫^或$,正規表示式代表匹配部分內容,都加上則表示權匹配
eg:測試郵箱正規表示式:Pattern的作用是描述正規表示式的格式支援,使用靜態方法compile註冊正規表示式,生成例項。
String regStr="^[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.com|\\.cn|\\.net)+$";
Pattern pattern=Pattern.compile(regStr);//註冊正規表示式
String mailStr="chang_2013@chang.com.cn";
//匹配字串,返回描述匹配結果的Matcher例項
Matcher matcher=pattern.matcher(mailStr);
//通過呼叫Matcher的find方法得知是否匹配成功
if(matcher.find()){ System.out.println("郵箱匹配成功!"); }
else{ System.out.println("郵箱格式錯誤!"); }
4.6 Date類
java.util.Date類用於封裝日期及時間資訊,一般僅用它顯示某個日期,不對他作任何操作處理,作處理用Calendar類,計算方便。
Date date=new Date();//建立一個Date例項,預設的構造方法建立的日期代表當前系統時間
System.out.println(date);//只要輸出的不是類名@hashcode值,就說明它重寫過toString()
long time=date.getTime();//檢視date內部的毫秒值
date.setTime(time+1000*60*60*24);//設定毫秒數讓一個時間Date表示一天後的當前時間
int year=date.getYear();//畫橫線的方法不建議再使用:1、有千年蟲問題。2、不方便計算
4.7 Calendar類
java.util.Calendar類用於封裝日曆資訊,其主作用在於其方法可以對時間分量進行運算。
1)通過Calendar的靜態方法獲取一個例項該方法會根據當前系統所在地區來自行決定時區,幫我們建立Calendar例項,這裡要注意,實際上根據不同的地區,Calendar有若干個子類實現。而Calendar本身是抽象類,不能被例項化!我們不需要關心建立的具體例項為哪個子類,我們只需要根據Calendar規定的方法來使用就可以了。
2)日曆類所解決的根本問題是簡化日期的計算,要想表示某個日期還應該使用Date類描述。Calendar是可以將其描述的時間轉化為Date的,我們只需要呼叫其getTime()方法就可以獲取描述的日期的Date物件了。
3)通過日曆類計算時間:為日曆類設定時間,日曆類設定時間使用通用方法set。
set(int field,int value),field為時間分量,Calendar提供了相應的常量值,value為對應的值。
4)只有月份從0開始:0為1月,以此類推,11為12月,其他時間是正常的從1開始。也可以使用Calendar的常量 calendar.NOVEMBER……等.
5)Calendar.DAY_OF_MONTH 月裡邊的天---號;
Calendar.DAY_OF_WEEK 星期裡的天---星期幾
Calendar.DAY_OF_YEAR 年裡的天
Calendar calendar=Calendar.getInstance();//構造出來表示當前時間的日曆類
Date now=calendar.getTime();//獲取日曆所描述的日期
calendar.set(Calendar.YEAR, 2012);//設定日曆表示2012年
calendar.set(Calendar.DAY_OF_MONTH,15);//設定日曆表示15號
calendar.add(Calendar.DAY_OF_YEAR, 22);//想得到22天以後是哪天
calendar.add(Calendar.DAY_OF_YEAR, -5);//5天以前是哪天
calendar.add(Calendar.MONTH, 1);得到1個月後是哪天
System.out.println(calendar.getTime());
6)獲取當前日曆表示的日期中的某個時間單位可以使用get方法.
int year=calendar.get(Calendar.YEAR);
int month=calendar.get(Calendar.MONTH);
int day=calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(year+"年"+(month+1)+"月"+day+"日");//month要處理
4.8 SimpleDateFormat類
java.text.SimpleDateFormat類,日期轉換類,該類的作用是可以很方便的在字串和日期類之間相互轉換。
1)這裡我們在字串與日期類間相互轉換是需要一些約束的,"2012-02-02"這個字串如何轉換為Date物件?Date物件又如何轉為字串?
parse方法用於按照特定格式將表示時間的字串轉化成Date物件。
format方法用於將日期資料(物件)按照指定格式轉為字串。
2)常用格式字串
字元 含義 示例
y 年 yyyy年-2013年;yy年-13年
M 月 MM月-01月;M月-1月
d 日 dd日-06日;d日-6日-
E 星期 E-星期日(Sun)
a AM或PM a-下午(PM)
H 24小時制 a h時-小午12時
HH:mm:ss-12:46:33
hh(a):mm:ss-12(下午):47:48
h 12小時制
m 分鐘
s 秒
eg:字串轉成Date物件
//建立一個SimpleDateFormat並且告知它要讀取的字串格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
String dateFormat="2013-05-14";//建立一個日期格式字串
//將一個字串轉換為相應的Date物件
Date date=sdf.parse(dateFormat);//要先捕獲異常
System.out.println(date);//輸出這個Date物件
eg:Date物件轉成字串
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date now=new Date(); String nowStr=sdf.format(now);//把日期物件傳進去
3)在日期格式中 - 和 空格 無特殊意義,無特殊含義的都將原樣輸出。
eg:將時間轉為特定格式
//將當前系統時間轉換為2012/05/14 17:05:22的效果
SimpleDateFormat format1=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
nowStr=format1.format(now); System.out.println(nowStr);
4.9 DateFormat類
java.text.DateFormat類(抽象類)是SimpleDateFormat類的父類,用的少,沒SimpleDateFormat靈活。
建立用於將Date物件轉換為日期格式的字串的DateFormat,建立DateFormat物件的例項,使用靜態方法getDateInstance(style,aLocale),style為輸出日期格式的樣式:DateFormat有對應的常量;aLocale為輸出的地區資訊,影響字串的語言和表現形式。
Date now=new Date(); DateFormat format=DateFormat.getDateInstance( DateFormat.MEDIUM,Locale.CHINA);
4.10包裝類
Java語言的8種基本型別分別對應了8種“包裝類”。每一種包裝類都封裝了一個對應的基本型別成員變數,同時還提供了針對該資料型別的實用方法。
1)包裝類的目的:用於將基本型別資料當作引用型別看待。
2)包裝類的名字:除了Integer(int),Character(char)外,其餘包裝類名字都是基本型別名首字母大寫。
3)拆、裝箱:Integer i=new Integer(1);建立一個以物件形式存在的整數1,這種從基本型別轉為引用型別的過程稱之為“裝箱”,反之叫“拆箱”。
4)裝箱:方式一:Double d=new Double(2.2);//裝箱
方式二:Double d=Double.valueOf(2.2);//基本型別都有valueOf方法
5)拆箱:double num=d.doubleValue();//拆箱
6)包裝類使用前提:JDK1.5+
public static void say(Object obj){ System.out.println(obj); }
int a=1;//基本型別,不是Object子類!
say(a);//在java 1.4版本的時候,這裡還是語法錯誤的!因為int是基本型別,不是Object物件,要自己寫8種基本型別對應的方法
7)包裝類的使用:例項化一個物件,該物件代表整數1;Integer的作用是讓基本型別int作為一個引用型別去看待。這樣就可以參與到物件導向的程式設計方式了。由此我們可以將一個int當作一個Object去看待了,也成為了Object的子類。
Integer i=new Integer(a);//裝箱,或者寫Integer i=new Integer(1);
Integer ii=Integer.valueOf(a);//裝箱另一種方式
int num=i.intValue();//拆箱 say(i);//Integer是Object的子類,可以呼叫!
8)JDK1.5包裝類自動拆裝箱(原理):在編譯源程式的時候,編譯器會預處理,將未作拆箱和裝箱工作的語句自動拆箱和裝箱。可通過反編譯器發現。
say(Integer.valueOf(a));自動裝箱 num=i;//引用型別變數怎麼能複製給基本型別呢?
//num=i.intValuse();//自動拆箱
9)包裝類的一些常用功能:將字串轉換為其型別,方法是:parseXXX,XXX代表其型別。這裡要特別注意!一定要保證待轉換的字串描述的確實是或者相容要轉換的資料型別!否則會丟擲異常!
String numStr="123"; System.out.println(numStr+1);//1231
int num=Integer.parseInt(numStr); System.out.println(num+1)//124
long longNum=Long.parseLong(numStr); System.out.println(longNum);//123
double doubleNum=Double.parseDouble(numStr); System.out.println(doubleNum);//123.0
10)Integer提供了幾個有趣的方法:將一個整數轉換為16進位制的形式,並以字串返回;將一個整數轉換為2進位制的形式,並以字串返回。
String bStr=Integer.toBinaryString(num); String hStr=Integer.toHexString(num);
11)所有包裝類都有幾個共同的常:獲取最大、最小值。
int max=Integer.MAX_VALUE;//int最大值 int min=Integer.MIN_VALUE;//int最小值
System.out.println(Integer.toBinaryString(max)); System.out.println(Integer.toBinaryString(min));
4.11 BigDecimal類
表示精度更高的浮點型,在java.math.BigDecimal包下,該類可以進行更高精度的浮點運算。需要注意的是,BigDecimal可以描述比Double還要高的精度,所以在轉換為基本型別時,可能會丟失精度!
1)BigDecimal的使用:建立一個BigDecimal例項,可以使用構造方法BigDecimal(String numberFormatString)用字串描述一個浮點數作為引數傳入。
BigDecimal num1=new BigDecimal("3.0");
BigDecimal num2=new BigDecimal("2.9"); //運算結果依然為BigDecimal表示的結果
BigDecimal result=num1.subtract(num2);//num1-num2 System.out.println(result);
float f=result.floatValue();//將輸出結果轉換為基本型別float
int i=result.intValue();//將輸出結果轉換為基本型別int
2)BigDecimal可以作加add、減subtract、乘multiply、除divide等運算:這裡需要注意除法,由於除法存在結果為無限不迴圈小數,所以對於除法而言,我們要制定取捨模式,否則會一直計算下去,直到報錯(記憶體溢位)。
result=num1.divide(num2,8,BigDecimal.ROUND_HALF_UP);
//小數保留8位,捨去方式為四捨五入
4.12 BigInteger類
使用描述更長位數的整數“字串”,來表示、儲存更長位數的整數,在java.math.BigInteger包下。
1)BigInteger的使用:建立BigInteger
BigInteger num=new BigInteger("1");//num=new BigInteger(1);不可以,沒有這樣的構造器
//這種方式我們可以將一個整數的基本型別轉換為BigInteger的例項
num=BigInteger.valueOf(1);
2)理論上:BigInteger存放的整數位數只受記憶體容量影響。
3)BigInteger同樣支援加add、減subtract、乘multiply、除divide等運算。
eg:1-200的階乘
for(int i=1;i<=200;i++){ num=num.multiply(BigInteger.valueOf(i)); }
System.out.println("結果"+num.toString().length()+"位"); System.out.println(num);
4.13 Collection集合框架
在實際開發中,需要將使用的物件儲存於特定資料結構的容器中。而JDK提供了這樣的容器——集合框架,集合框架中包含了一系列不同資料結構(線性表、查詢表)的實現類。

1)Collection常用方法:
①int size():返回包含物件個數。 ②boolean isEmpty():返回是否為空。
③boolean contains(Object o):判斷是否包含指定物件。
④void clear():清空集合。 ⑤boolean add(E e):向集合中新增物件。
⑥boolean remove(Object o):從集合中刪除物件。
⑦boolean addAll(Collection<? extends E > c):另一個集合中的所有元素新增到集合
⑧boolean removeAll(Collection<?> c):刪除集合中與另外一個集合中相同的原素
⑨Iterator<E> iterator():返回該集合的對應的迭代器
2)Collection和Collentions的區別
Collection是java.util下的介面,它是各種集合的父介面,繼承於它的介面主要有Set 和List;Collections是個java.util下的類,是針對集合的幫助類,提供一系列靜態方法實現對各種集合的搜尋、排序、執行緒安全化等操作。
4.14 List集合的實現類ArrayList和LinkedList
List介面是Collection的子介面,用於定義線性表資料結構,元素可重複、有序的;可以將List理解為存放物件的陣列,只不過其元素個數可以動態的增加或減少。
1)List介面的兩個常見的實現類:ArrayList和LinkedList,分別用動態陣列和連結串列的方式實現了List介面。List、ArrayList和LinkedList均處於java.util包下。
2)可以認為ArrayList和LinkedList的方法在邏輯上完全一樣,只是在效能上有一定的差別,ArrayList更適合於隨機訪問,而LinkedList更適合於插入和刪除,在效能要求不是特別苛刻的情形下可以忽略這個差別。

ArrayList LinkedList
3)使用List我們不需要在建立的時候考慮容量集合的容量是根據其所儲存的元素決定的換句話說,集合的容量是可以自動擴充的。
4)List的實現類會重寫toString方法,依次呼叫所包含物件的toString方法,返回集合中所包含物件的字串表現。
5)常用方法:
①add(Object obj):向想集合末尾追加一個新元素,從該方法的引數定義不難看出,集合可以存放任意型別的元素,但在實際程式設計中我們發現,幾乎不會向集合中存放一種以上的不同型別的元素。
②size()方法:返回當前集合中存放物件的數量。
③clear()方法:用於清空集合。
④isEmpty()方法:用於返回集合是否為空。
List list=new ArrayList(); list.add("One"); list.add("Two"); list.add("Three");
//list.add(1);//不建議這樣的操作!儘量不在同一個集合中存放不用型別元素
System.out.println("集合中元素的數量:"+list.size());
System.out.println(list);//System.out.println(list.toString());
//ArrayList重寫了toString()方法返回的字串是每個元素的toString()返回值的序列
list.clear();//清空 System.out.println("清空後元素的數量:"+list.size());
System.out.println("集合是否為空?:"+list.isEmpty());
⑤contains(Object obj)方法:檢查給定物件是否被包含在集合中,檢查規則是將obj物件與集合中每個元素進行equals比較,若比對了所有元素均沒有equals為true的則返回false。
 注意事項:根據情況重寫equals:若比較是否是同一個物件,則不需要重寫,直接用contains裡的equals比較即可。若重寫equals為內容是否相同,則按內容比較,不管是否同一個物件。是否重寫元素的equals方法對集合的操作結果有很大的效果不同!
⑥boolean remove(Object obj)方法:刪除一個元素,不重寫equals,不會有元素被刪除(因為比較的是物件的地址,都不相同),重寫equals為按內容比較,則刪除第一個匹配的就退出,其他即使內容相同也不會被刪除。
List list=new ArrayList();//多型的寫法 //ArrayList arrayList=new ArrayList();//正常的寫法
list.add(new Point(1,2)); //Point point =new Point(1,2); list.add(point);等量代換
list.add(new Point(3,4)); list.add(new Point(5,6));
System.out.println("集合中元素的數量:"+list.size()); System.out.println(list);
Point p=new Point(1,2);//建立一個Point物件
System.out.println("p在集合中存在麼?"+list.contains(p));//不重寫為false 重寫為true
System.out.println("刪前元素:"+list.size());
list.remove(p);//將p物件刪除,不重寫equals,不會有元素被刪除
System.out.println("刪後元素:"+list.size()); System.out.println(list);
⑦E remove(int index)方法:移除此列表中指定位置上的元素。向左移動所有後續元素(將其索引減1)。因此在做刪除操作時集合的大小為動態變化的,為了防止漏刪,必須從後往前刪!
ArrayList list=new ArrayList(); list.add("java"); list.add("aaa");
list.add("java"); list.add("java"); list.add("bbb");
//相鄰的元素刪不掉!
for(int i=0;i<list.size();i++){ if( "java".equals(list.get(i))); list.remove(i); }
//可以刪乾淨!
for(int i=list.size()-1;i>=0;i--){ if("java".equals(list.get(i))){ list.remove(i); }
⑧addAll(Collection c)方法:允許將c對應的集合中所有元素存入該集合,即並集。注意,這裡的引數為Collection,所以換句話說,任何集合型別都可以將其元素存入其他集合中!
⑨removeAll(Collection c)方法:刪除與另一個集合中相同的元素。它的“相同”邏輯通過equals方法來判斷。
⑩retainAll(Collection c)方法:保留與另一個集合中相同的元素,即交集。它的“相同”邏輯通過equals方法來判斷。
list1.addAll(list2);//並集
list1.removeAll(list3);//從list1中刪除list3中相同(equals為true)的元素
list1.retainAll(list2);//保留list1中刪除list2中相同(equals為true)的元素
⑪Object get(int index)方法:根據元素下標獲取對應位置的元素並返回,這裡元素的下標和陣列相似。
⑫Object set(int index,Object newElement)方法:將index位置的元素修改為newElement修改後會將被修改的元素返回。因此,可實現將List中第i個和第j個元素交換的功能:list.set ( i , list.set ( j , list.get ( i ) ) ) ;
⑬add(int index,Object newElement)方法:使用add的過載方法,我們可以向index指定位置插入newElement,原位置的元素自動向後移動,即所謂的“插隊”。
⑭Object remove(int index)方法:將集合中下標為index的元素刪除,並將被刪除的元素返回(不根據equals,根據下標刪除元素)。
List list=new ArrayList(); list.add("One"); list.add("Two"); list.add("Three");
//因為get方法是以Object型別返回的元素,所以需要造型,預設泛型Object
String element=(String)list.get(2);//獲取第三個元素 System.out.println(element);
for(int i=0;i<list.size();i++){//遍歷集合 System.out.println(list.get(i)); }
Object old=list.set(2, "三");
System.out.println("被替換的元素:"+old); System.out.println(list);
list.add(2, "二");//在Two與“三”之間插入一個“二” System.out.println(list);
Object obj=list.remove(1); System.out.println("被刪除的元素:"+obj);
⑮indexOf(Object obj)方法:用於在集合中檢索物件,返回物件第一次出現的下標。
⑯lastIndexOf(Object obj)方法:用於在集合中檢索物件,返回物件最後一次出現的下標。
⑰Object[] toArray()方法:該方法繼承自Collection的方法,該方法會將集合以物件陣列的形式返回。
⑱toArray()的過載方法,T[] toArray(T[] a):可以很方便的讓我們轉換出實際的陣列型別,如下例,引數new Point[0]的作用是作為返回值陣列的型別,所以引數傳入的陣列不需要有任何長度,因為用不到,就沒有必要浪費空間。
Object[] array=list.toArray();//將集合以物件陣列的形式返回
for(int i=0;i<array.length;i++){ Point p=(Point)array[i]; System.out.println(p.getX()); }
Point[] array1=(Point[])list.toArray(new Point[0]);//toArray()的過載方法
for(int i=0;i<array1.length;i++){ Point p=array1[i];//不需要每次都強轉了
System.out.println(p.getX()); }
⑲List<E> subList(int fromIndex, int toIndex)方法:獲取子集合,但在獲取子集後,若對子集合的元素進行修改,則會影響原來的集合。
List<Integer> list=new ArrayList<Integer>();
for(int i=0;i<10;i++){ list.add(i); }
List<Integer> subList=list.subList(3, 8);//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 取子集(3-7)
System.out.println(subList);//[3, 4, 5, 6, 7]
//我們在獲取子集後,若對自己元素進行修改,會影響原來的集合
for(int i=0;i<subList.size();i++){ int element=subList.get(i);
element*=10; subList.set(i, element);
//subList.set(i, subList.get(i)*10);同上三步 }
System.out.println(list);//原集合內容也被修改了
⑳Comparable介面:針對物件陣列或者集合中的元素進行排序時,首選需要確定物件元素的“比較”邏輯(即哪個大哪個小)。Comparable介面用於表示物件間的大小關係,我們需要實現Comparable介面,並重寫compareTo()方法定義比較規則。
public int compareTo(ComparablePoint o){ int r=x*x+y*y;//自身點到原點的距離
int other=o.x*o.x+o.y*o.y;//引數點到原點的距離
//返回結果大於0,自身比引數大;小於0,自身比引數小;等於0,自身和引數相等; //equals返回true的時候,comparTo的返回值應該為0
return r-other; }
○21Collections.sort()方法:需要集合中的物件實現Comparable介面,從而可以呼叫其compareTo方法判斷物件的大小,否則sort將無法判斷。該方法會依次呼叫集合中每個元素的compareTo方法,並進行自然排序(從小到大)。
List<ComparablePoint> list=new ArrayList<ComparablePoint>();
list.add(new ComparablePoint(1,5)); list.add(new ComparablePoint(3,4));
list.add(new ComparablePoint(2,2)); System.out.println(list);//輸出順序與存方時一致
Collections.sort(list); System.out.println(list);
○22Comparator介面:比較器。一旦Java類實現了Comparable,其比較邏輯就已經確定了,如果希望在排序中的操作按照“臨時指定規則”,即自定義比較規則。可以採用Comparator介面回撥方式。
Comparator比較器建立步驟:A.定義一個類並實現Comparator介面。B.實現介面中的抽象方法compare(E o1,E o2)。C.例項化這個比較器D.呼叫Collections的過載方法:sort(Collection c,Comparator comparator)進行排序。通常使用匿名類方式建立一個例項來定義比較器。
Comparator<ComparablePoint> c=new Comparator<ComparablePoint>(){
public int compare(ComparablePoint o1,ComparablePoint o2){
return o1.getX()-o2.getX();//兩個點的X值大的大 } };
Collections.sort(list, c); System.out.println(list);
4.15 Iterator迭代器
所有Collection的實現類都實現了iterator方法,該方法返回一個Iterator介面型別的物件,用於實現對集合元素迭代的便利。在java.util包下。
1)Iterator定義有三個方法:
①boolean hasNext()方法:判斷指標後面是否有元素。
②E next()方法:指標後移,並返回當前元素。E代表泛型,預設為Object型別。
③void remove()方法:在原集合中刪除剛剛返回的元素。
2)對於List集合而言,可以通過基於下標的get方法進行遍歷;而iterator方法是針對Collection介面設計的,所以,所有實現了Collection介面的類,都可以使用Iterator實現迭代遍歷。
3)迭代器的使用方式:先問後拿。問:boolean hasNext()該方法詢問迭代器當前集合是否還有元素;拿:E next()該方法會獲取當前元素。迭代器的迭代方法是while迴圈量身定製的。
List list=new ArrayList(); list.add("One"); list.add("#");
Iterator it=list.iterator();
while(it.hasNext()){//集合中是否還有下一個元素
Object element=it.next();//有就將其取出
System.out.println(element); }
4)迭代器中的刪除問題:在迭代器迭代的過程中,我們不能通過“集合”的增刪等操作,來改變該集合的元素數量!否則會引發迭代異常!若想刪除迭代出來的元素,只能通過Iterator。迭代器在使用自己的remove()方法時,可以將剛剛獲取的元素從集合中刪除,但是不能重複呼叫兩次!即在不迭代的情況下,不能在一個位置刪兩次。
while(it.hasNext()){//集合中是否還有下一個元素
String element=(String)it.next();//有就將其取出,next返回值為E(泛型)預設為Object所以需要強轉
if("#".equals(element)){ //list.remove(element);不可以!
it.remove();//刪除當前位置元素 } }
4.16泛型
1)泛型是 JDK1.5引入的新特性,泛型的本質是引數化型別。在類、介面、方法的定義過程中,所操作的資料型別為傳入的指定引數型別。所有的集合型別都帶有泛型引數,這樣在建立集合時可以指定放入集合中的物件型別。同時,編譯器會以此型別進行檢查。
2)ArrayList支援泛型,泛型尖括號裡的符號可隨便些,但通常大寫E。
3)迭代器也支援泛型,但是迭代器使用的泛型應該和它所迭代的集合的泛型型別一致!
4)泛型只支援引用型別,不支援基本型別,但可以使用對應的包裝類
5)如果泛型不指定型別的話,預設為Object型別。
ArrayList<Point> list=new ArrayList<Point>();
list.add(new Point(1,2)); list.add(new Point(3,4));
//list.add("哈哈");//定義泛型後,只執行Point型別,否則造型異常
for(int i=0;i<list.size();i++){ Point p=/*(Point)也不需要強轉造型了*/list.get(i);
System.out.println(p.getX()); }
Iterator<Point> it=list.iterator(); while(it.hasNext()){ Point p=it.next();
//也不需要強轉了 System.out.println(p); }
6)自定義泛型
Point p=new Point(1,2);//只能儲存整數
//把Point類的int都改成泛型E,或者也可設定多個泛型Point<E,Z>
Point<Double> p1=new Point<Double>(1.0,2.3);//設定一個泛型
Point<Double,Long> p2=new Point<Double,Long>(2.3,3L);//設定多個泛型
4.17增強型for迴圈
JDK在1.5版本推出了增強型for迴圈,可以用於陣列和集合的遍歷。
 注意事項:集合中要有值,否則直接退出(不執行迴圈)。
1)老迴圈:自己維護迴圈次數, 迴圈體自行維護獲取元素的方法。
int[] array=new int[]{1,2,3,4,5,6,7};
for(int i=0;i<array.length;i++){//維護迴圈次數
int element=array[i];//獲取陣列元素 System.out.print(element); }
2)新迴圈:自動維護迴圈次數(由遍歷的陣列或集合的長度決定),自動獲取每次迭代的元素。
int[] array=new int[]{1,2,3,4,5,6,7};
for(int element:array){ System.out.print(element); }
3)新迴圈執行流程:遍歷陣列array中的每個元素,將元素一次賦值給element後進入迴圈體,直到所有元素均被迭代完畢後退出迴圈。
 注意事項:使用新迴圈,element的型別應與迴圈迭代的陣列或集合中的元素型別一致!至少要是相容型別!
4)新迴圈的內部實現是使用迭代器完成的Iterator。
5)使用新迴圈遍歷集合:集合若使用新迴圈,應該為其定義泛型,否則我們只能使用Object作為被接收元素時的型別。通常情況下,集合都要加泛型,要明確集合中的型別,集合預設是Object。
ArrayList<String> list=new ArrayList<String>();
list.add("張三"); list.add("李四"); list.add("王五");
for(String str:list){//預設是Object自己加泛型String System.out.println(str); }
4.18 List高階-資料結構:Queue佇列
佇列(Queue)是常用的資料結構,可以將佇列看成特殊的線性表,佇列限制了對線性表的訪問方式:只能從線性表的一端新增(offer)元素,從另一端取出(poll)元素。Queue介面:在包java.util.Queue。
1)佇列遵循先進先出原則:FIFO(First Input First Output)佇列不支援插隊,插隊是不道德的。
2)JDK中提供了Queue介面,同時使得LinkedList實現了該介面(選擇LinkedList實現Queue的原因在於Queue經常要進行插入和刪除的操作,而LinkedList在這方面效率較高)。


poll offer
3)常用方法:
①boolean offer(E e):將一個物件新增至隊尾,如果新增成功則返回true。
②poll():從佇列中取出元素,取得的是最早的offer元素,從佇列中取出元素後,該元素會從佇列中刪除。若方法返回null說明 佇列中沒有元素了。
③peek():獲取隊首的元素(不刪除該元素!)
eg:佇列相關操作
Queue<String> queue=new LinkedList<String>();
queue.offer("A"); queue.offer("B"); queue.offer("C");
System.out.println(queue);//[A, B, C]
System.out.println("隊首:"+queue.peek());//獲取隊首元素,但不令其出隊
String element=null; while((element=queue.poll())!=null){
System.out.println(element); }
4.19 List高階-資料結構:Deque棧
棧(Deque)是常用的資料結構,是Queue佇列的子介面,因此LinkedList也實現了Deque介面。棧將雙端佇列限制為只能從一端入隊和出隊,對棧而言即是入棧和出棧。子彈夾就是一種棧結構。在包java.util.Deque下。
1)棧遵循先進後出的原則:FILO(First Input Last Output)。
2)常用方法:
①push:壓入,向棧中存入資料。
②pop:彈出,從棧中取出資料。
③peek:獲取棧頂位置的元素,但不取出。
 注意事項:我們在使用pop獲取棧頂元素之前,應現使用peek方法獲取該元素,確定該元素不為null的情況下才應該將該元素從棧中彈出”,否則若棧中沒有元素後,我們呼叫pop會丟擲異常“NoSuchElementException”。
eg:棧相關操作
Deque<Character> deque=new LinkedList<Character>();
for(int i=0;i<5;i++){ deque.push((char)('A'+i)); }
System.out.println(deque);
//注意使用peek判斷棧頂是否有元素
while(deque.peek()!=null){ System.out.print(deque.pop()+" "); }
4.20 Set集合的實現類HashSet
Set是無序,用於儲存不重複的物件集合。在Set集合中儲存的物件中,不存在兩個物件equals比較為true的情況。
1)HashSet和TreeSet是Set集合的兩個常見的實現類,分別用hash表和排序二叉樹的方式實現了Set集合。HashSet是使用雜湊演算法實現Set的。
2)Set集合沒有get(int index)方法,我們不能像使用List那樣,根據下標獲取元素。想獲取元素需要使用Iterator。
3)向集合新增元素也使用add方法,但是add方法不是向集合末尾追加元素,因為無序。
4)巨集觀上講:元素的順序和存放順序是不同的,但是在內容不變的前提下,存放順序是相同的,但在我們使用的時候,要當作是無序的使用。
Set<String> set=new HashSet<String>();//多型
//也可HashSet<String> set=new HashSet<String>();
set.add("One"); set.add("Two"); set.add("Three"); Iterator<String> it=set.iterator();
while(it.hasNext()){ String element=it.next(); System.out.print(element+" "); }
for(String element:set){ System.out.print(element+" "); }//新迴圈遍歷Set集合
5)hashCode對HashSet的影響:若我們不重寫hashCode,那麼使用的就是Object提供的,而該方法是返回地址(控制程式碼)!換句話說,就是不同的物件,hashCode不同。
6)對於重寫了equals方法的物件,強烈要求重寫繼承自Object類的hashCode方法的,因為重寫hashCode方法與否會對集合操作有影響!
7)重寫hashCode方法需要注意兩點:
①與equals方法的一致性,即equals比較返回為true的物件其hashCode方法返回值應該相同。
②hashCode返回的數值應該符合hash演算法要求,如果有很多物件的hashCode方法返回值都相同,則會大大降低hash表的效率。一般情況下,可以使用IDE(如Eclipse)提供的工具自動生成hashCode方法。
8)boolean contains(Object o)方法:檢視物件是否在set中被包含。下例雖然有新建立的物件,但是通過雜湊演算法找到了位置後,和裡面存放的元素進行equals比較為true,所以依然認為是被包含的(重寫equals了時)。
Set<Point> set=new HashSet<Point>(); set.add(new Point(1,2));
set.add(new Point(3,4)); System.out.println(set.contains(new Point(1,2)));
9)HashCode方法和equals方法都重寫時對hashSet的影響:將兩個物件同時放入HashSet集合,發現存在,不再放入(不重複集)。當我們重寫了Point的equals方法和hashCode方法後,我們發現雖然p1和p2是兩個物件,但是當我們將他們同時放入集合時,p2物件並沒有被新增進集合。因為p1在放入後,p2放入時根據p2的hashCode計算的位置相同,且p2與該位置的p1的equals比較為true, hashSet認為該物件已經存在,所以拒絕將p2存入集合。
Set<Point> set=new HashSet<Point>();
Point p1=new Point(1,2); Point p2=new Point(1,2);
System.out.println("兩者是否同一物件:"+(p1==p2));
System.out.println("兩者內容是否一樣:"+p1.equals(p2));
System.out.println("兩者HashCode是否一樣:"+ (p1.hashCode()==p2.hashCode()));
set.add(p1); set.add(p2); System.out.println("hashset集合的元素數"+set.size());
for(Point p:set){ System.out.println(p); }
10)不重寫hashCode方法,但是重寫了equals方法對hashSet的影響:兩個物件都可以放入HashStet集合中,因為兩個物件具有不用的hashCode值,那麼當他們在放入集合時,通過hashCode值進行的雜湊演算法結果就不同。那麼他們會被放入集合的不同位置,位置不相同,HashSet則認為它們不同,所以他們可以全部被放入集合。
11)重寫了hashCode方法,但是不重寫equals方法對hashSet的影響:在hashCode相同的情況下,在存放元素時,他們會在相同的位置,hashSet會在相同位置上將後放入的物件與該位置其他物件一次進行equals比較,若不相同,則將其存入在同一個位置存入若干元素,這些元素會被放入一個連結串列中。由此可以看出,我們應該儘量使得多種類的不同物件的hashcode值不同,這樣才可以提高HashSet在檢索元素時的效率,否則可能檢索效率還不如List。
12)結論:不同物件存放時,不會儲存hashCode相同並且equals相同的物件,缺一不可。否則HashSet不認為他們是重複物件。
4.21 Map集合的實現類HashMap
Map介面定義的集合又稱為查詢表,用於儲存所謂“Key-Value”鍵值對。Key可以看成是Value的索引。而往往Key是Value的一部分內容。
1)Key不可以重複,但所儲存的Value可以重複。
2)根據內部結構的不同,Map介面有多種實現類,其中常用的有內部為hash表實現的HashMap和內部為排序二叉樹實現的TreeMap。同樣這樣的資料結構在存放資料時,也不建議存放兩種以上的資料型別,所以,通常我們在使用Map時也要使用泛型約束儲存內容的型別。
3)建立Map時使用泛型,這裡要約束兩個型別,一個是key的型別,一個是value的型別。
4)基本原理圖:

5)HashMap集合中常用的方法:
①V put(K Key,V value):將元素以Key-Value的形式放入map。若重複儲存相同的key時,實際的操作是替換Key所對應的value值。
②V get(Object key):返回key所對應的value值。如果不存在則返回null。
③boolean containsKey(Object Key):判斷集合中是否包含指定的Key。
④boolean containsValue(Object value):判斷集合中是否包含指定的Value。
6)若給定的key在map中不存在則返回null,所以,原則上在從map中獲取元素時要先判斷是否有該元素,之後再使用,避免空指標異常的出現。Map在獲取元素時非常有針對性,集合想獲取元素需要遍歷集合內容,而Map不需要,你只要給他特定的key就可以獲取該元素。
Map<String,Point> map=new HashMap<String,Point>();
map.put("1,2", new Point(1,2)); map.put("3,4", new Point(3,4));
Point p=map.get("1,2"); System.out.println("x="+p.getX()+",y="+p.getY());
map.put("1,2", new Point(5,6));//會替換之前的
p=map.get("1,2"); System.out.println("x="+p.getX()+",y="+p.getY());
p=map.get("haha"); System.out.println("x="+p.getX()+",y="+p.getY());//會報空指異常
eg:統計每個數字出現的次數。步驟:①將字串str根據“,”拆分。②建立map。③迴圈拆分後的字串陣列。④蔣梅一個數字作為key在map中檢查是否包含。⑤包含則對value值累加1。⑥不包含則使用該數字作為key,value為1存入map。
String str="123,456,789,456,789,225,698,759,456";
String[] array=str.split(",");
Map<String,Integer> map=new HashMap<String,Integer>();
for(String number:array){ if(map.containsKey(number)){
int sum=map.get(number);//將原來統計的數字取出 sum++; //對統計數字加1
map.put(number, sum); //放回 map.put(number, map.get(number)+1);等同上三部
}else{ map.put(number, 1);//第一次出現value為1 } }
System.out.println(map);//HashMap也重寫了toString()
7)計算機中有這麼一句話:越靈活的程式效能越差,顧及的多了。
8)遍歷HashMap方式一:獲取所有的key並根據key獲取value從而達到遍歷的效果(即迭代Key)。keySet()方法:是HashMap獲取所有key的方法,該方法可以獲取儲存在map下所有的key並以Set集合的形式返回。
Map<String,Point> map=new HashMap<String,Point>();
map.put("1,2", new Point(1,2)); map.put("2,3", new Point(2,3));
map.put("3,4", new Point(3,4)); map.put("4,5", new Point(4,5));
/** 因為key在HashMap的泛型中規定了型別為String,所以返回的Set中的元素也是String,為了更好的使用,我們在定義Set型別變數時也應該加上泛型 */
Set<String> keyset=map.keySet();
for(String key:keyset){ Point p=map.get(key);//根據key獲取value
System.out.println(key+":"+p.getX()+","+p.getY()); }
for(Iterator<String> it=keyset.iterator() ; it.hasNext() ; ){//普通for迴圈
String key=it.next(); Point p=map.get(key);
System.out.println(key+":"+p.getX()+","+p.getY()); }
9)LinkedHashMap:用法和HashMap相同,內部維護著一個連結串列,可以使其存放元素時的順序與迭代時一致。
10)Entry類,遍歷HashMap方式二:以“鍵值對”的形式迭代。Map支援另一個方法entrySet():該方法返回一個Set集合,裡面的元素是map中的每一組鍵值對,Map以Entry類的例項來描述每一個鍵值對。其有兩個方法:getKey()獲取key值;getValue()獲取value值。Entry也需要泛型的約束,其約束的泛型應該和Map相同!Entry所在位置:java.util.Map.Entry。
Map<String,Point> map=new LinkedHashMap<String,Point>();
map.put("1,2", new Point(1,2)); map.put("2,3", new Point(2,3));
map.put("3,4", new Point(3,4)); map.put("4,5", new Point(4,5)); //泛型套泛型
Set<Entry<String,Point>> entrySet=map.entrySet();//Set的泛型不會變,就是Entry
for(Entry<String,Point> entry:entrySet){
String key=entry.getKey();//獲取key Point p=entry.getValue();//獲取value
System.out.println(key+","+p.getX()+","+p.getY()); }
11)List、Map、Set三個介面儲存元素時各有什麼特點:
①List:是有序的Collection,使用此介面能夠精確的控制每個元素插入的位置。使用者能夠使用索引(元素在List中的位置,類似於陣列下標)來訪問List中的元素,這類似於Java的陣列。
②Set:是一種不包含重複的元素的Collection,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。
③Map:請注意,Map沒有繼承Collection介面,Map提供key到value的對映。
4.22單例模式和模版方法模式
設計模式是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。
1)使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性。簡單的說:設計模式是經典問題的模式化解決方法。
2)經典設計模式分為三種型別,共23類。
建立模型式:單例模式、工廠模式等
結構型模式:裝飾模式、代理模式等
行為型模式:模版方法模式、迭代器模式等
3)單例設計模式:意圖:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。適用性:當類只能有一個例項而且客戶可以從一個眾所周知的訪問點訪問它。任何情況下,該類只能建立一個例項!
4)單例設計模式建立步驟:①定義一個私有的靜態的當前型別的屬性。②私有化構造方法。③定義一個靜態的可以獲取當前類例項的方法。這個方法中我們可以判斷是否建立過例項,建立過就直接返回,從而達到單例的效果。
private static DemoSingleton obj;
//或private static DemoSingleton obj=new DemoSingleton();
private DemoSingleton(){ }
public static DemoSingleton getInstance(){
if(obj==null){ obj= new DemoSingleton(); }
return obj; }
5)模版方法模式:意圖:定義一個操作中的演算法過程的框架,而將一些步驟延遲到子類中實現。類似於定義介面或抽象類,子類去實現抽象方法。

五、Java SE核心II
5.1 Java異常處理機制
異常結構中的父類Throwable類,其下子類Exceptionlei類和Error類。我們在程式中可以捕獲的是Exception的子類異常。
Error系統級別的錯誤:Java執行時環境出現的錯誤,我們不可控。
Exception是程式級別的錯誤:我們可控。
1)異常處理語句:try-catch,如果try塊捕獲到異常,則到catch塊中處理,否則跳過忽略catch塊(開發中,一定有解決的辦法才寫,無法解決就向上拋throws)。
try{//關鍵字,只能有一個try語句
可能發生異常的程式碼片段
}catch(Exception e){//列舉程式碼中可能出現的異常型別,可有多個catch語句
當出現了列舉的異常型別後,在這裡處理,並有針對性的處理
}
2)良好的程式設計習慣,在異常捕獲機制的最後書寫catch(Exception e)(父類,頂極異常)捕獲未知的錯誤(或不需要針對處理的錯誤)。
3)catch的捕獲是由上至下的,所以不要把父類異常寫在子類異常的上面,否則子類異常永遠沒有機會處理!在catch塊中可以使用方法獲取異常資訊:
①getMessage()方法:用來得到有關異常事件的資訊。
②printStackTrace()方法:用來跟蹤異常事件發生時執行堆疊的內容。
4)throw關鍵字:用於主動丟擲一個異常
當我們的方法出現錯誤時(不一定是真實異常),這個錯誤我們不應該去解決,而是通知呼叫方法去解決時,會將這個錯誤告知外界,而告知外界的方式就是throw異常(丟擲異常)catch語句中也可丟擲異常。雖然不解決,但要捕獲,然後丟擲去。
使用環境:
我們常在方法中主動丟擲異常,但不是什麼情況下我們都應該丟擲異常。原則上,自身決定不了的應該丟擲。那麼方法中什麼時候該自己處理異常什麼時候丟擲?
方法通常有引數,呼叫者在呼叫我們的方法幫助解決問題時,通常會傳入引數,若我們方法的邏輯是因為引數的錯誤而引發的異常,應該丟擲,若是我們自身的原因應該自己處理。
public static void main(String[] args) {
try{/**通常我們呼叫方法時需要傳入引數的話,那麼這些方法,JVM都不會自動處理異常,而是將錯誤拋給我們解決*/
String result=getGirlFirend("女神"); System.out.println("追到女神了麼?"+result);
}catch(Exception e){
System.out.println("沒追到");//我們應該在這裡捕獲異常並處理。
}
}
public static String getGirlFirend(String name){
try{ if("春哥".equals(name)){ return "行";
}else if("曾哥".equals(name)){ return "行";
}else if("我女朋友".equals(name)){ return "不行";
}else{/**當出現了錯誤(不一定是真實異常)可以主動向外界丟擲一個異常!*/
throw new RuntimeException("人家不幹!");
}
}catch(NullPointerException e){
throw e;//出了錯不解決,拋給呼叫者解決
}
}
5)throws關鍵字:不希望直接在某個方法中處理異常,而是希望呼叫者統一處理該異常。宣告方法的時候,我們可以同時宣告可能丟擲的異常種類,通知呼叫者強制捕獲。就是所謂的“醜話說前面”。原則上throws宣告的異常,一定要在該方法中丟擲。否則沒有意義。相反的,若方法中我們主動通過throw丟擲一個異常,應該在throws中宣告該種類異常,通知外界捕獲。
 注意事項:
 注意throw和throws關鍵字的區別:丟擲異常和宣告丟擲異常。
 不能在main方法上throws,因為呼叫者JVM直接關閉程式。
public static void main(String[] args) {
try{ Date today=stringToDate("2013-05-20"); } catch (ParseException e){
//catch中必須含有有效的捕獲stringToDate方法throws的異常
// 輸出這次錯誤的棧資訊可以直觀的檢視方法呼叫過程和出錯的根源
e.printStackTrace(); } }
eg:將一個字串轉換為一個Date物件,丟擲的異常是字元格式錯誤java.text.ParseException
public static Date stringToDate(String str) throws ParseException{
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-DD");
Date date=format.parse(str); return date; }
6)捕獲異常兩種方式:上例SimpleDataFormat的parse方法在宣告的時候就是用了throws,強制我們呼叫parse方法時必須捕獲ParseException,我們的做法有兩種:一是新增try-catch捕獲該異常,二是在我們的方法中宣告出也追加這種異常的丟擲(繼續往外拋)。
7)java中丟擲異常過程:java虛擬機器在執行程式時,一但在某行程式碼執行時出現了錯誤,JVM會建立這個錯誤的例項,並丟擲。這時JVM會檢查出錯程式碼所在的方法是否有try捕獲,若有,則檢查catch塊是否有可以處理該異常的能力(看能否把異常例項作為引數傳進去,看有沒有匹配的異常型別)。若沒有,則將該異常拋給該方法的呼叫者(向上拋)。以此類推,直到拋至main方法外仍沒有解決(即拋給了JVM處理)。那麼JVM會終止該程式。
8)java中的異常Exception分為:
①非檢測異常(RuntimeException子類):編譯時不檢查異常。若方法中丟擲該類異常或其子類,那麼宣告方法時可以不在throws中列舉該類丟擲的異常。常見的執行時異常有: NullPointerException、 IllegalArgumentException、
ClassCastException、NumberFormatException、
ArrayIndexOutOfBoundsException、ArithmeticException
②可檢測異常(非RuntimeException子類):編譯時檢查,除了執行時異常之外的異常,都是可檢查異常,則必須在宣告方法時用throws宣告出可能丟擲的異常種類!
9)finally塊:finally塊定義在catch塊的最後(所有catch最後),且只能出現一次(0-1次), 無論程式是否出錯都會執行的塊! 無條件執行!通常在finally語句中進行資源的消除工作,如關閉開啟的檔案,刪除臨時檔案等。

public static void main(String[] args) {
System.out.println( test(null)+","+test("0")+","+test("") ); }
/**輸出結果?1,0,2 ? 4,4,4為正確結果 */
public static int test(String str){
try{ return str.charAt(0)-'0';
}catch(NullPointerException e){ return 1;
}catch(RuntimeException e){ return 2;
}catch(Exception e){ return 3;
}finally{//無條件執行 return 4; } }
10)重寫方法時的異常處理
如果使用繼承時,在父類別的某個地方上宣告了throws某些異常,而在子類別中重新定義該方法時,可以:①不處理異常(重新定義時不設定throws)。②可僅throws父類別中被重新定義的方法上的某些異常(丟擲一個或幾個)。③可throws被重新定義的方法上的異常之子類別(丟擲異常的子類)。
但不可以:①throws出額外的異常。 ②throws被重新定義的方法上的異常之父類別(丟擲了異常的父類)。
5.2 File檔案類
java使用File類(java.io.File)表示作業系統上檔案系統中的檔案或目錄。換句話說,我們可以使用File操作硬碟上的檔案或目錄進行建立或刪除。
File可以描述檔案或目錄的名字,大小等資訊,但不能對檔案的內容操作!File類的構造器都是有參的。
1)關於路徑的描述:不同的檔案系統差異較大,Linux和Windows就不同!最好使用相對路徑,不要用絕對路徑。
2)“.”代表的路徑:當前目錄(專案所處的目錄),在eclipse_workspace/project_name下,File.separator:常量,目錄分隔符,推薦使用!根據系統自動識別用哪種分割符,windows中為/,Linux中為\。
3)建立該物件並不意味著硬碟上對應路徑上就有該檔案了,只是在記憶體中建立了該物件去代表路徑指定的檔案。當然這個路徑對應的檔案可能根本不存在!
   File file=new File("."+File.separator+"data.dat");// 效果為./data.dat
//File file=new File("e:/XX/XXX.txt");不建議使用
4)createNewFile()中有throws宣告,要求強制捕獲異常!
5)新建檔案或目錄:
①boolean mkdir():只能在已有的目錄基礎上建立目錄。
②boolean mkdirs():會建立所有必要的父目錄(不存在的自動建立)並建立該目錄。
③boolean createNewFile():建立一個空的新檔案。
6)建立目錄中檔案的兩種方式:
①直接指定data.dat需要建立的位置,並呼叫createNewFile(),前提是目錄都要存在!
②先建立一個File例項指定data.dat即將存放的目錄,若該目錄不存在,則建立所有不存在的目錄,再建立一個File例項,代表data.dat檔案,建立是基於上一個代表目錄的File例項的。使用File(File dir,String fileName)構造方法建立File例項,然後再呼叫createNewFile():在dir所代表的目錄中表示fileName指定的檔案
File dir=new File("."+File.separator+"demo"+File.separator+"A");
if(!dir.exists()){ dir.mkdirs();//不存在則建立所有必須的父目錄和當親目錄 }
File file=new File(dir,"data.dat");
if(!file.exists()){file.createNewFile();System.out.println("檔案建立完畢!"); }
7)檢視檔案或目錄屬性常用方法
①long length():返回檔案的長度。
②long lastModified():返回檔案最後一次被修改的時間。
③String getName():返回檔案或目錄名。 ⑧String getPath():返回路徑字串。
④boolean exists():是否存在。 ⑨boolean isFile():是否是標準檔案。
⑤boolean isDirectory():是否是目錄。 ⑩boolean canRead():是否可以讀取。
⑥boolean canWrite():是否可以寫入、修改。
⑦File[] listFiles():獲取當親目錄的子項(檔案或目錄)
eg1:File類相關操作
File dir=new File("."); if(dir.exists()&&dir.isDirectory()){//是否為一個目錄
File[] files=dir.listFiles();//獲取當前目錄的子項(檔案或目錄)
for(File file:files){//迴圈子項
if(file.isFile()){//若這個子項是一個檔案
System.out.println("檔案:"+file.getName());
}else{ System.out.println("目錄:"+file.getName()); } } }
eg2:遞迴遍歷出所有子項
File dir=new File("."); File[] files=dir.listFiles(); if(files!=null&&files.length>0){//判斷子項陣列有項
for(File file:files){//遍歷該目錄下的所有子項
if(file.isDirectory()){//若子項是目錄
listDirectory(file);//不到萬不得已,不要使用遞迴,非常消耗資源
}else{System.out.println("檔案:"+file);//有路徑顯示,輸出File的toString()
//file.getName()無路徑顯示,只獲取檔名 } } }
8)刪除一個檔案:boolean delete():①直接寫檔名作為路徑和"./data.dat"代表相同檔案,也可直接寫目錄名,但要注意第2條。②刪除目錄時:要確保該目錄下沒有任何子項後才可以將該目錄刪除,否則刪除失敗!
File dir=new File("."); File[] files=dir.listFiles();
if(files!=null&&files.length>0){ for(File file:files){ if(file.isDirectory()){
deleteDirectory(file);//遞迴刪除子目錄下的所有子項 }else{
if(!file.delete()){ throw new IOException("無法刪除檔案:"+file); }
System.out.println("檔案:"+file+"已被刪除!"); } }
9)FileFilter:檔案過濾器。FileFilter是一個介面,不可例項化,可以規定過濾條件,在獲取某個目錄時可以通過給定的刪選條件來獲取滿足要求的子項。accept()方法是用來定義過濾條件的引數pathname是將被過濾的目錄中的每個子項一次傳入進行匹配,若我們認為該子項滿足條件則返回true。如下重寫accept方法。
FileFilter filter=new FileFilter(){
public boolean accept(File pathname){
return pathname.getName().endsWith(".java");//保留檔名以.java結尾的
//return pathname.length()>1700;按大小過濾 } };
File dir=new File(".");//建立一個目錄
File[] sub=dir.listFiles(filter);//獲取過濾器中滿足條件的子項,回撥模式
for(File file:sub){ System.out.println(file); }
10)回撥模式:我們定義一段邏輯,在呼叫其他方法時,將該邏輯通過引數傳入。這個方法在執行過程中會呼叫我們傳入的邏輯來達成目的。這種現象就是回撥模式。最常見的應用環境:按鈕監聽器,過濾器的應用。
5.3 RandomAccessFile類
可以方便的讀寫檔案內容,但只能一個位元組一個位元組(byte)的讀寫8位。
1)計算機的硬碟在儲存資料時都是byte by byte的,位元組埃著位元組。
2)RandomAccessFile開啟檔案模式:rw:開啟檔案後可進行讀寫操作;r:開啟檔案後只讀。
3)RandomAccessFile是基於指標進行讀寫操作的,指標在哪裡就從哪裡讀寫。
①void seek(long pos)方法:從檔案開頭到設定位置的指標偏移量,在該位置發生下一次讀寫操作。
②getFilePointer()方法:獲取指標當前位置,而seek(0)則將指標移動到檔案開始的位置。
③int skipBytes(int n)方法:嘗試跳過輸入的n個位元組。
4)RandomAccessFile類的構造器都是有參的。
①RandomAccessFile構造方法1:
RandomAccessFile raf=new RandomAccessFile(file,"rw");
②RandomAccessFile構造方法2:
RandomAccessFile raf=new RandomAccessFile("data.dat","rw");
直接根據檔案路徑指定,前提是確保其存在!
5)讀寫操作完了,不再寫了就關閉:close();
6)讀寫操作:
File file=new File("data.dat");//建立一個File物件用於描述該檔案
if(!file.exists()){//不存在則建立該檔案
file.createNewFile();//建立該檔案,應捕獲異常,僅為演示所以拋給main了 }
RandomAccessFile raf=new RandomAccessFile(file,"rw");//建立RandomAccessFile,並將File傳入,RandomAccessFile對File表示的檔案進行讀寫操作。
/**1位16進位制代表4位2進位制;2位16進位制代表一個位元組 8位2進位制;
* 4位元組代表32位2進位制;write(int) 寫一個位元組,且是從低8位寫*/
int i=0x7fffffff;//寫int值最高的8位 raf.write(i>>>24);//00 00 00 7f
raf.write(i>>>16);//00 00 7f ff raf.write(i>>>8);// 00 7f ff ff
raf.write(i);// 7f ff ff ff
byte[] data=new byte[]{0,1,2,3,4,5,6,7,8,9};//定義一個10位元組的陣列並全部寫入檔案
raf.write(data);//寫到這裡,當前檔案應該有14個位元組了
/**寫位元組陣列的過載方法:write(byte[] data.int offset,int length),從data陣列的offset位置開始寫,連續寫length個位元組到檔案中 */
raf.write(data, 2, 5);// {2,3,4,5,6}
System.out.println("當前指標的位置:"+raf.getFilePointer());
raf.seek(0);//將指標移動到檔案開始的位置
int num=0;//準備讀取的int值
int b=raf.read();//讀取第一個位元組 7f 也從低8位開始
num=num | (b<<24);//01111111 00000000 00000000 00000000
b=raf.read();//讀取第二個位元組 ff
num=num| (b<<16);//01111111 11111111 00000000 00000000
b=raf.read();//讀取第三個位元組 ff
num=num| (b<<8);//01111111 11111111 11111111 00000000
b=raf.read();//讀取第四個位元組 ff
num=num| b;//01111111 11111111 11111111 11111111
System.out.println("int最大值:"+num); raf.close();//寫完了不再寫了就關了
7)常用方法:
①write(int data):寫入第一個位元組,且是從低8位寫。
②write(byte[] data):將一組位元組寫入。
③write(byte[] data.int offset,int length):從data陣列的offset位置開始寫,連續寫length個位元組到檔案中。
④writeInt(int):一次寫4個位元組,寫int值。
⑤writeLong(long):一次寫8個位元組,寫long值。
⑥writeUTF(String):以UTF-8編碼將字串連續寫入檔案。
write……
①int read():讀一個位元組,若已經讀取到檔案末尾,則返回-1。
②int read(byte[] buf):嘗試讀取buf.length個位元組。並將讀取的位元組存入buf陣列。返回值為實際讀取的位元組數。
③int readInt():連續讀取4位元組,返回該int值
④long readLong():連續讀取8位元組,返回該long值
⑤String readUTF():以UTF-8編碼將字串連續讀出檔案,返回該字串值
read……
byte[] buf=new byte[1024];//1k容量 int sum=raf.read(buf);//嘗試讀取1k的資料
System.out.println("總共讀取了:"+sum+"個位元組");
System.out.println(Arrays.toString(buf)); raf.close();//寫完了不再寫了就關了
8)複製操作:讀取一個檔案,將這個檔案中的每一個位元組寫到另一個檔案中就完成了複製功能。
try { File srcFile=new File("chang.txt");
RandomAccessFile src=new RandomAccessFile(srcFile,"r");//建立一個用於讀取檔案的RandomAccessFile用於讀取被拷貝的檔案
File desFile=new File("chang_copy.txt"); desFile.createNewFile();//建立複製檔案
RandomAccessFile des=new RandomAccessFile(desFile,"rw");//建立一個用於寫入檔案的RandomAccessFile用於寫入拷貝的檔案
//使用位元組陣列作為緩衝,批量讀寫進行復制操作比一個位元組一個位元組讀寫效率高的多!
byte[] buff=new byte[1024*100];//100k 建立一個位元組陣列,讀取被拷貝檔案的所有位元組並寫道拷貝檔案中
int sum=0;//每次讀取的位元組數
while((sum=src.read(buff))>0){ des.write(buff,0,sum);//注意!讀到多少寫多少!}
src.close(); des.close(); System.out.println("複製完畢!");
} catch (FileNotFoundException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace(); }
//int data=0;//用於儲存每一個讀取的位元組
//讀取一個位元組,只要不是-1(檔案末尾),就進行復制工作
//while((data=src.read())!=-1){ des.write(data);//將讀取的字元寫入 }

9)基本型別序列化:將基本型別資料轉換為位元組陣列的過程。writeInt(111):將int值111轉換為位元組並寫入磁碟;持久化:將資料寫入磁碟的過程。
5.4基本流:FIS和FOS
Java I/O 輸入/輸出
流:根據方向分為:輸入流和輸出流。方向的定了是基於我們的程式的。流向我們程式的流叫做:輸入流;從程式向外流的叫做:輸出流
我們可以把流想象為管道,管道里流動的水,而java中的流,流動的是位元組。
1)輸入流是用於獲取(讀取)資料的,輸出流是用於向外輸出(寫出)資料的。
InputStream:該介面定義了輸入流的特徵
OutputStream:該介面定義了輸出流的特徵
2)流根據源頭分為:
基本流(節點流):從特定的地方讀寫的流類,如磁碟或一塊記憶體區域。即有來源。
處理流(高階流、過濾流):沒有資料來源,不能獨立存在,它的存在是用於處理基本流的。是使用一個已經存在的輸入流或輸出流連線建立的。
3)流根據處理的資料單位不同劃分為:
位元組流:以一個“位元組”為單位,以Stream結尾
字元流:以一個“字元”為單位,以Reader/Writer結尾
4)close()方法:流用完一定要關閉!流關閉後,不能再通過其讀、寫資料
5)用於讀寫檔案的位元組流FIS/FOS(基本流)
①FileInputStream:檔案位元組輸入流。 ②FileOutputStream:檔案位元組輸出流。
6)FileInputStream 常用構造方法:
①FileInputStream(File file):通過開啟一個到實際檔案的連線來建立一個FileInputStream,該檔案通過檔案系統中的File物件file指定。即向file檔案中寫入資料。
②FileInputStream(String filePath):通過開啟一個到實際檔案的連線來建立一個FileInputStream,該檔案通過檔案系統中的檔案路徑名指定。也可直接寫當前專案下檔名。
常用方法:
①int read(int d):讀取int值的低8位。
②int read(byte[] b):將b陣列中所有位元組讀出,返回讀取的位元組個數。
③int read(byte[] b,int offset,int length):將b陣列中offset位置開始讀出length個位元組。
④available()方法:返回當前位元組輸入流 可讀取的總位元組數。
7)FileOutputStream常用構造方法:
①FileOutputStream(File File):建立一個向指定File物件表示的檔案中寫入資料的檔案輸出流。會重寫以前的內容,向file檔案中寫入資料時,若該檔案不存在,則會自動建立該檔案。
②FileOubputStream(File file,boolean append):append為true則對當前檔案末尾進行寫操作(追加,但不重寫以前的)。
③FileOubputStream(String filePath):建立一個向具有指定名稱的檔案中寫入資料的檔案輸出流。前提路徑存在,寫當前目錄下的檔名或者全路徑。
④FileOubputStream(String filePath,boolean append):append為true則對當前檔案末尾進行寫操作(追加,但不重寫以前的)。
常用方法:
①void write(int d):寫入int值的低8位。
②void write(byte[] d):將d陣列中所有位元組寫入。
③void write(byte[] d,int offset,int length):將d陣列中offset位置開始寫入length個位元組。
5.5緩衝位元組高階流:BIS和BOS
對傳入的流進行處理加工,可以巢狀使用。
1)BufferedInputStream:緩衝位元組輸入流
A.構造方法:BufferedInputStream(InputStream in)
 BufferedInputStream(InputStream in, int size)
B.常用方法:
①int read():從輸入流中讀取一個位元組。
②int read(byte[] b,int offset,int length):從此位元組輸入流中給定偏移量offset處開始將各位元組讀取到指定的 byte 陣列中。
2)BufferedOutputStream:緩衝位元組輸出流
A.構造方法:BufferedOutputStream(OutputStream out)
 BufferedOutputStream(OutputStream out, int size)
B.常用方法:
①void write(int d):將指定的位元組寫入此緩衝的輸出流。
②void write(byte[] d,int offset,int length):將指定 byte陣列中從偏移量 offset開始的 length個位元組寫入此緩衝的輸出流。
③void flush():將緩衝區中的資料一次性寫出,“清空”緩衝區。
C.內部維護著一個緩衝區,每次都儘可能的讀取更多的位元組放入到緩衝區,再將緩衝區中的內容部分或全部返回給使用者,因此可以提高讀寫效率。
3)辨別高階流的簡單方法:看構造方法,若構造方法要求傳入另一個流,那麼這個流就是高階流。所以高階流是沒有空引數的構造器的,都需要傳入一個流。
4)有緩衝效果的流,一般為寫入操作的流,在資料都寫完後一定要flush,flush的作用是將緩衝區中未寫出的資料一次性寫出:bos.flush();即不論快取區有多少資料,先寫過去,緩衝區再下班~確保所有字元都寫出
5)使用JDK的話,通常情況下,我們只需要關閉最外層的流。第三方流可能需要一層一層關。
5.6基本資料型別高階流:DIS和DOS
是對“流”功能的擴充套件,簡化了對基本型別資料的讀寫操作。
1)DataInputStream(InputStream in):可以直接讀取基本資料型別的流
常用方法:
①int readInt():連續讀取4個位元組(一個int值),返回該int值
②double readDouble():連續讀取8個位元組(一個double值),返回double值
③String readUTF():連續讀取字串
……
2)DataOutputStream(OutputStream out):可以直接寫基本資料型別的流
常用方法:
①void writeInt(int i):連續寫入4個位元組(一個int值)
②void writeLong(long l):連續寫入8個位元組(一個long值)
③void writeUTF(String s):連續寫入字串
④void flush():將緩衝區中的資料一次性寫出,“清空”緩衝區。
……
5.7字元高階流:ISR和OSW
以“單個”“字元”為單位讀寫資料,一次處理一個字元(unicode)。
字元流底層還是基於位元組形式讀寫的。
在字元輸入輸出流階段,進行編碼修改與設定。
所有字元流都是高階流。
1) OutputStreamWriter:字元輸出流。
A.常用構造方法:
OutputStreamWriter(OutputStream out):建立一個字符集的輸出流。
OutputStreamWriter(OutputStream out, String charsetName):建立一個使用指定字符集的輸出流。
B.常用方法:
①void write(int c):寫入單個字元。
②void write(char c[], int off, int len):寫入從字元陣列off開頭到len長度的部分
③void write(String str, int off, int len):寫入從字串off開頭到len長度的部分。
④void flush():將緩衝區中的資料一次性寫出,“清空”緩衝區。
⑤void close():關閉流。
eg:向檔案中寫入字元:①建立檔案輸出流(位元組流)。②建立字元輸出流(高階流),處理檔案輸出流,目的是我們可以以位元組為單位寫資料。③寫入字元。④寫完後關閉流。
OutputStreamWriter writer=null;//不寫try-catch外的話finally找不到流,就無法關閉
try{ FileOutputStream fos=new FileOutputStream("writer.txt");
// writer=new OutputStreamWriter(fos);//預設構造方法使用系統預設的編碼集
writer=new OutputStreamWriter(fos,"UTF-8");//最好指定字符集輸出
writer.write("你好!"); writer.flush();//將緩衝區資料一次性寫出
}catch(IOException e){ throw e; }
finally{ if(writer!=null){ writer.close(); } }
2)InputStreamReader:字元輸入流。
A.常用構造方法:
InputStreamReader(InputStream in):建立一個字符集的輸入流。
InputStreamReader(InputStream in, String charsetName):建立一個使用指定字符集的輸入流。
B.常用方法:
①int read():讀取單個字元。
②int read(char cbuf[], int offset, int length):讀入字元陣列中從offset開始的length長度的字元。
③void close():關閉流。
eg:讀取檔案中的字元
InputStreamReader reader=null;
try{//建立用於讀取檔案的位元組出入流
FileInputStream fis=new FileInputStream("writer.txt");
//建立用於以字元為單位讀取資料的高階流
reader=new InputStreamReader(fis,"UTF-8"); int c=-1;//讀取資料
while((c=reader.read())!=-1){ //InputStreamReader只能一個字元一個字元的讀
System.out.println((char)c); }
}catch(IOException e){ throw e; }
finally{ if(reader!=null){ reader.close(); } }
5.8緩衝字元高階流:BR和BW
可以以“行”為單位讀寫“字元”,高階流。
在字元輸入輸出流修改編碼。
1)BufferedWriter:緩衝字元輸出流,以行為單位寫字元
A.常用構造方法:
BufferedWriter(Writer out):建立一個使用預設大小的緩衝字元輸出流。
BufferedWriter(Writer out,int size):建立一個使用給定大小的緩衝字元輸出流。
B.常用方法:
①void write(int c):寫入單個字元。
②void write(char[] c,int off,int len):寫入字元陣列從off開始的len長度的字元。
③void write(String s,int off,int len):寫入字串中從off開始的len長度的字元。
④void newLine():寫入一個行分隔符。
⑤flush():將緩衝區中的資料一次性寫出,“清空”緩衝區。
⑥close():關閉流。
 注意事項:BufferedWriter的構造方法中不支援給定一個位元組輸出流,只能給定一個字元輸出流Writer的子類,Writer是字元輸出流的父類。
//建立用於寫檔案的輸出流
FileOutputStream fos=new FileOutputStream("buffered.txt");
//建立一個字元輸出流,在字元輸入輸出流修改編碼
OutputStreamWriter osw=new OutputStreamWriter(fos,"UTF-8");
BufferedWriter writer=new BufferedWriter(osw);
writer.write("你好啊!!"); writer.newLine();//輸出一個換行
writer.write("我是第二行!!"); writer.newLine();//輸出一個換行
writer.write("我是第三行!!"); writer.close();//輸出流關閉後,不能再通過其寫資料
2)BufferedReader:緩衝字元輸入流,以行為單位讀字元
A.常用構造方法:
BufferedReader(Reader in):建立一個使用預設大小的緩衝字元輸入流。
BufferedReader(Reader in,int size):建立一個使用指定大小的緩衝字元輸入流。
B.常用方法:
①int read():讀取單個字元。如果已到達流末尾,則返回-1。
②int read(char cbuf[], int off, int len):從字元陣列中讀取從off開始的len長度的字元。返回讀取的字元數,如果已到達流末尾,則返回-1。
③String readLine():讀取一個文字行。通過下列字元之一即可認為某行已終止:換行 ('\n')、回車 ('\r') 或回車後直接跟著換行。如果已到達流末尾,則返回 null。EOF:end of file檔案末尾。
④void close():關閉流。
eg:讀取指定檔案中的資料,並顯示在控制檯
FileInputStream fis=new FileInputStream(
"src"+File.separator+"day08"+File.separator+"DemoBufferedReader.java");
InputStreamReader isr=new InputStreamReader(fis);
BufferedReader reader=new BufferedReader(isr); String str=null;
if((str=reader.readLine())!=null){//readLine()讀取一行字元並以字串形式返回
System.out.println(str); } reader.close();
eg:讀取控制檯輸入的每以行資訊,直到在控制檯輸入exit退出程式
//1 將鍵盤的位元組輸入流轉換為字元輸入流
InputStreamReader isr=new InputStreamReader(System.in);
//2 將字元輸入流轉換為緩衝字元輸入流,按行讀取資訊
BufferedReader reader=new BufferedReader(isr);
// 迴圈獲取使用者輸入的資訊並輸出到控制檯
String info=null; while(true){ info=reader.readLine(); if("exit".equals(info.trim())){ break; }
System.out.println(info);//輸出到控制檯 } reader.close();
5.9檔案字元高階流:FR和FW
用於讀寫“文字檔案”的“字元”輸入流和輸出流。
1)FileWriter寫入:繼承OutputStreamWriter
A.常用構造方法
FileWriter(File file) 、FileWriter(File file, boolean append)
FileWriter(String filePath)、FileWriter(String fileName, boolean append)
意思和FileOutputStream的四個同型別引數的構造方法一致。
 注意事項:FileWriter的效果等同於:FileOutputStream + OutputStreamWriter。
B.常用方法:
①void write(int c):寫入單個字元。
②void write(char c[], int off, int len):寫入字元陣列從off到len長度的部分
③void write(String str, int off, int len):寫入字串從off到len長度的部分。
④void flush():將緩衝區中的資料一次性寫出,“清空”緩衝區。
⑤void close():關閉流。
FileWriter writer=new FileWriter("filewriter.txt");
//File file=new File("filewriter.txt");
//FileWriter writer=new FileWriter(file);
writer.write("hello!FileWriter!"); writer.close();
2) FileReader讀取:繼承InputStreamReader
A.“只能”以“字元”為單位讀取檔案,所以效率低
B.常用構造方法
FileReader(File file)、FileReader(String filePath)
意思和FileInputStream的兩個同型別引數的構造方法一致。
C.常用方法:
①int read():讀取單個字元。
②int read(char cbuf[], int offset, int length):讀入字元陣列中從offset開始的length長度的字元。
③void close():關閉流。
FileReader reader=new FileReader("filewriter.txt");
//int c=-1; //只能以字元為單位讀取檔案
//while((c=reader.read())!=-1){ System.out.println((char)c); }
//將檔案字元輸入流轉換為緩衝字元輸入流便可以行為單位讀取
BufferedReader br=new BufferedReader(reader);
String info=null; while((info=br.readLine())!=null){ System.out.println(info); }
br.close();
5.10 PrintWriter
另一種緩衝“字元”輸出流,以“行”為單位,常用它作輸出,BufferedWriter用的少。
1)Servlet:執行在伺服器端的小程式,給客戶端傳送相應使用的輸出流就是PrintWriter。
2)寫方法:println(String data):帶換行符輸出一個字串,不用手動換行了。
println……
3)構造方式:
PrintWriter(File file):以行為單位向檔案寫資料
PrintWriter(OutputStream out):以行為單位向位元組輸出流寫資料
PrintWriter(Writer writer):以行為單位向字元輸出流寫資料
PrintWriter(String fileName):以行為單位向指定路徑的檔案寫資料
PrintWriter writer=new PrintWriter("printwriter.txt"); //向檔案寫入一個字串
writer.println("你好!PrintWriter");//自動加換行符
/**我們要在確定做寫操作的時候呼叫flush()方法,否則資料可能還在輸出流的緩衝區中,沒有作真實的寫操作!*/
writer.flush(); writer.close();
eg:將輸出流寫入檔案
System.out.println("你好!!"); PrintStream out=System.out;
PrintStream fileOut=new PrintStream( new FileOutputStream("SystemOut.txt") );
System.setOut(fileOut);//將我們給定的輸出流賦值到System.out上
System.out.println("你好!我是輸出到控制檯的!"); System.setOut(out);
System.out.println("我是輸出到控制檯的!"); fileOut.close();
5.11物件序列化
將一個物件轉換為位元組形式的過程就是物件序列化。序列化還有個名稱為序列化,序列化後的物件再被反序列化後得到的物件,與之前的物件不再是同一個物件。
1)物件序列化必須實現Serializable介面,但該介面無任何抽象方法,不需要重寫方法,只為了標註該類可序列化。
2)且同時建議最好新增版本號(編號隨便寫):serialVersionUID。版本號,用於匹配當前類與其被反序列化的物件是否處於同樣的特徵(屬性列表一致等)。反序列化時,ObjectInputStream會根據被反序列化物件的版本與當前版本進行匹配,來決定是否反序列化。 不加版本號可以,但是可能存在反序列化失敗的風險。
3)JDK提供的大多數java bean都實現了該介面
4)transient關鍵字:序列化時忽略被它修飾的屬性。
5)物件的序列化使用的類:ObjectOutputStream
writeObject(Object obj):①將給定物件序列化。②然後寫出。
6)物件的反序列化使用的類:ObjectInputStream
Object readObject():將讀取的位元組序列還原為物件
7)對於HTTP協議:通訊一次後,必須斷開連線,想再次通訊要再次連線。
8)想要實現斷點續傳,我們必須告訴伺服器我們當前讀取檔案的開始位置。相當於我們本地呼叫的seek(),因為我們不可能直接呼叫伺服器的物件的方法,所以我們只能通過某種方式告訴伺服器我們要幹什麼。讓它自行呼叫自己流物件的seek()到我們想讀取的位置。bytes=0- 的意思是告訴伺服器從第一個位元組開始讀,即seek(0)從頭到尾;bytes=128- 的意思是告訴伺服器從地129個位元組開始讀,即seek(128)。String prop="bytes="+info.getPos()+"-";
eg:序列化和反序列化
try{ DownloadInfo info=new DownloadInfo("http://www.baidu.com/download/xxx.zip"
, "xxx.zip" );
info.setPos(12587); info.setFileSize(5566987);
File file=new File("obj.tmp");//將物件序列化以後寫到檔案中
FileOutputStream fos=new FileOutputStream(file);
//通過oos可以將物件序列化後寫入obj.tmp檔案中
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(info);//將info序列化後寫出 oos.close();
///反序列化操作
FileInputStream fis=new FileInputStream(file);
ObjectInputStream ois=new ObjectInputStream(fis);
DownloadInfo obj=(DownloadInfo)ois.readObject();//反序列化
System.out.println(obj.getUrl()); System.out.println(obj.getFileName());
System.out.println(obj.getFileSize()); System.out.println(obj.getPos());
System.out.println(info==obj); ois.close();
}catch(Exception e){ e.printStackTrace(); System.out.println("非常sorry!"); }
5.12 Thread執行緒類及多執行緒
程式:一個作業系統中可以同時執行多個任務(程式),每個執行的任務(程式)被稱為一個程式。即系統級別上的多執行緒(多個任務)。
執行緒:一個程式同時可能執行多個任務(順序執行流),那麼每個任務(順序執行流)就叫做一個執行緒。即在程式內部。
併發:執行緒是併發執行的。作業系統將時間化分為若干個片段(時間片),儘可能的均勻分配給每一個任務,被分配時間片後,任務就有機會被cpu所執行。微觀上看,每個任務都是走走停停的。但隨著cpu高效的執行,巨集觀上看所有任務都在執行。這種都執行的現象稱之為併發,但不是絕對意義上的“同時發生”。
1)Thread類的例項代表一個併發任務。任何執行緒物件都是Thread類的(子類)例項。Thread類是執行緒的模版,它封裝了複雜的執行緒開啟等操作,封裝了作業系統的差異性。因此併發的任務邏輯實現只要重寫Thread的run方法即可。
2)執行緒排程:執行緒排程機制會將所有併發任務做統一的排程工作,劃分時間片(可以被cup執行的時間)給每一個任務,時間片儘可能的均勻,但做不到絕對均勻。同樣,被分配時間片後,該任務被cpu執行,但排程的過程中不能保證所有任務都是平均的獲取時間片的次數。只能做到儘可能平均。這兩個都是程式不可控的。
3)執行緒的啟動和停止:void start():想併發操作不要直接呼叫run方法!而是呼叫執行緒的start()方法啟動執行緒!void stop():不要使用stop()方法來停止執行緒的執行,這是不安全的操作,想讓執行緒停止,應該通過run方法的執行完畢來進行自然的結束。
4)執行緒的建立方式一:1:繼承自Thread。2:重寫run方法:run方法中應該定義我們需要併發執行的任務邏輯程式碼。
5)執行緒的建立方式二:將執行緒與執行的邏輯分離開,即實現Runnalbe介面。因為有了這樣的設計,才有了執行緒池。關注點在於要執行的邏輯。
6)Runnable介面:用於定義執行緒要執行的任務邏輯。我們定一個類實現Runnable介面,這時我們必須重寫run方法,在其中定義我們要執行的邏輯。之後將Runnable交給執行緒去執行。從而實現了執行緒與其執行的任務分離開。將任務分別交給不同的執行緒併發處理,可以使用執行緒的過載構造方法:Thread(Runnable runnable)。解藕:執行緒與執行緒體解藕,即打斷依賴關係。Spring的ioc就是幹這個的。
/**建立兩個需要併發的任務,MyFirstRunnable和MySecRunnable都繼承了Runnable介面並重寫了run()方法 */
Runnable r1=new MyFirstRunnable(); Runnable r2=new MySecRunnable();
Thread t1=new Thread(r1); Thread t2=new Thread(r2);
t1.start(); t2.start();
7)執行緒的建立方式三:使用匿名內部類方式建立執行緒
new Thread(){public void run(){…}}.start(); /** * 匿名類實現繼承Thread形式*/
Thread t1=new Thread(){
public void run(){
for(int i=0;i<1000;i++){
System.out.println(i);
}
}
};
new Thread(new Runnable(){public void run(){…}}).start();
/**匿名類實現Runnable介面的形式 */
Thread t2=new Thread(new Runnable(){
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你好"+i+"次");
}
}
} );
8)執行緒生命週期:

9)執行緒睡眠阻塞:使當前執行緒放棄cpu時間,進入阻塞狀態。在阻塞狀態的執行緒不會分配時間片。直到該執行緒結束阻塞狀態回到Runnable狀態,方可再次獲得時間片來讓cpu執行(進入Running狀態)。
①static void sleep(times)方法:讓當前執行緒主動進入Block阻塞狀態,並在time毫秒後回到Runnalbe狀態。
 注意事項:使用Thread.sleep()方法阻塞執行緒時,強制讓我們必須捕獲“中斷異常”。 引發情況:當前執行緒處於Sleep阻塞期間,被另一個執行緒中斷阻塞狀態時,當前執行緒會丟擲該異常。
int i=0; while(true){ System.out.println(i+"秒"); i++;
try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } }
10)void interrupt()方法:打斷/喚醒執行緒。一個執行緒可以提前喚醒另外一個sleep Block的執行緒。
 注意事項:方法中定義的類叫區域性內部類:區域性內部類中,若想引用當前方法的其他區域性變數,那麼該變數必須是final的。
final Thread lin=new Thread(){
public void run(){ System.out.println("林:睡覺了……");
try { Thread.sleep(1000000); } catch (InterruptedException e) {
System.out.println("林:幹嘛呢!幹嘛呢!幹嘛呢!");
System.out.println("林:都破了相了!"); } } };
lin.start();//啟動第一個執行緒
Thread huang=new Thread(){
public void run(){ System.out.println("80一錘子,您說咂哪兒?");
for(int i=0;i<5;i++){ System.out.println("80!");
try { Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } }
System.out.println("咣噹!");
System.out.println("黃:搞定!");
lin.interrupt();//中斷第一個執行緒的阻塞狀態 } };
huang.start();//啟動第二個執行緒
11)執行緒的其他方法:
①static void yield():當前執行緒讓出處理器(離開Running狀態)即放棄當前時間片,主動進入Runnable狀態等待。
②final void setPriority(int):設定執行緒優先順序;優先順序越高的執行緒,理論上獲取cpu的次數就越多。但理想與現實是有差距的……設定執行緒優先順序一定要線上程啟動前設定!
③final void join():等待該執行緒終止。
Thread t1=new Thread(){
public void run(){ for(int i=0;i<100;i++){ System.out.println("我是誰啊?");
Thread.yield(); } }
};
Thread t2=new Thread(){
public void run(){ for(int i=0;i<100;i++){ System.out.println("我是修水管的");
Thread.yield(); } }
};
Thread t3=new Thread(){
public void run(){ for(int i=0;i<100;i++){ System.out.println("我是打醬油的");
Thread.yield(); } }
};
t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY);
t1.start(); t2.start(); t3.start();
12)執行緒併發安全問題:synchronized關鍵字,執行緒安全鎖、同步監視器。
多執行緒在訪問同一個資料時(寫操作),可能會引發不安全操作。
①哪個執行緒報錯不捕獲,則執行緒死,不影響主程式。
②同步:同一時刻只能有一個執行,A和B配合工作,步調一致的處理(B得到A的執行結果才能繼續)。如一群人上公交車。
非同步:同一時刻能有多個執行,併發,各自幹各自的。如一群人上卡車。
③synchronized可以修飾方法也可以單獨作為語句塊存在(同步塊)。作用是限制多執行緒併發時同時訪問該作用域。
④synchronized修飾方法後,會為方法上鎖。方法就不是非同步的了,而是同步的。鎖的是當前物件。
⑤synchronized同步塊:分析出只有一段程式碼需要上鎖,則使用。效率比直接修飾方法要高。
⑥執行緒安全的效率低,如Vector、Hashtable。執行緒不安全的效率高,如ArrayList、HashMap
synchronized void getMoney(int money){ if(count==0){
throw new RuntimeException("餘額為0"); }
Thread.yield(); count-=money; }
void getMoney(int money){
synchronized(this){//synchronized(Object){需要同步的程式碼片段}
if(count==0){ throw new RuntimeException("餘額為0"); }
Thread.yield(); count-=money; }
13)Daemon後臺執行緒也稱為守護執行緒:噹噹前程式中“所有”“前臺”執行緒死亡後,後臺執行緒將被強制死亡(非自然死亡),無論是否還在執行。
①守護執行緒,必須在啟動執行緒前呼叫。
②main方法也是靠執行緒執行的,且是一個前臺執行緒。
③正在執行的執行緒都是守護執行緒時,JVM退出。
14)wait/notify方法
這兩個方法不是線上程Thread中定義的方法,這兩個方法定義在Object中。兩個方法的作用是用於協調執行緒工作的。
①等待機制與鎖機制密切關聯:wait/notify方法必須與synchronized同時使用,誰呼叫wait或otify方法,就鎖誰!
②wait()方法:當條將不滿足時,則等待。當條件滿足時,等待該條件的執行緒將被喚醒。如:瀏覽器顯示一個圖片,displayThread要想顯示圖片,則必須等代下載執行緒downloadThread將該圖片下載完畢。如果圖片沒有下雜完成,則dialpayThread可以暫停。當downloadThread下載完成後,再通知displayThread可以顯示了,此時displayThread繼續執行。
③notify()方法:隨機通知、喚醒一個在當前物件身上等待的執行緒。
④notifyAll方法:通知、喚醒所有在當前物件身上等待的執行緒。


5.13 Socket網路程式設計
Socket套接字。在java.net.Socket包下。
1)網路通訊模型:C/S:client/server,客戶端/伺服器端;B/S:browser/server,瀏覽器端/伺服器端;C/S結構的優點:應用的針對性強,畫面絢麗,應用功能複雜。缺點:不易維護。B/S結構的優點:易於維護。缺點:效果差,互動性不強。
2)Socket:封裝著本地的地址,服務埠等資訊。ServerSocket:服務端的套接字。
伺服器:使用ServerSocket監聽指定的埠,埠可以隨意指定(由於1024以下的埠通常屬於保留埠,在一些作業系統中不可以隨意使用,所以建議使用大於1024的埠),等待客戶連線請求,客戶連線後,會話產生;在完成會話後,關閉連線。
客戶端:使用Socket對網路上某一個伺服器的某一個埠發出連線請求,一旦連線成功,開啟會話;會話完成後,關閉Socket。客戶端不需要指定開啟的埠,通常臨時的、動態的分配一個1024以上的埠。
3)永遠都是Socket去主動連線ServerSocket。一個ServerSocket可以接收若干個Socket的連線。網路通訊的前提:一定要捕獲異常。
4)Socket連線基於TCP/IP協議,是一種長連線(長時間連著)。
5)讀取伺服器資訊會阻塞,寫操作不會。
6)建立連線並向伺服器傳送資訊步驟:①通過伺服器的地址及埠與伺服器連線,而建立Socket時需要以上兩個資料。②連線成功後可以通過Socket獲取輸入流和輸出流,使用輸入流接收服務端傳送過來的資訊。③關閉連線。
7)連線伺服器:一旦Socket被例項化,那麼它就開始通過給定的地址和埠號去嘗試與伺服器進行連線(自動的)。這裡的地址"localhost"是伺服器的地址,8088埠是伺服器對外的埠。我們自身的埠是系統分配的,我們無需知道。
8)和伺服器通訊(讀寫資料):使用Socket中的getInputStream()獲取輸入流,使用getOutputStream()獲取輸出流。
9)ServerSocket構造方法要求我們傳入開啟的埠號,ServerSocket物件在建立的時候就向作業系統申請開啟這個埠。
10)通過呼叫ServerSocket的accept方法,使伺服器端開始等待接收客戶端的連線。該方法是一個阻塞方法,監聽指定的埠是否有客戶端連線。直到有客戶端與其連線並接收客戶端套接字,否則該方法不會結束。
eg1.1:客戶端ClientDemo類
private Socket socket;
public void send(){
try{ System.out.println("開始連線伺服器"); socket=new Socket("localhost",8088);
InputStream in=socket.getInputStream();//獲取輸入流
OutputStream out=socket.getOutputStream();//獲取輸出流
/**將輸出流變成處理字元的緩衝字元輸出流*/
PrintWriter writer=new PrintWriter(out); writer.println("你好!伺服器!");
/**注意,寫到輸出流的緩衝區裡了,並沒有真的發給伺服器。想真的傳送就要作真實的寫操作,清空緩衝區*/
writer.flush();
/**將輸入流轉換為緩衝字元輸入流*/
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
/**讀取伺服器傳送過來的資訊*/
String info=reader.readLine();//讀取伺服器資訊會阻塞 System.out.println(info);
writer.println("再見!伺服器!"); writer.flush();
info=reader.readLine(); System.out.println(info);
}catch(Exception e){ e.printStackTrace(); } }
public static void main(String[] args){
ClientDemo demo=new ClientDemo(); demo.send();//連線伺服器並通訊 }
eg1.2:伺服器端ServerDemo類(不使用執行緒)
private ServerSocket socket=null; private int port=8088;
/**構建ServerDemo物件時就開啟服務埠*/
public ServerDemo(){
try{ socket=new ServerSocket(port); }catch(Exception e){ e.printStackTrace(); } }
/**開始服務,等待收受客戶端的請求並與其通訊*/
public void start(){
try{ System.out.println("等待客戶端連線……"); Socket s=socket.accept();
//獲取與客戶端通訊的輸入輸出流
InputStream in=s.getInputStream(); OutputStream out=s.getOutputStream();
//包裝為緩衝字元流
PrintWriter writer=new PrintWriter(out);
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
//先聽客戶端傳送的資訊
String info=reader.readLine();//這裡同樣會阻塞 System.out.println(info);
//傳送資訊給客戶端
writer.println("你好!客戶端"); writer.flush();
info=reader.readLine(); System.out.println(info);
writer.println("再見!客戶端"); writer.flush();
socket.close();//關閉與客戶端的連線
}catch(Exception e){ e.printStackTrace(); } }
public static void main(String[] args){ System.out.println("伺服器啟動中……");
ServerDemo demo=new ServerDemo(); demo.start(); }
eg2:伺服器端ServerDemo類(使用執行緒),start()方法的修改以及Handler類
public void start(){
try{while(true){ System.out.println("等待客戶端連線……"); Socket s=socket.accept();
/** 當一個客戶端連線了,就啟動一個執行緒去接待它 */
Thread clientThread=new Thread(new Handler(s)); clientThread.start(); }
}catch(Exception e){ e.printStackTrace(); } }
/** 定義執行緒體,該執行緒的作用是與連線到伺服器端的客戶端進行互動操作 */
class Handler implements Runnable{
private Socket socket;//當前執行緒要進行通訊的客戶端Socket
public Handler(Socket socket){//通過構造方法將客戶端的Socket傳入
this.socket=socket; }
public void run(){
try{ //獲取與客戶端通訊的輸入輸出流
InputStream in=socket.getInputStream();OutputStream out=socket.getOutputStream();
PrintWriter writer=new PrintWriter(out);//包裝為緩衝字元流
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
String info=reader.readLine();//先聽客戶端傳送的資訊,這裡同樣會阻塞
System.out.println(info);
//傳送資訊給客戶端
writer.println("你好!客戶端"); writer.flush();
info=reader.readLine(); System.out.println(info);
writer.println("再見!客戶端"); writer.flush();
socket.close();//關閉與客戶端的連線
}catch(Exception e){ e.printStackTrace(); } } }
public static void main(String[] args){ System.out.println("伺服器啟動中……");
ServerDemo demo=new ServerDemo(); demo.start(); }
5.14執行緒池
執行緒若想啟動需要呼叫start()方法。這個方法要做很多操作。要和作業系統打交道。註冊執行緒等工作,等待執行緒排程。ExecutorService提供了管理終止執行緒池的方法。
1)執行緒池的概念:首先建立一些執行緒,它們的集合稱為執行緒池,當伺服器接受到一個客戶請求後,就從執行緒池中取出一個空閒的執行緒為之服務,服務完後不關閉該執行緒,而是將該執行緒還回到執行緒池中。線上程池的程式設計模式下,任務是提交給整個執行緒池,而不是直接交給某個執行緒,執行緒池在拿到任務後,它就在內部找有無空閒的執行緒,再把任務交給內部某個空閒的執行緒,一個執行緒同時只能執行一個任務,但可以同時向一個執行緒池提交多個任務。
2)執行緒池的建立都是工廠方法。我們不要直接去new執行緒池,因為執行緒池的建立還要作很多的準備工作。
3)常見構造方法:
①Executors.newCachedThreadPool():可根據任務需要動態建立執行緒,來執行任務。若執行緒池中有空閒的執行緒將重用該執行緒來執行任務。沒有空閒的則建立新執行緒來完成任務。理論上池子裡可以放int最大值個執行緒。快取執行緒生命週期1分鐘,得不到任務直解kill
②Executors.newFixedThreadPool(int threads):建立固定大小的執行緒池。池中的執行緒數是固定的。若所有執行緒處於飽和狀態,新任務將排隊等待。
③Executors.newScheduledThreadPool():建立具有延遲效果的執行緒池。可將帶執行的任務延遲指定時長後再執行。
④Executors.newSingleThreadExecutor():建立單執行緒的執行緒池。池中僅有一個執行緒。所有未執行的任務排隊等待。
5.15雙緩衝佇列
BlockingQueue:解決了讀寫資料阻塞問題,但是同時寫或讀還是同步的。
1)雙緩衝佇列加快了讀寫資料操作,雙緩衝對列可以規定佇列儲存元素的大小,一旦佇列中的元素達到最大值,待插入的元素將等。等待時間是給定的,當給定時間到了元素還沒有機會被放入佇列那麼會丟擲超時異常。
2)LinkedBlockingQueue是一個可以不指定佇列大小的雙緩衝佇列。若指定大小,當達到峰值後,待入隊的將等待。理論上最大值為int最大值。
eg1.1:log伺服器寫日誌檔案,客戶端ClientDemo類,try語句塊中修改如下
try{ System.out.println("開始連線伺服器");
socket=new Socket("localhost",8088);
OutputStream out=socket.getOutputStream();
PrintWriter writer=new PrintWriter(out);
while(true){
writer.println("你好!伺服器!");
writer.flush();
Thread.sleep(500); } }
eg1.2:log伺服器寫日誌檔案,伺服器端ServerDemo類,增加執行緒池和雙緩衝佇列兩個屬性,刪掉與原客戶端的輸出流
private ExecutorService threadPool;//執行緒池
private BlockingQueue<String> msgQueue; //雙緩衝佇列
public ServerDemo(){
try{ socket=new ServerSocket(port);
//建立50個執行緒的固定大小的執行緒池
threadPool=Executors.newFixedThreadPool(50);
msgQueue=new LinkedBlockingQueue<String>(10000);
/**建立定時器,週期性的將佇列中的資料寫入檔案*/
Timer timer=new Timer();
timer.schedule(new TimerTask(){
public void run(){
try{ //建立用於向檔案寫資訊的輸出流
PrintWriter writer=new PrintWriter(new FileWriter("log.txt",true));
//從佇列中獲取所有元素,作寫出操作
String msg=null;
for(int i=0;i<msgQueue.size();i++){
/**引數 0:時間量TimeUnit.MILLISECONDS:時間單位*/
msg=msgQueue.poll(0,TimeUnit.MILLISECONDS);
if(msg==null){ break; }
writer.println(msg);//通過輸出流寫出資料
}
writer.close();
}catch(Exception e){ e.printStackTrace(); }
}
}, 0,500);
}catch(Exception e){ e.printStackTrace(); } }
public void start(){
try{ while(true){ System.out.println("等待客戶端連線……");
Socket s=socket.accept();
/**將執行緒體(併發的任務)交給執行緒池,執行緒池會自動將該任務分配給一個空閒執行緒去執行。*/ threadPool.execute(new Handler(s));
System.out.println("一個客戶端連線了,分配執行緒"); }
}catch(Exception e){ e.printStackTrace(); } }
/**定義執行緒體,該執行緒的作用是與連線到伺服器端的客戶端進行互動操作*/
class Handler implements Runnable{
private Socket socket;//當前執行緒要進行通訊的客戶端Socket
public Handler(Socket socket){//通過構造方法將客戶端的Socket傳入
this.socket=socket; }
public void run(){
try{ //獲取與客戶端通訊的輸入輸出流
InputStream in=socket.getInputStream();
//包裝為緩衝字元流
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
String info=null;
while(true){//迴圈讀取客戶端傳送過來的資訊
info=reader.readLine();
if(info!=null){ //插入對列成功返回true,失敗返回false
//該方法會阻塞執行緒,若中斷會報錯!
boolean b=msgQueue.offer(info, 5, TimeUnit.SECONDS); }
}
}catch(Exception e){ e.printStackTrace(); }
}
}

相關文章