Java核心知识体系6:集合框架详解
Java核心知識(shí)體系1:泛型機(jī)制詳解
Java核心知識(shí)體系2:注解機(jī)制詳解
Java核心知識(shí)體系3:異常機(jī)制詳解
Java核心知識(shí)體系4:AOP原理和切面應(yīng)用
Java核心知識(shí)體系5:反射機(jī)制詳解
1 集合框架圖總覽
我們來簡(jiǎn)單解讀下上面這個(gè)框架圖:
- 所有集合類都位于java.util包下
- Iterator是遍歷集合的工具,我們經(jīng)常通過Iterator迭代器來遍歷集合。我們說Collection依賴于Iterator,是因?yàn)镃ollection的實(shí)現(xiàn)類都要實(shí)現(xiàn)iterator()函數(shù),返回一個(gè)Iterator對(duì)象。ListIterator主要作用就是遍歷List。
- Java的集合類主要由兩個(gè)接口派生而出:Collection和Map,作為Java集合框架的根接口,這兩個(gè)接口包含了一些子接口和實(shí)現(xiàn)類。
- 集合接口:即圖中的 LinkIterator、List、Set、Queue、SortedMap、SortedMap 6個(gè)接口(即短虛線框部分),表示不同集合類型,是集合框架的基礎(chǔ)。
- 抽象類:即圖中的 AbstractCollection、AbstractList、AbstractSet、AbstractMap、AbstractSequentialList 5個(gè)抽象類(長(zhǎng)虛線框部分),抽象類只是對(duì)集合接口的部分實(shí)現(xiàn),有需要的話可以繼續(xù)擴(kuò)展,完善自定義集合類。
- 實(shí)現(xiàn)類:即圖片中LinkHashMap、TreeMap等8個(gè)實(shí)現(xiàn)類(實(shí)線框部分),主要是對(duì)接口的具體實(shí)現(xiàn)。
- Collection 接口包含一組允許重復(fù)的對(duì)象
- Set 接口繼承 Collection,但是集合內(nèi)的元素不重復(fù)。Set的實(shí)現(xiàn)類有HastSet和TreeSet。HashSet依賴于HashMap,它實(shí)際上是通過HashMap實(shí)現(xiàn)的;TreeSet依賴于TreeMap,它實(shí)際上是通過TreeMap實(shí)現(xiàn)的。
- List 接口繼承 Collection,集合內(nèi)元素允許重復(fù),但維護(hù)了元素的插入順序,所以是個(gè)有序隊(duì)列。每一個(gè)元素都有它的索引。第一個(gè)元素的索引值是0。List的實(shí)現(xiàn)類有LinkedList, ArrayList, Vector, Stack。
- Map接口是鍵-值對(duì)象頂層接口,下面還包含了一些子接口和實(shí)現(xiàn)類。AbstractMap是個(gè)抽象類,它實(shí)現(xiàn)了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是繼承于AbstractMap。Hashtable雖然繼承于Dictionary,但它實(shí)現(xiàn)了Map接口。
- Set、List和Map是集合的三大類:
- List:有序集合,集合中元素可重復(fù),訪問元素可以根據(jù)元素索引訪問。
- Set:無序集合,集合中元素不可以重復(fù),訪問集合中的元素只能根據(jù)元素自身信息來訪問(因此元素不允許重復(fù))。
- Map:Key-value模式的鍵值對(duì)元素,訪問時(shí)根據(jù)元素key來讀取對(duì)應(yīng)的value。
- Arrays和Collections是操作數(shù)組、集合的兩個(gè)工具類。
完成對(duì)上面框架的整體介紹之后,我們接著對(duì)每個(gè)類別進(jìn)行詳細(xì)的分析。
2 Collection接口
Collection接口是處理對(duì)象集合的根接口,其中定義了很多對(duì)元素進(jìn)行操作的方法。Collection接口有兩個(gè)主要的子接口List和Set,注意Map不是Collection的子接口,這個(gè)要牢記。
Collection接口中的方法如下:
其中,有幾個(gè)比較常用的方法,比如方法add()添加一個(gè)元素到集合中,addAll()將指定集合中的所有元素添加到集合中,contains()方法檢測(cè)集合中是否包含指定的元素,toArray()方法返回一個(gè)表示集合的數(shù)組。
另外,Collection中有一個(gè)iterator()函數(shù),它的作用是返回一個(gè)Iterator接口。通常,我們通過Iterator迭代器來遍歷集合。ListIterator是List接口所特有的,在List接口中,通過ListIterator()返回一個(gè)ListIterator對(duì)象。
Collection接口有兩個(gè)常用的子接口,下面會(huì)詳細(xì)介紹。
2.1 List接口
List集合代表一個(gè)有序集合,集合中每個(gè)元素都有其對(duì)應(yīng)的順序索引。List集合允許使用重復(fù)元素,可以通過索引來訪問指定位置的集合元素。
List接口繼承于Collection接口,它可以定義一個(gè)允許重復(fù)的有序集合。因?yàn)長(zhǎng)ist中的元素是有序的,所以我們可以通過使用索引(元素在List中的位置,類似于數(shù)組下標(biāo))來訪問List中的元素,這類似于Java的數(shù)組。
List接口為Collection直接接口。List所代表的是有序的Collection,即它用某種特定的插入順序來維護(hù)元素順序。用戶可以對(duì)列表中每個(gè)元素的插入位置進(jìn)行精確地控制,同時(shí)可以根據(jù)元素的整數(shù)索引(在列表中的位置)訪問元素,并搜索列表中的元素。實(shí)現(xiàn)List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
2.1.1 ArrayList
ArrayList是一個(gè)動(dòng)態(tài)數(shù)組,也是我們最常用的集合。它允許任何符合規(guī)則的元素插入甚至包括null。每一個(gè)ArrayList都有一個(gè)初始容量(10),該容量代表了數(shù)組的大小。隨著容器中的元素不斷增加,容器的大小也會(huì)隨著增加。在每次向容器中增加元素的同時(shí)都會(huì)進(jìn)行容量檢查,當(dāng)快溢出時(shí),就會(huì)進(jìn)行擴(kuò)容操作。所以如果我們明確所插入元素的多少,最好指定一個(gè)初始容量值,避免過多的進(jìn)行擴(kuò)容操作而浪費(fèi)時(shí)間、效率。
size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時(shí)間運(yùn)行。add 操作以分?jǐn)偟墓潭〞r(shí)間運(yùn)行,也就是說,添加 n 個(gè)元素需要 O(n) 時(shí)間(由于要考慮到擴(kuò)容,所以這不只是添加元素會(huì)帶來分?jǐn)偣潭〞r(shí)間開銷那樣簡(jiǎn)單)。
ArrayList擅長(zhǎng)于隨機(jī)訪問。同時(shí)ArrayList是非同步的。
2.1.2 LinkedList
同樣實(shí)現(xiàn)List接口的LinkedList與ArrayList不同,ArrayList是一個(gè)動(dòng)態(tài)數(shù)組,而LinkedList是一個(gè)雙向鏈表。所以它除了有ArrayList的基本操作方法外還額外提供了get,remove,insert方法在LinkedList的首部或尾部。
由于實(shí)現(xiàn)的方式不同,LinkedList不能隨機(jī)訪問,它所有的操作都是要按照雙重鏈表的需要執(zhí)行。在列表中索引的操作將從開頭或結(jié)尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價(jià)在List中進(jìn)行插入和刪除操作。
與ArrayList一樣,LinkedList也是非同步的。如果多個(gè)線程同時(shí)訪問一個(gè)List,則必須自己實(shí)現(xiàn)訪問同步。一種解決方法是在創(chuàng)建List時(shí)構(gòu)造一個(gè)同步的List:
List list = Collections.synchronizedList(new LinkedList(...));
2.1.3 Vector
與ArrayList相似,但是Vector是同步的。所以說Vector是線程安全的動(dòng)態(tài)數(shù)組。它的操作與ArrayList幾乎一樣。
的
2.1.4 Stack
Stack繼承自Vector,實(shí)現(xiàn)一個(gè)后進(jìn)先出的堆棧。Stack提供5個(gè)額外的方法使得Vector得以被當(dāng)作堆棧使用。基本的push和pop 方法,還有peek方法得到棧頂?shù)脑兀琫mpty方法測(cè)試堆棧是否為空,search方法檢測(cè)一個(gè)元素在堆棧中的位置。Stack剛創(chuàng)建后是空棧。
2.2 Set接口
Set是一種不包括重復(fù)元素的Collection。它維持它自己的內(nèi)部排序,所以隨機(jī)訪問沒有任何意義。與List一樣,它同樣允許null的存在但是僅有一個(gè)。由于Set接口的特殊性,所有傳入Set集合中的元素都必須不同,同時(shí)要注意任何可變對(duì)象,如果在對(duì)集合中元素進(jìn)行操作時(shí),導(dǎo)致element1.equals(element2)為true,則必定會(huì)產(chǎn)生數(shù)據(jù)沖突的問題。Set接口有三個(gè)具體實(shí)現(xiàn)類,分別是
- 散列集HashSet
- 鏈?zhǔn)缴⒘屑疞inkedHashSet
- 樹形集TreeSet
Set是一種不包含重復(fù)的元素的Collection,無序,即任意的兩個(gè)元素element1和element2都有element1.equals(element2)為false,Set最多可以有一個(gè)null元素。需要注意的是,雖然Set中元素沒有順序性,但是元素在set中的位置是由該元素的HashCode決定的,所以具體的位置其實(shí)是固定的。
此外需要說明一點(diǎn),在set接口中的不重復(fù)是有特殊要求的。
舉一個(gè)例子:對(duì)象A和對(duì)象B,本來是不同的兩個(gè)對(duì)象,正常情況下它們是能夠放入到Set里面的,但是如果對(duì)象A和B的都重寫了hashcode和equals方法,并且重寫后的hashcode和equals方法是相同的話。那么A和B是不能同時(shí)放入到Set集合中去的,也就是Set集合中的去重和hashcode與equals方法直接相關(guān)。
為了更好地理解,請(qǐng)看下面的例子:
private static void setWork() {
Set<String> set = new HashSet<>();
set.add("Brand1");
set.add("Brand2");
set.add("Brand1");
System.out.println("Set Size:" + set.size());
System.out.println("Set Elements:" + set.toString());
//再次添加一個(gè)字符串對(duì)象 Brand2,然后通過equals方法比較,發(fā)現(xiàn)是相等的,所以添加失敗返回false
boolean result = set.add(new String("Brand2"));
System.out.println(result);
System.out.println(set);
}
執(zhí)行結(jié)果:
Set Size:2
Set Elements:[Brand1, Brand2]
false
[Brand1, Brand2]
可以看出,因?yàn)橛衕ashcode和equals方法,用來比較指向的字符串對(duì)象所存儲(chǔ)的字符串是否相等,所以第二個(gè)Brand1加進(jìn)去是無效的。
程序通過new關(guān)鍵字來創(chuàng)建新的字符串對(duì)象Brand2,使用==運(yùn)算符判斷返回false,使用equals方法比較返回true,所以同樣不能添加到Set集合中,最終還是兩個(gè)元素。
2.2.1 HashSet
HashSet 是一個(gè)沒有重復(fù)元素的集合。它是由HashMap實(shí)現(xiàn)的,不保證元素的順序(這里所說的沒有順序是指:元素插入的順序與輸出的順序不一致),而且HashSet允許使用null 元素。HashSet是非同步的,如果多個(gè)線程同時(shí)訪問一個(gè)哈希set,而其中至少一個(gè)線程修改了該set,那么它必須保持外部同步。 HashSet按Hash算法來存儲(chǔ)集合的元素,因此具有很好的存取和查找性能。
HashSet的實(shí)現(xiàn)方式大致如下,通過一個(gè)HashMap存儲(chǔ)元素,元素是存放在HashMap的Key中,而Value統(tǒng)一使用一個(gè)Object對(duì)象。
HashSet使用和理解中容易出現(xiàn)的誤區(qū):
- HashSet中存放null值
HashSet中是允許存入null值的,但是在HashSet中僅僅能夠存入一個(gè)null值。 - HashSet中存儲(chǔ)元素的位置是固定的
HashSet中存儲(chǔ)的元素的是無序的,這個(gè)沒什么好說的,但是由于HashSet底層是基于Hash算法實(shí)現(xiàn)的,使用了hashcode,所以HashSet中相應(yīng)的元素的位置是固定的。 - 必須小心操作可變對(duì)象(Mutable Object)。
如果一個(gè)Set中的可變?cè)馗淖兞俗陨頎顟B(tài)導(dǎo)致Object.equals(Object)=true將導(dǎo)致一些問題。
2.2.2 LinkedHashSet
LinkedHashSet繼承自HashSet,其底層是基于LinkedHashMap來實(shí)現(xiàn)的,有序,非同步。LinkedHashSet集合同樣是根據(jù)元素的hashCode值來決定元素的存儲(chǔ)位置,但是它同時(shí)使用鏈表維護(hù)元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當(dāng)遍歷該集合時(shí)候,LinkedHashSet將會(huì)以元素的添加順序訪問集合的元素。
2.2.3 TreeSet
TreeSet是一個(gè)有序集合,其底層是基于TreeMap實(shí)現(xiàn)的,非線程安全。TreeSet可以確保集合元素處于排序狀態(tài)。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序?yàn)槟J(rèn)的排序方式。當(dāng)我們構(gòu)造TreeSet時(shí),若使用不帶參數(shù)的構(gòu)造函數(shù),則TreeSet的使用自然比較器;若用戶需要使用自定義的比較器,則需要使用帶比較器的參數(shù)。
注意:TreeSet集合不是通過hashcode和equals函數(shù)來比較元素的.它是通過compare或者comparaeTo函數(shù)來判斷元素是否相等.compare函數(shù)通過判斷兩個(gè)對(duì)象的id,相同的id判斷為重復(fù)元素,不會(huì)被加入到集合中。
3 Map接口
Map與List、Set接口不同,它是由一系列鍵值對(duì)組成的集合,提供了key到Value的映射。同時(shí)它也沒有繼承Collection。在Map中它保證了key與value之間的一一對(duì)應(yīng)關(guān)系。也就是說一個(gè)key對(duì)應(yīng)一個(gè)value,所以它不能存在相同的key值,當(dāng)然value值可以相同。
3.1 HashMap
以哈希表數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),查找對(duì)象時(shí)通過哈希函數(shù)計(jì)算其位置,它是為快速查詢而設(shè)計(jì)的,其內(nèi)部定義了一個(gè)hash表數(shù)組(Entry[] table),元素會(huì)通過哈希轉(zhuǎn)換函數(shù)將元素的哈希地址轉(zhuǎn)換成數(shù)組中存放的索引,如果有沖突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來,可能通過查看HashMap.Entry的源碼它是一個(gè)單鏈表結(jié)構(gòu)。
3.2 LinkedHashMap
LinkedHashMap是HashMap的一個(gè)子類,它保留插入的順序,如果需要輸出的順序和輸入時(shí)的相同,那么就選用LinkedHashMap。
LinkedHashMap是Map接口的哈希表和鏈接列表實(shí)現(xiàn),具有可預(yù)知的迭代順序。此實(shí)現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。
LinkedHashMap實(shí)現(xiàn)與HashMap的不同之處在于,后者維護(hù)著一個(gè)運(yùn)行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。
根據(jù)鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序(調(diào)用get方法)的鏈表。默認(rèn)是按插入順序排序,如果指定按訪問順序排序,那么調(diào)用get方法后,會(huì)將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。
注意,此實(shí)現(xiàn)不是同步的。如果多個(gè)線程同時(shí)訪問鏈接的哈希映射,而其中至少一個(gè)線程從結(jié)構(gòu)上修改了該映射,則它必須保持外部同步。
由于LinkedHashMap需要維護(hù)元素的插入順序,因此性能略低于HashMap的性能,但在迭代訪問Map里的全部元素時(shí)將有很好的性能,因?yàn)樗枣湵韥砭S護(hù)內(nèi)部順序。
3.3 TreeMap
TreeMap 是一個(gè)有序的key-value集合,非同步,基于紅黑樹(Red-Black tree)實(shí)現(xiàn),每一個(gè)key-value節(jié)點(diǎn)作為紅黑樹的一個(gè)節(jié)點(diǎn)。TreeMap存儲(chǔ)時(shí)會(huì)進(jìn)行排序的,會(huì)根據(jù)key來對(duì)key-value鍵值對(duì)進(jìn)行排序,其中排序方式也是分為兩種,一種是自然排序,一種是定制排序,具體取決于使用的構(gòu)造方法。
自然排序:TreeMap中所有的key必須實(shí)現(xiàn)Comparable接口,并且所有的key都應(yīng)該是同一個(gè)類的對(duì)象,否則會(huì)報(bào)ClassCastException異常。
定制排序:定義TreeMap時(shí),創(chuàng)建一個(gè)comparator對(duì)象,該對(duì)象對(duì)所有的treeMap中所有的key值進(jìn)行排序,采用定制排序的時(shí)候不需要TreeMap中所有的key必須實(shí)現(xiàn)Comparable接口。
TreeMap判斷兩個(gè)元素相等的標(biāo)準(zhǔn):兩個(gè)key通過compareTo()方法返回0,則認(rèn)為這兩個(gè)key相等。
如果使用自定義的類來作為TreeMap中的key值,且想讓TreeMap能夠良好的工作,則必須重寫自定義類中的equals()方法,TreeMap中判斷相等的標(biāo)準(zhǔn)是:兩個(gè)key通過equals()方法返回為true,并且通過compareTo()方法比較應(yīng)該返回為0。
4 Iterator 與 ListIterator詳解
4.1 Iterator
Iterator的定義如下:
public interface Iterator<E> {}
Java的Iterator(迭代器)是一個(gè)設(shè)計(jì)模式,它使你可以遍歷一個(gè)容器(如列表,集合,隊(duì)列等)。它提供了一種方法來順序訪問聚合對(duì)象的元素,而不需要暴露該對(duì)象的內(nèi)部表示。
Iterator提供的API接口如下:
- boolean hasNext():判斷集合里是否存在下一個(gè)元素。如果有,hasNext()方法返回 true。
- Object next():返回集合里下一個(gè)元素。
- void remove():刪除集合里上一次next方法返回的元素。
參考如下:
import java.util.*;
public class Main {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)ArrayList對(duì)象
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add("Java");
// 獲取該ArrayList的迭代器
Iterator<String> it = list.iterator();
// 使用迭代器遍歷列表中的元素
while(it.hasNext()) {
String element = it.next();
System.out.println(element);
if ("Java".equals(element)) {
it.remove();
}
}
// 移除之后的ArrayList對(duì)象
System.out.println("after remove element 「java」: " + list);
}
}
輸出結(jié)果如下:
Hello
World
Java
remove java element : [Hello, World]
需要注意的點(diǎn)如下:
- Iterator只能單向移動(dòng)。
- Iterator.remove()是唯一安全在迭代過程中修改集合;如果在迭代過程中以任何其它的方式修改了基本集合將會(huì)產(chǎn)生未知的行為。而且每調(diào)用一次next()方法,remove()方法只能被調(diào)用一次,如果違反這個(gè)規(guī)則將拋出一個(gè)異常,因?yàn)榭赡軐?dǎo)致數(shù)據(jù)異常。
4.2 ListIterator
ListIterator是一個(gè)功能更加強(qiáng)大的迭代器, 它繼承于Iterator接口,只能用于各種List類型的訪問。它提供了在列表中插入和刪除元素的方法,以及使用hasPrevious()和previous()方法在迭代過程中向前和向后遍歷列表的功能。
以下是ListIterator的主要方法:
- boolean hasNext(): 返回true如果迭代器有下一個(gè)元素。
- Object next(): 返回迭代器的下一個(gè)元素并將指針移到下一個(gè)元素。
- boolean hasPrevious(): 返回true如果迭代器有前一個(gè)元素。
- Object previous(): 返回迭代器的前一個(gè)元素并將指針移到前一個(gè)元素。
- int nextIndex(): 返回迭代器下一次要訪問的元素的索引。
- int previousIndex(): 返回迭代器上一次訪問的元素的索引。
- void remove(): 從列表中刪除迭代器最后一次返回的元素。
- void add(Object o): 在迭代器指向的位置插入指定的元素。
- void set(E e): 從列表中替換迭代器最后一次返回的元素。
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
由以上定義我們可以推出ListIterator可以:
- 雙向移動(dòng)(向前或者向后遍歷)
- 產(chǎn)生相對(duì)于迭代器在列表中指向的當(dāng)前位置的前一個(gè)和后一個(gè)元素的索引
- 可以使用set()方法替換它訪問過的最后一個(gè)元素
- 可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一個(gè)元素
使用示例:
public static void listIteratorWork() {
// 創(chuàng)建一個(gè) ArrayList
List<String> list = new ArrayList<>();
list.add("Element A");
list.add("Element B");
list.add("Element C");
System.out.println("當(dāng)前列表 : " + list);
// 獲取 ListIterator 對(duì)象
ListIterator<String> listIterator = list.listIterator();
// 使用 hasNext() 和 next() 方法迭代列表
System.out.println("逐一遍歷 : ");
while (listIterator.hasNext()) {
System.out.println(listIterator.next() + ", " + listIterator.previousIndex() + ", " + listIterator.nextIndex());
}
// 在迭代過程中使用 add() 方法添加元素
listIterator.add("Element D");
System.out.println("添加一個(gè)元素之后:" + list);
// 在迭代過程中使用 set() 方法進(jìn)行元素修改
listIterator = list.listIterator(1);
System.out.print("修改一個(gè)元素之后:");
while (listIterator.hasNext()) {
if ("Element D".equals(listIterator.next())) {
listIterator.set("Element replace");
}
}
System.out.println(list);
}
輸出結(jié)果如下:
當(dāng)前列表 : [Element A, Element B, Element C]
逐一遍歷 :
Element A, 0, 1
Element B, 1, 2
Element C, 2, 3
添加一個(gè)元素之后:[Element A, Element B, Element C, Element D]
修改一個(gè)元素之后:[Element A, Element B, Element C, Element replace]
5 面試考點(diǎn)分析
5.1 ArrayList和LinkedList對(duì)比
- ArrayList是實(shí)現(xiàn)了基于動(dòng)態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList基于鏈表的數(shù)據(jù)結(jié)構(gòu)。
- 兩者都是線程不安全,都實(shí)現(xiàn)了Collection接口。
- 數(shù)據(jù)結(jié)構(gòu):ArrayList是基于動(dòng)態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList是基于雙向鏈表的數(shù)據(jù)結(jié)構(gòu)。
- 性能:ArrayList支持隨機(jī)訪問,查詢快,增刪慢,查詢的時(shí)間復(fù)雜度為O(1),插入和刪除的時(shí)間復(fù)雜度為O(n),因?yàn)閷?duì)插入和刪除位置后面的元素進(jìn)行移動(dòng)位置,以保證內(nèi)存的連續(xù)性,所以
- 對(duì)于隨機(jī)訪問get和set,ArrayList絕對(duì)優(yōu)于LinkedList,因?yàn)長(zhǎng)inkedList要移動(dòng)指針。
- 對(duì)于新增和刪除操作add和remove,LinedList比較占優(yōu)勢(shì),因?yàn)锳rrayList要移動(dòng)數(shù)據(jù)。
- 內(nèi)存空間占用:?ArrayList的空 間浪費(fèi)主要體現(xiàn)在在list列表的結(jié)尾會(huì)預(yù)留一定的容量空間,而LinkedList的空間花費(fèi)則體現(xiàn)在它的每一個(gè)元素都需要消耗 比ArrayList更多的空間(因?yàn)橐娣胖苯雍罄^和直接前驅(qū)以及數(shù)據(jù))。
5.2 HashTable與HashMap對(duì)比
- 相同點(diǎn):
- 都實(shí)現(xiàn)了Map、Cloneable、java.io.Serializable接口。
- 都是存儲(chǔ)"鍵值對(duì)(key-value)"的散列表,而且都是采用拉鏈法實(shí)現(xiàn)的。
- 不同點(diǎn):
- 是否安全:HashMap是非線程安全的,HashTable 是線程安全的;HashTable 內(nèi)部的方法基本都經(jīng)過?synchronized?修飾。(如果你要保證線程安全的話就使用 ConcurrentHashMap )
- 同步性:HashTable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的 。
- 對(duì)null值的處理:HashMap的key、value都可為null,HashTable的key、value都不可為null 。
- 基類不同:HashMap繼承于AbstractMap,而Hashtable繼承于Dictionary。
- 支持的遍歷種類不同:HashMap只支持Iterator(迭代器)遍歷。而Hashtable支持Iterator(迭代器)和Enumeration(枚舉器)兩種方式遍歷。
- 操作效率:因?yàn)榫€程安全的問題,HashMap 要比 HashTable 效率高一點(diǎn)。另外,HashTable 基本被淘汰,不要在代碼中使用它;
- 對(duì)Null key 和Null value的支持:?HashMap 中,null 可以作為鍵,這樣的鍵只有一個(gè),可以有一個(gè)或多個(gè)鍵所對(duì)應(yīng)的值為 null。但是在 HashTable 中 put 進(jìn)的鍵值只要有一個(gè) null,直接拋出 NullPointerException
- 初始容量大小和每次擴(kuò)充容量大小的不同 :
- 創(chuàng)建時(shí)如果不指定容量初始值,Hashtable 默認(rèn)的初始大小為11,之后每次擴(kuò)充,容量變?yōu)樵瓉淼?n+1。HashMap 默認(rèn)的初始化大小為16。之后每次擴(kuò)充,容量變?yōu)樵瓉淼?倍。
- 創(chuàng)建時(shí)如果給定了容量初始值,那么 Hashtable 會(huì)直接使用你給定的大小,而 HashMap 會(huì)將其擴(kuò)充為2的冪次方大小。也就是說 HashMap 總是使用2的冪作為哈希表的大小,后面會(huì)介紹到為什么是2的冪次方。
- 底層數(shù)據(jù)結(jié)構(gòu):?JDK1.8 以后的 HashMap 在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長(zhǎng)度大于閾值(默認(rèn)為8)時(shí),將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時(shí)間。Hashtable 沒有這樣的機(jī)制。
5.3 LinkedHashMap和TreeMap比較
LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時(shí),先得到的記錄肯定是先插入的,也可以在構(gòu)造時(shí)用帶參數(shù),按照應(yīng)用次數(shù)排序。在遍歷的時(shí)候會(huì)比HashMap慢。
TreeMap實(shí)現(xiàn)SortMap接口,內(nèi)部實(shí)現(xiàn)是紅黑樹。能夠把它保存的記錄根據(jù)鍵排序,默認(rèn)是按鍵值的升序排序,也可以指定排序的比較器,當(dāng)用Iterator 遍歷TreeMap時(shí),得到的記錄是排過序的。TreeMap不允許key的值為null。非同步的。
5.4 HashSet、LinkedHashSet、TreeSet比較
5.4.1 Set接口
Set不允許包含相同的元素,如果試圖把兩個(gè)相同元素加入同一個(gè)集合中,add方法返回false。
Set判斷兩個(gè)對(duì)象相同不是使用==運(yùn)算符,而是根據(jù)equals方法。也就是說,只要兩個(gè)對(duì)象用equals方法比較返回true,Set就不會(huì)接受這兩個(gè)對(duì)象。
5.4.2 HashSet
HashSet有以下特點(diǎn):
- 不能保證元素的排列順序,順序有可能發(fā)生變化。
- 不是同步的。
- 集合元素可以是null,但只能放入一個(gè)null。
HashSet集合判斷兩個(gè)元素相等的標(biāo)準(zhǔn)是兩個(gè)對(duì)象通過equals方法比較相等,并且兩個(gè)對(duì)象的hashCode()方法返回值也相等。
5.4.3 LinkedHashSet
LinkedHashSet集合同樣是根據(jù)元素的hashCode值來決定元素的存儲(chǔ)位置,但是它同時(shí)使用鏈表維護(hù)元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當(dāng)遍歷該集合時(shí)候,LinkedHashSet將會(huì)以元素的添加順序訪問集合的元素。
LinkedHashSet在迭代訪問Set中的全部元素時(shí),性能比HashSet好,但是插入時(shí)性能稍微遜色于HashSet。
5.4.4 TreeSet類
TreeSet是SortedSet接口的唯一實(shí)現(xiàn)類,TreeSet可以確保集合元素處于排序狀態(tài)。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序?yàn)槟J(rèn)的排序方式。向TreeSet中加入的應(yīng)該是同一個(gè)類的對(duì)象。
TreeSet判斷兩個(gè)對(duì)象不相等的方式是兩個(gè)對(duì)象通過equals方法返回false,或者通過CompareTo方法比較沒有返回0。
5.5 Iterator和ListIterator區(qū)別
- ListIterator有add()方法,可以向List中添加對(duì)象,而Iterator不能
- ListIterator和Iterator都有hasNext()和next()方法,可以實(shí)現(xiàn)順序向后遍歷,但是ListIterator有hasPrevious()和previous()方法,可以實(shí)現(xiàn)逆向(順序向前)遍歷。Iterator就不可以。
- ListIterator可以定位當(dāng)前的索引位置,nextIndex()和previousIndex()可以實(shí)現(xiàn)。Iterator沒有此功能。
- 都可實(shí)現(xiàn)刪除對(duì)象,但是ListIterator可以實(shí)現(xiàn)對(duì)象的修改,set()方法可以實(shí)現(xiàn)。Iierator僅能遍歷,不能修改。
因?yàn)長(zhǎng)istIterator的這些功能,可以方便的實(shí)現(xiàn)對(duì)LinkedList等List數(shù)據(jù)結(jié)構(gòu)的操作。其實(shí),數(shù)組對(duì)象也可以用迭代器來實(shí)現(xiàn)。
5.6 Collection 和 Collections區(qū)別
Java中的Collection和Collections都是用于處理集合的類,但它們有一些重要的區(qū)別。
繼承關(guān)系:Collection是所有集合類的根接口,它定義了集合的基本操作,比如添加元素、刪除元素等。Collections是一個(gè)幫助類,它提供了靜態(tài)方法來操作和操作集合,比如排序、查找等。Collections類通過實(shí)現(xiàn)和實(shí)例化集合類的各種基本操作,讓集合類的操作更加簡(jiǎn)單。
用法:Collection通常用于定義集合類的基本操作,而Collections則提供了各種靜態(tài)方法來操作和操作集合。Collections類中包含了很多有用的靜態(tài)方法,比如排序、查找、復(fù)制等。
總結(jié)
以上是生活随笔為你收集整理的Java核心知识体系6:集合框架详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 理解Go中的零值
- 下一篇: 设计模式(十二)代理