一、異常概念
1慎玖、異常:有異于常態(tài)贮尖,和正常情況不一樣,有錯(cuò)誤出現(xiàn)趁怔,阻止當(dāng)前方法或作用域湿硝。
2、異常處理:將出現(xiàn)的異常提示給編程人員與用戶润努,使原本將要中斷的程序繼續(xù)運(yùn)行或者退出关斜。并且能夠保存數(shù)據(jù)和釋放資源。
二铺浇、異常體系結(jié)構(gòu)
1蚤吹、所有異常都繼承于Throwable類,其下有兩大子類:
(1)Error類:錯(cuò)誤随抠,一般編程人員不太接觸裁着,如虛擬機(jī)錯(cuò)誤、線程死鎖拱她。硬傷:使程序崩潰
(2)Exception類:異常二驰,編碼、環(huán)境秉沼、用戶輸入等問(wèn)題桶雀,其子類主要有:
- 非檢查異常(運(yùn)行時(shí)異常RuntimeException):【由java虛擬機(jī)自動(dòng)捕獲】如空指針NullPointer、越界ArrayIndexOutofBounds唬复、錯(cuò)誤類型轉(zhuǎn)換ClassCast矗积、算數(shù)異常Arithmetic等
- 檢查異常CheckException:【需要手動(dòng)添加捕獲和處理語(yǔ)句】文件異常IO等
異常處理:
一、try-catch(多catch塊)-finally
(1)try塊:負(fù)責(zé)捕獲異常敞咧,一旦try中發(fā)現(xiàn)異常棘捣,程序的控制權(quán)將被移交給catch塊中的異常處理程序⌒萁ǎ【try語(yǔ)句塊不可以獨(dú)立存在乍恐,必須與 catch 或者 finally 塊同存】
(2)catch塊:如何處理?比如發(fā)出警告:提示测砂、檢查配置茵烈、網(wǎng)絡(luò)連接,記錄錯(cuò)誤等砌些。執(zhí)行完catch塊之后程序跳出catch塊呜投,繼續(xù)執(zhí)行后面的代碼。
·編寫catch塊的注意事項(xiàng):多個(gè)catch塊處理的異常類,要按照先catch子類后catch父類的處理方式仑荐,因?yàn)闀?huì)【就近處理】異常(由上自下)雕拼。
(3)finally:最終執(zhí)行的代碼,用于關(guān)閉和釋放資源等
異常處理
try-catch以及try-catch-finally
try{
//一些會(huì)拋出的異常
}catch(Exception e){
//處理該異常的代碼塊
}finally{
//最終要執(zhí)行的代碼
}
終止執(zhí)行释漆,交由異常處理程序(拋出提醒或記錄日志等),異常代碼塊外代碼正常執(zhí)行篮迎。
try會(huì)拋出很多種類型的異常男图,多個(gè)catch塊捕獲多鐘錯(cuò)誤。
多重異常處理代碼塊順序問(wèn)題:先子類再父類(順序不對(duì)也會(huì)提醒錯(cuò)誤)甜橱,finally語(yǔ)句塊處理最終將要執(zhí)行的代碼
return在try-catch-finally中:
1逊笆、不管有木有出現(xiàn)異常,finally塊中代碼都會(huì)執(zhí)行岂傲;
2难裆、當(dāng)try和catch中有return時(shí),finally仍然會(huì)執(zhí)行镊掖;
3乃戈、finally是在return后面的表達(dá)式運(yùn)算后執(zhí)行的(此時(shí)并沒(méi)有返回運(yùn)算后的值,而是先把要返回的值保存起來(lái)亩进,管finally中的代碼怎么樣症虑,返回的值都不會(huì)改變,仍然是之前保存的值)归薛,所以函數(shù)返回值是在finally執(zhí)行前確定的谍憔;
4、finally中最好不要包含return主籍,否則程序會(huì)提前退出习贫,返回值不是try或catch中保存的返回值。
1.e.printStackTrace()可以輸出異常信息
2.-1為拋出異常的習(xí)慣寫法
3.如果方法中try,catch,finally中沒(méi)有返回語(yǔ)句千元,則會(huì)調(diào)用這三個(gè)語(yǔ)句塊之外的return結(jié)果
4.finally塊無(wú)論如何苫昌,不管前面是正常還是異常,都要執(zhí)行幸海。
5.finally 在try中的return之后 在返回主調(diào)函數(shù)之前執(zhí)行蜡歹。
兩個(gè)重要的關(guān)鍵字:throw和throws
1.throws的異常列表可以是拋出一條異常,也可以是拋出多條異常涕烧,每個(gè)類型的異常中間用逗號(hào)隔開(kāi)
2.方法體中調(diào)用會(huì)拋出異常的方法或者是先拋出一個(gè)異常:用throw new Exception()
throw寫在方法體里月而,表示“拋出異常”這個(gè)動(dòng)作
3.如果某個(gè)方法調(diào)用了拋出異常的方法议纯,那么必須添加try catch語(yǔ)句去嘗試捕獲這種異常父款,
只有Error,Exception,RuntimeException提供了帶cause參數(shù)的構(gòu)造器憨攒,其他的所有異常類只能通過(guò)initCause()來(lái)設(shè)置cause世杀。
所有Throwable的子類構(gòu)造器中都可以接受一個(gè)cause對(duì)象作為參數(shù)。cause是異常原由肝集,代表著原始異常瞻坝。既可以在當(dāng)前位置創(chuàng)建并拋出行的異常,也可以通過(guò)cause追蹤到異常最初發(fā)生的位置杏瞻。
異常鏈?zhǔn)且环N面向?qū)ο缶幊碳夹g(shù)所刀,指將捕獲的異常包裝進(jìn)一個(gè)新的異常中并重新拋出的異常處理方式。原異常被保存為新異常的一個(gè)屬性(比如cause)捞挥。這個(gè)想法是指一個(gè)方法應(yīng)該拋出定義在相同的抽象層次上的異常浮创,但不會(huì)丟棄更低層次的信息。
把捕獲的異常包裝成新的異常砌函,在新異常里添加原始的異常斩披,并將新異常拋出,它們就像是鏈?zhǔn)椒磻?yīng)一樣讹俊,一個(gè)導(dǎo)致(cause)另一個(gè)
這個(gè)想法是指一個(gè)方法應(yīng)該拋出定義在相同的抽象層次上的異常垦沉,(將所有捕獲到的異常包裝為新的異常類,即定義在相同的抽象層次上拋出)但不會(huì)丟棄更低層次的信息仍劈。
實(shí)現(xiàn)異常鏈功能的兩種基本寫法:
public class chainTest {
/**
* @param args
* Test1拋出喝大了異常
* Test2調(diào)用test1捕獲了喝大了異常乡话,并且包裝成運(yùn)行時(shí)異常,繼續(xù)拋出
* main方法中調(diào)用test2嘗試捕獲test2方法拋出的異常
*/
public static void main(String[] args) {
try{ // TODO Auto-generated method stub
chainTest ct=new chainTest();
ct.Test2();}
catch(Exception e){
e.printStackTrace();
}
}public void Test1()throws DrunkException{
throw new DrunkException("喝車別開(kāi)酒");
}
public void Test2(){
try{
Test1();
}catch( DrunkException e){
RuntimeException rte=new RuntimeException(e);
//rte.initCause(e);
e.printStackTrace();
throw rte;
}
}
}
關(guān)于異常的經(jīng)驗(yàn)總結(jié)總結(jié):
1耳奕、處理運(yùn)行時(shí)異常時(shí)绑青,采用邏輯去合理規(guī)避同時(shí)輔助try-catch處理
2、在多重catch塊后面屋群,可以加一個(gè)catch(Exception)來(lái)處理可能會(huì)被遺漏的異常
3闸婴、對(duì)于不確定的代碼,也可以加上try-catch芍躏,處理潛在的異常
4邪乍、盡量去處理異常,切記只是簡(jiǎn)單的調(diào)用printStackTrace()去打印
5对竣、具體如何處理異常庇楞,要根據(jù)不同的業(yè)務(wù)需求和異常類型去決定
6、盡量添加finally語(yǔ)句塊去釋放占用的資源
Java 中的字符串
在 Java 中否纬,字符串被作為 String 類型的對(duì)象處理吕晌。 String 類位于 java.lang 包中。默認(rèn)情況下临燃,該包被自動(dòng)導(dǎo)入所有的程序睛驳。
String 對(duì)象創(chuàng)建后則不能被修改烙心,是不可變的,所謂的修改其實(shí)是創(chuàng)建了新的對(duì)象乏沸,所指向的內(nèi)存空間不同淫茵。
1、 通過(guò) String s1="愛(ài)慕課"; 聲明了一個(gè)字符串對(duì)象蹬跃, s1 存放了到字符串對(duì)象的引用匙瘪,常量字符串存儲(chǔ)在堆內(nèi)存中,s1只是對(duì)存放了到字符串的引用,通過(guò) s1="歡迎來(lái)到:"+s1; 改變了字符串 s1 蝶缀,其實(shí)質(zhì)是創(chuàng)建了新的字符串對(duì)象丹喻,變量 s1 指向了新創(chuàng)建的字符串對(duì)象,即使s1指向了另一塊堆內(nèi)存。
2扼劈、 一旦一個(gè)字符串在內(nèi)存中創(chuàng)建驻啤,則這個(gè)字符串將不可改變菲驴。如果需要一個(gè)可以改變的字符串荐吵,我們可以使用StringBuffer或者StringBuilder(后面章節(jié)中會(huì)講到)。
3赊瞬、 每次 new 一個(gè)字符串就是產(chǎn)生一個(gè)新的對(duì)象先煎,即便兩個(gè)字符串的內(nèi)容相同,使用 ”==” 比較時(shí)也為 ”false” ,如果只需比較內(nèi)容是否相同巧涧,應(yīng)使用 ”equals()” 方法(前面條件運(yùn)算符章節(jié)講過(guò)哦~~)
String常用方法
int length() 返回當(dāng)前字符串的長(zhǎng)度
int indexOf(int ch) 查找ch字符在該字符串中第一次出現(xiàn)的位置
int indexOf(String str) 查找str子字符串在該字符串中第一次出現(xiàn)的位置
int lastIndexOf(int ch) 查找ch字符在該字符串中最后一次出現(xiàn)的位置
int lastIndexOf(String str) 查找str子字符串在該字符串中最后一次出現(xiàn)的位置
String substring(int beginIndex) 獲取從beginIndex位置開(kāi)始到結(jié)束的子字符串
String substring(int beginIndex, int endIndex) 獲取從beginIndex位置開(kāi)始到endIndex位置的子字符串
String trim() 返回去除了前后空格的字符串
boolean equals(Object obj) 將該字符串與制定對(duì)象比較薯蝎,返回true或false
String toLowerCase() 將字符串轉(zhuǎn)換為小寫
String toUpperCase() 將字符串轉(zhuǎn)換為大寫
char charAt(int index) 獲取字符串中指定位置的字符
String[] split(String regex, int limit) 將字符串分割為子字符串,返回字符串?dāng)?shù)組
byte[] getBytes() 將該字符串轉(zhuǎn)換為byte數(shù)組
在Java中谤绳,除了可以使用 String 類來(lái)存儲(chǔ)字符串占锯,還可以使用 StringBuilder 類或 StringBuffer 類存儲(chǔ)字符串,其中:
(1)String 類具有是不可變性:多個(gè)字符串進(jìn)行拼接了以后產(chǎn)生一個(gè)新的臨時(shí)變量并指向一個(gè)新的對(duì)象或新的地址缩筛。
(2)StringBuffer 是線程安全的消略,而 StringBuilder 則沒(méi)有實(shí)現(xiàn)線程安全功能,所以性能略高瞎抛。
如果需要?jiǎng)?chuàng)建一個(gè)內(nèi)容可變的字符串對(duì)象艺演,應(yīng)優(yōu)先考慮使用 StringBuilder 類。
StringBuilder 對(duì)象桐臊,用來(lái)存儲(chǔ)字符串胎撤,并對(duì)其做了追加和插入操作。這些操作修改了 str 對(duì)象的值断凶,而沒(méi)有創(chuàng)建新的對(duì)象伤提,這就是 StringBuilder 和 String 最大的區(qū)別。
// 創(chuàng)建一個(gè)空的StringBuilder對(duì)象
StringBuilder str=new StringBuilder();
// 追加字符串
str.append("jaewkjldfxmopzdm");
// 從后往前每隔三位插入逗號(hào)
for(int i=str.length()-3;i>0;i=i-3)
str.insert(i,",");
// 將StringBuilder對(duì)象轉(zhuǎn)換為String對(duì)象并輸出
System.out.print(str.toString());
包裝類(自動(dòng)導(dǎo)入认烁,讓基本數(shù)據(jù)類型具有對(duì)象的特性)主要提供了兩大類方法:(String,包裝類都可以直接String或者Integer飘弧,Double,Float一個(gè)對(duì)象识藤,包裝類,系統(tǒng)自動(dòng)導(dǎo)入)
1. 將本類型和其他基本類型進(jìn)行轉(zhuǎn)換的方法 對(duì)象.類型Value();
裝箱:把基本類型轉(zhuǎn)換成包裝類次伶,使其具有對(duì)象的性質(zhì)痴昧,又可分為手動(dòng)裝箱和自動(dòng)裝箱。
int i=10; Integer x=new Integer(i);//手動(dòng)裝箱 Integer y=i;//自動(dòng)裝箱
拆箱:把包裝類對(duì)象轉(zhuǎn)換成基本類型的值冠王,又可分手動(dòng)拆箱和自動(dòng)拆箱赶撰。
Integer j=new Integer(8); int m=j.intValue();//手動(dòng)拆箱 int n=j;//自動(dòng)拆箱。
2 將字符串和本類型及包裝類互相轉(zhuǎn)換的方法
裝箱之后 基本數(shù)據(jù)類型就變成了類 而類具有很多屬性和方法 比如 int 裝箱成Integer后 Integer.MAX_VALUE 就代表了
int類型的最大值 Integer.toHexString可以將int類型的數(shù)值轉(zhuǎn)變?yōu)樽址?而這些 int都做不到...
基本類型轉(zhuǎn)換為字符串有三種方法:
1.包裝類的toString()方法
2.String的valueOf()方法
3.用一個(gè)空字符串加上基本類型柱彻,得到的就是基本數(shù)據(jù)類型對(duì)應(yīng)的字符串
eg:int c=10;
String str1=Integer.toString(c);
String str2=String.valueOf(c);
String str3=c+" ";
將字符串轉(zhuǎn)換成基本類型有兩種方法:
1.包裝類的parseXxx靜態(tài)方法
2.調(diào)用包裝類的valueOf()方法轉(zhuǎn)換為基本數(shù)據(jù)類型的包裝類豪娜,會(huì)自動(dòng)拆箱
String str="8";
int a=Integer.parseInt(str);
int b=Integer.valueOf(str);
ps:其它基本數(shù)據(jù)類型與字符串的相互轉(zhuǎn)換方法都類似。
使用 Date 和 SimpleDateFormat 類表示時(shí)間
SimpleDateFormat 可以對(duì)日期時(shí)間進(jìn)行格式化,(由于Date默認(rèn)輸出的時(shí)間格式不友好因此需要轉(zhuǎn)換)如可以將日期轉(zhuǎn)換為指
定格式的文本哟楷,也可將文本轉(zhuǎn)換為日期瘤载。
1. 使用format()方法將日期轉(zhuǎn)換為指定格式的文本
Date d = new Date();
SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//指定轉(zhuǎn)換的目標(biāo)格式,"yyyy-MM-dd HH:mm:ss"為預(yù)定義字符串卖擅。
String today = s.format(d);//結(jié)果如:2014-06-11 09:55:48
2. 使用parse()方法將文本轉(zhuǎn)換為日期
String day = "2014年02月14日 10:30:20";
SimpleDateFormat s = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");//“yyyy年MM月dd日 HH:mm:ss” 指定了字
符串的日期格式鸣奔,調(diào)用 parse() 方法將文本轉(zhuǎn)換為日期。
Date date = s.parse(day);//結(jié)果如:Fri Feb 14 10:30:20 CST 2014
注意:
1惩阶、調(diào)用SimpleDateFormat對(duì)象的parse()方法時(shí)可能會(huì)出現(xiàn)轉(zhuǎn)換異常挎狸,即ParseException,因此需要進(jìn)行異常處理断楷。
2锨匆、指定日期格式中的月MM和小時(shí)HH必須大寫,小寫結(jié)果會(huì)不同的冬筒。
3恐锣、使用Date 類時(shí)需要導(dǎo)入java.util包,使用SimpleDateFormat時(shí)需要導(dǎo)入java.text包舞痰。
Calendar 類的應(yīng)用
Calendar 類
1.通過(guò)調(diào)用 getInstance() 靜態(tài)方法獲取一個(gè) Calendar 對(duì)象---對(duì)象已初始化
Calendar c = Calendar.getInstance();
2.通過(guò)調(diào)用 get() 方法獲取日期時(shí)間信息
int month=c.get(Calendar.MONTH)+1;----0表示1月份
3.提供 getTime() 方法土榴,用來(lái)獲取 Date 對(duì)象
Date date=c.getTime();----將Calender對(duì)象轉(zhuǎn)換為Date對(duì)象
4.通過(guò) getTimeInMillis() 方法,獲取此 Calendar 的時(shí)間值
long time=c.getTimeInMillis();----獲取當(dāng)前毫秒
使用 Math 類操作數(shù)據(jù)
Math 類位于 java.lang 包中匀奏,包含用于執(zhí)行基本數(shù)學(xué)運(yùn)算的方法鞭衩, Math 類的所有方法都是靜態(tài)方法,所以使用該類中的方法時(shí)娃善,可以直接使用類名.方法名论衍,如: Math.round();
集合框架
1.常用容器繼承關(guān)系圖
Iterator不是容器,只是一個(gè)操作遍歷集合的方法
2.Collection和Map
在Java容器中一共定義了2種集合, 頂層接口分別是Collection和Map聚磺。但是這2個(gè)接口都不能直接被實(shí)現(xiàn)使用坯台,分別代表兩種不同類型的容器。
Collection:是容器繼承關(guān)系中的頂層接口瘫寝。是一組對(duì)象元素組蜒蕾。有些容器允許重復(fù)元素有的不允許稠炬,有些有序有些無(wú)序。 JDK不直接提供對(duì)于這個(gè)接口的實(shí)現(xiàn)咪啡,但是提供繼承與該接口的子接口比如 List Set首启。
接口定義:
public interface Collection<E> extends Iterable<E> {
...
}
泛型<E>即該Collection中元素對(duì)象的類型,繼承的Iterable是定義的一個(gè)遍歷操作接口撤摸,采用hasNext next的方式進(jìn)行遍歷毅桃。
幾個(gè)重要的接口方法:
add(E e)確保此 collection 包含指定的元素(可選操作)。
clear()移除此 collection 中的所有元素(可選操作)准夷。
contains(Object o)如果此 collection 包含指定的元素钥飞,則返回 true。
isEmpty()如果此 collection 不包含元素衫嵌,則返回 true读宙。
iterator()返回在此 collection 的元素上進(jìn)行迭代的迭代器。
remove(Object o)從此 collection 中移除指定元素的單個(gè)實(shí)例楔绞,如果存在的話(可選操作)结闸。
retainAll(Collection<?> c)僅保留此 collection 中那些也包含在指定 collection 的元素(可選操作)。
size()返回此 collection 中的元素?cái)?shù)
toArray()返回包含此 collection 中所有元素的數(shù)組
toArray(T[] a)返回包含此 collection 中所有元素的數(shù)組墓律;返回?cái)?shù)組的運(yùn)行時(shí)類型與指定數(shù)組的運(yùn)行時(shí)類型相同
Map:一個(gè)保存鍵值映射的對(duì)象膀估。 映射Map中不能包含重復(fù)的key幔亥,每一個(gè)key最多對(duì)應(yīng)一個(gè)value耻讽。
Map集合提供3種遍歷訪問(wèn)方法:
1.獲得所有key的集合然后通過(guò)key訪問(wèn)value。
2.獲得value的集合帕棉。
3.獲得key-value鍵值對(duì)的集合(key-value鍵值對(duì)其實(shí)是一個(gè)對(duì)象针肥,里面分別有key和value)。
Map的訪問(wèn)順序取決于Map的遍歷訪問(wèn)方法的遍歷順序香伴。 有的Map慰枕,比如TreeMap可以保證訪問(wèn)順序,但是有的比如HashMap即纲,無(wú)法保證訪問(wèn)順序具帮。
接口定義如下:
public interface Map<K,V> {
...
interface Entry<K,V> {
K getKey();
V getValue();
...
}
}
泛型<K,V>分別代表key和value的類型。這時(shí)候注意到還定義了一個(gè)內(nèi)部接口Entry低斋,其實(shí)每一個(gè)鍵值對(duì)都是一個(gè)Entry的實(shí)例關(guān)系對(duì)象蜂厅,所以Map實(shí)際其實(shí)就是Entry的一個(gè)Collection,
然后Entry里面包含key膊畴,value掘猿。再設(shè)定key不重復(fù)的規(guī)則,自然就演化成了Map唇跨。(個(gè)人理解)
幾個(gè)重要的接口方法:
clear()從此映射中移除所有映射關(guān)系
containsKey(Object key)如果此映射包含指定鍵的映射關(guān)系稠通,則返回 true衬衬。
containsValue(Object value)如果此映射將一個(gè)或多個(gè)鍵映射到指定值,則返回 true改橘。
entrySet()返回此映射中包含的映射關(guān)系的 Set 視圖滋尉。
equals(Object o) 比較指定的對(duì)象與此映射是否相等。
get(Object key)返回指定鍵所映射的值飞主;如果此映射不包含該鍵的映射關(guān)系兼砖,則返回 null。
isEmpty()如果此映射未包含鍵-值映射關(guān)系既棺,則返回 true讽挟。
keySet()返回此映射中包含的鍵的 Set 視圖。
put(K key, V value)將指定的值與此映射中的指定鍵關(guān)聯(lián)(可選操作)丸冕。
putAll(Map<? extends K,? extends V> m)從指定映射中將所有映射關(guān)系復(fù)制到此映射中(可選操作)耽梅。
remove(Object key)如果存在一個(gè)鍵的映射關(guān)系,則將其從此映射中移除(可選操作)胖烛。
size()返回此映射中的鍵-值映射關(guān)系數(shù)眼姐。
values()返回此映射中包含的值的 Collection 視圖。
3個(gè)遍歷Map的方法:
1.Set<K> keySet():會(huì)返回所有key的Set集合佩番,因?yàn)閗ey不可以重復(fù)众旗,所以返回的是Set格式,而不是List格式趟畏。
獲取到所有key的Set集合后贡歧,由于Set是Collection類型的,所以可以通過(guò)Iterator去遍歷所有的key赋秀,然后再通過(guò)get方法獲取value
如下:
Map<String,String> map = new HashMap<String,String>();
map.put("01", "zhangsan");
map.put("02", "lisi");
map.put("03", "wangwu");
Set<String> keySet = map.keySet();//先獲取map集合的所有鍵的Set集合
Iterator<String> it = keySet.iterator();//有了Set集合利朵,就可以獲取其迭代器。
while(it.hasNext()) {
String key = it.next();
String value = map.get(key);//有了鍵可以通過(guò)map集合的get方法獲取其對(duì)應(yīng)的值猎莲。
System.out.println("key: "+key+"-->value: "+value);//獲得key和value值
}
2.Collection<V> values():直接獲取values的集合绍弟,無(wú)法再獲取到key。所以如果只需要value的場(chǎng)景可以用這個(gè)方法著洼。獲取到后使用Iterator去遍歷所有的value樟遣。
如下:
Map<String,String> map = new HashMap<String,String>();
map.put("01", "zhangsan");
map.put("02", "lisi");
map.put("03", "wangwu");
Collection<String> collection = map.values();//返回值是個(gè)值的Collection集合
System.out.println(collection);
3.Set< Map.Entry< K, V>> entrySet():是將整個(gè)Entry對(duì)象作為元素返回所有的數(shù)據(jù)。然后遍歷Entry身笤,分別再通過(guò)getKey和getValue獲取key和value豹悬。
如下:
Map<String,String> map = new HashMap<String,String>();
map.put("01", "zhangsan");
map.put("02", "lisi");
map.put("03", "wangwu");
//通過(guò)entrySet()方法將map集合中的映射關(guān)系取出(這個(gè)關(guān)系就是Map.Entry類型)
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//將關(guān)系集合entrySet進(jìn)行迭代,存放到迭代器中
Iterator<Map.Entry<String, String>> it = entrySet.iterator();
while(it.hasNext()) {
Map.Entry<String, String> me = it.next();//獲取Map.Entry關(guān)系對(duì)象me
String key = me.getKey();//通過(guò)關(guān)系對(duì)象獲取key
String value = me.getValue();//通過(guò)關(guān)系對(duì)象獲取value
}
通過(guò)以上3種遍歷方式我們可以知道展鸡,如果你只想獲取key屿衅,建議使用keySet。如果只想獲取value莹弊,建議使用values涤久。如果key value希望遍歷涡尘,建議使用entrySet。
(雖然通過(guò)keySet可以獲得key再間接獲得value响迂,但是效率沒(méi)entrySet高考抄,不建議使用這種方法)
3.List、Set和Queue
List和Set蔗彤。他們2個(gè)是繼承Collection的子接口川梅,就是說(shuō)他們也都是負(fù)責(zé)存儲(chǔ)單個(gè)元素的容器。
最大的區(qū)別如下:
1.List是存儲(chǔ)的元素容器是有個(gè)有序的可以索引到元素的容器然遏,并且里面的元素可以重復(fù)贫途。
2.Set里面和List最大的區(qū)別是Set里面的元素對(duì)象不可重復(fù)。
List:一個(gè)有序的Collection(或者叫做序列)待侵。使用這個(gè)接口可以精確掌控元素的插入丢早,還可以根據(jù)index獲取相應(yīng)位置的元素。
不像Set秧倾,list允許重復(fù)元素的插入怨酝。有人希望自己實(shí)現(xiàn)一個(gè)list,禁止重復(fù)元素那先,并且在重復(fù)元素插入的時(shí)候拋出異常农猬,但是我們不建議這么做。
List提供了一種特殊的iterator遍歷器售淡,叫做ListIterator斤葱。這種遍歷器允許遍歷時(shí)插入,替換勋又,刪除苦掘,雙向訪問(wèn)换帜。 并且還有一個(gè)重載方法允許從一個(gè)指定位置開(kāi)始遍歷楔壤。
List接口新增的接口,會(huì)發(fā)現(xiàn)add惯驼,get這些都多了index參數(shù)蹲嚣,說(shuō)明在原來(lái)Collection的基礎(chǔ)上,List是一個(gè)可以指定索引祟牲,有序的容器隙畜。
在這注意以下添加的2個(gè)新Iteractor方法:
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
ListIterator的代碼:
public interface ListIterator<E> extends Iterator<E> {
// Query Operations
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
一個(gè)集合在遍歷過(guò)程中進(jìn)行插入刪除操作很容易造成錯(cuò)誤,特別是無(wú)序隊(duì)列说贝,是無(wú)法在遍歷過(guò)程中進(jìn)行這些操作的议惰。
但是List是一個(gè)有序集合,所以在這實(shí)現(xiàn)了一個(gè)ListIteractor乡恕,可以在遍歷過(guò)程中進(jìn)行元素操作言询,并且可以雙向訪問(wèn)俯萎。
List的實(shí)現(xiàn)類,ArrayList和LinkedList.
1.ArrayList
ArrayList是一個(gè)實(shí)現(xiàn)了List接口的可變數(shù)組
可以插入null
它的size, isEmpty, get, set, iterator,add這些方法的時(shí)間復(fù)雜度是O(1),如果add n個(gè)數(shù)據(jù)則時(shí)間復(fù)雜度是O(n).
ArrayList不是synchronized的运杭。
然后我們來(lái)簡(jiǎn)單看下ArrayList源碼實(shí)現(xiàn)夫啊。這里只寫部分源碼分析。
所有元素都是保存在一個(gè)Object數(shù)組中辆憔,然后通過(guò)size控制長(zhǎng)度撇眯。
transient Object[] elementData;
private int size;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
其實(shí)在每次add的時(shí)候會(huì)判斷數(shù)據(jù)長(zhǎng)度,如果不夠的話會(huì)調(diào)用Arrays.copyOf虱咧,復(fù)制一份更長(zhǎng)的數(shù)組熊榛,并把前面的數(shù)據(jù)放進(jìn)去。
我們?cè)倏聪聄emove的代碼是如何實(shí)現(xiàn)的腕巡。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
其實(shí)就是直接使用System.arraycopy把需要?jiǎng)h除index后面的都往前移一位然后再把最后一個(gè)去掉来候。
2.LinkedList
LinkedList是一個(gè)鏈表維護(hù)的序列容器。和ArrayList都是序列容器逸雹,一個(gè)使用數(shù)組存儲(chǔ)营搅,一個(gè)使用鏈表存儲(chǔ)。
數(shù)組和鏈表2種數(shù)據(jù)結(jié)構(gòu)的對(duì)比:
1.查找方面梆砸。數(shù)組的效率更高转质,可以直接索引出查找,而鏈表必須從頭查找帖世。
2.插入刪除方面休蟹。特別是在中間進(jìn)行插入刪除,這時(shí)候鏈表體現(xiàn)出了極大的便利性日矫,只需要在插入或者刪除的地方斷掉鏈然后插入或者移除元素赂弓,然后再將前后鏈重新組裝,但是數(shù)組必須重新復(fù)制一份將所有數(shù)據(jù)后移或者前移哪轿。
3.在內(nèi)存申請(qǐng)方面盈魁,當(dāng)數(shù)組達(dá)到初始的申請(qǐng)長(zhǎng)度后,需要重新申請(qǐng)一個(gè)更大的數(shù)組然后把數(shù)據(jù)遷移過(guò)去才行窃诉。而鏈表只需要?jiǎng)討B(tài)創(chuàng)建即可杨耙。
如上LinkedList和ArrayList的區(qū)別也就在此.
總結(jié):
List實(shí)現(xiàn) 使用場(chǎng)景 數(shù)據(jù)結(jié)構(gòu)
ArrayList 數(shù)組形式訪問(wèn)List鏈?zhǔn)郊蠑?shù)據(jù),元素可重復(fù)飘痛,訪問(wèn)元素較快 數(shù)組
LinkedList 鏈表方式的List鏈?zhǔn)郊仙耗ぃ乜芍貜?fù),元素的插入刪除較快 雙向鏈表
Vector: 底層是數(shù)組數(shù)據(jù)結(jié)構(gòu)宣脉。線程同步车柠。被ArrayList替代了。因?yàn)樾实汀?/p>
4.Set
Set的核心概念就是集合內(nèi)所有元素不重復(fù)。在Set這個(gè)子接口中沒(méi)有在Collection特別實(shí)現(xiàn)什么額外的方法竹祷,應(yīng)該只是定義了一個(gè)Set概念介蛉。下面我們來(lái)看Set的幾個(gè)常用的實(shí)現(xiàn)HashSet、LinkedHashSet溶褪、TreeSet
HashSet:
HashSet實(shí)現(xiàn)了Set接口币旧,基于HashMap進(jìn)行存儲(chǔ)。遍歷時(shí)不保證順序猿妈,并且不保證下次遍歷的順序和之前一樣吹菱。HashSet中允許null元素。
進(jìn)入到HashSet源碼中我們發(fā)現(xiàn)彭则,所有數(shù)據(jù)存儲(chǔ)在:
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
意思就是HashSet的集合其實(shí)就是HashMap的key的集合鳍刷,然后HashMap的val默認(rèn)都是PRESENT。HashMap的定義即是key不重復(fù)的集合俯抖。使用HashMap實(shí)現(xiàn)输瓜,這樣HashSet就不需要再實(shí)現(xiàn)一遍。
所以所有的add芬萍,remove等操作其實(shí)都是HashMap的add尤揣、remove操作。遍歷操作其實(shí)就是HashMap的keySet的遍歷,舉例如下
...
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public void clear() {
map.clear();
}
...
LinkedHashSet:
LinkedHashSet的核心概念相對(duì)于HashSet來(lái)說(shuō)就是一個(gè)可以保持順序的Set集合柬祠。HashSet是無(wú)序的北戏,LinkedHashSet會(huì)根據(jù)add,remove這些操作的順序在遍歷時(shí)返回固定的集合順序漫蛔。這個(gè)順序不是元素的大小順序嗜愈,而是可以保證2次遍歷的順序是一樣的。
類似HashSet基于HashMap的源碼實(shí)現(xiàn)莽龟,LinkedHashSet的數(shù)據(jù)結(jié)構(gòu)是基于LinkedHashMap蠕嫁。過(guò)多的就不說(shuō)了。
TreeSet:
TreeSet即是一組有次序的集合毯盈,如果沒(méi)有指定排序規(guī)則Comparator剃毒,則會(huì)按照自然排序。(自然排序即e1.compareTo(e2) == 0作為比較)
注意:TreeSet內(nèi)的元素必須實(shí)現(xiàn)Comparable接口奶镶。
TreeSet源碼的算法即基于TreeMap迟赃,具體算法在說(shuō)明TreeMap的時(shí)候進(jìn)行解釋。
總結(jié):
Set實(shí)現(xiàn) 使用場(chǎng)景 數(shù)據(jù)結(jié)構(gòu)
HashSet 無(wú)序的厂镇、無(wú)重復(fù)的數(shù)據(jù)集合 基于HashMap
LinkedSet 維護(hù)次序的HashSet 基于LinkedHashMap
TreeSet 保持元素大小次序的集合,元素需要實(shí)現(xiàn)Comparable接口 基于TreeMap
4.HashMap左刽、LinkedHashMap捺信、TreeMap和WeakHashMap
HashMap就是最基礎(chǔ)最常用的一種Map,它無(wú)序,以散列表的方式進(jìn)行存儲(chǔ)迄靠。之前提到過(guò)秒咨,HashSet就是基于HashMap,只使用了HashMap的key作為單個(gè)元素存儲(chǔ)掌挚。
HashMap的訪問(wèn)方式就是繼承于Map的最基礎(chǔ)的3種方式雨席,詳細(xì)見(jiàn)上。在這里我具體分析一下HashMap的底層數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)吠式。
在看HashMap源碼前陡厘,先理解一下他的存儲(chǔ)方式-散列表(哈希表)。像之前提到過(guò)的用數(shù)組存儲(chǔ)特占,用鏈表存儲(chǔ)糙置。哈希表是使用數(shù)組和鏈表的組合的方式進(jìn)行存儲(chǔ)。(具體哈希表的概念自行搜索)如下圖就是HashMap采用的存儲(chǔ)方法是目。
詳細(xì):http://www.reibang.com/p/047e33fdefd2
總結(jié)
Map實(shí)現(xiàn) 使用場(chǎng)景 數(shù)據(jù)結(jié)構(gòu)
HashMap 哈希表存儲(chǔ)鍵值對(duì)谤饭,key不重復(fù),無(wú)序 哈希散列表
LinkedHashMap 是一個(gè)可以記錄插入順序和訪問(wèn)順序的HashMap 存儲(chǔ)方式是哈希散列表懊纳,但是維護(hù)了頭尾指針用來(lái)記錄順序
TreeMap 具有元素排序功能 紅黑樹(shù)
WeakHashMap 弱鍵映射揉抵,映射之外無(wú)引用的鍵,可以被垃圾回收 哈希散列表