Java集合框架完全解析
1、集合概述
現實生活中集合:很多事物湊在一起。
數學中的集合:具有共同屬性的事物的總體。
Java中的集合類:是一種工具類,就像是容器,儲存任意數量的具有共同屬性的對象。在編程時,常常需要集中存放多個數據,當然我們可以使用數組來保存多個對象。但數組長度不可變化,一旦初始化數組時指定了數組長度,則這個數組長度是不可變的,如果需要保存個數變化的數據,數組就有點無能為力了;而且數組無法保存具有映射關系的數據,如成績表:語文—79,數學—80,這種數據看上去像兩個數組,但這個兩個數組元素之間有一定的關聯關系。
為了保存數量不確定的數據,以及保存具有映射關系的數據(也被稱為關聯數組),Java提供集合類。集合類主要負責保存其他數據,因此集合類也被稱為容器類。所有容器類都位于Java.util包下。集合類和數組不一樣,數組元素既可以是基本類型的值,也可以是對象(實際上保存的是對象的引用);而集合里只能保存對象(實際上也是保存對象的引用,但通常習慣上認為集合里保存的是對象)。
Java集合框架由Java類庫的一系列接口、抽象類以及具體實現類組成。我們這里所說的集合就是把一組對象組織到一起,然后再根據不同的需求操縱這些數據。集合類型就是容納這些對象的一個容器。也就是說,最基本的集合特性就是把一組對象放一起集中管理。根據集合中是否允許有重復的對象、對象組織在一起是否按某種順序等標準來劃分的話,集合類型又可以細分為許多種不同的子類型。
Java集合框架為我們提供了一組基本機制以及這些機制的參考實現,其中基本的集合接口是Collection接口,其他相關的接口還有Iterator接口、RandomAccess接口等。這些集合框架中的接口定義了一個集合類型應該實現的基本機制,Java類庫為我們提供了一些具體集合類型的參考實現,根據對數據組織及使用的不同需求,只需要實現不同的接口即可。Java類庫還為我們提供了一些抽象類,提供了集合類型功能的部分實現,我們也可以在這個基礎上去進一步實現自己的集合類型。
Java集合框架的優勢有以下幾點:
1)這種框架是高性能的。對基本類集(動態數組,鏈接表,樹和散列表)的實現是高效率的。一般很少需要人工去對這些“數據引擎”編寫代碼(如果有的話)。
2)框架允許不同類型的類集以相同的方式和高度互操作方式工作。
3)類集是容易擴展和/或修改的。為了實現這一目標,類集框架被設計成包含一組標準的接口。對這些接口,提供了幾個標準的實現工具(例如LinkedList,HashSet和TreeSet),通常就是這樣使用的。如果你愿意的話,也可以實現你自己的類集。為了方便起見,創建用于各種特殊目的的實現工具。一部分工具可以使你自己的類集實現更加容易。
4)增加了允許將標準數組融合到類集框架中的機制。
2、集合接口和迭代器接口
Java的容器類主要由兩個接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,這兩個接口又包含了一些子接口或實現類。通過Collection與Map接口導出其他子接口及實現類的框架示意圖如下圖所示:
查看jdk中Collection類的源碼后會發現如下內容:
public interface Collection<E> extends Iterable<E> { //實現Collection接口的通用方法 int size(); boolean isEmpty(); boolean contains(Object o); Iterable<E> iterable(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); }通過源碼發現Collection是一個接口類,其繼承了Java迭代器接口Iterable。
Collection接口有三個主要的子接口:List、Set和Queue,注意Map不是Collection的子接口,這個要牢記。Collection中可以存儲無序元素,可以重復組和各自獨立的元素,即其內的每個位置僅持有一個元素,同時允許有多個null元素對象。
JDK不提供Collection接口的具體實現,而是提供了更加具體的子接口(如Set、List和Queue)的實現。那么Collection接口的存在有何作用呢?存在即是道理。
原因在于:為所有容器的實現類(如ArrayList實現了List接口,HashSet實現了Set接口)提供了兩個“標準”的構造函數來實現:一個無參的構造方法;一個帶有Collection類型參數的單參數構造方法。實際上,因為所有通用的容器類都遵從Collection接口,用第二種構造方法允許了容器之間相互的復制。
Collection接口中的方法如下:
其中,iterator方法用于返回一個實現了Iterator接口的對象,可以使用這個迭代器對象依次訪問集合中的元素。Iterator接口包含3個方法:
public interface Iterator<E>{ E next(); boolean hasNext(); void remove(); }通過反復調用next方法,可以逐個訪問集合中的每個元素。但是,如果到達了集合的末尾,next方法將拋出一個NoSuchElementException異常,因此,需要在調用next之前調用hasNext方法。如果迭代器對象還有多個供訪問的元素,這個方法就返回true。如果要查看集合中的所有元素,就請求一個迭代器,并在hasNext返回true后反復調用next方法。例如:
Collection<String> c = ...; Iterator<String> iter = c.iterator(); while(iter.hasNext()){ String element = iter.next(); do something with element }從Java SE5.0起,這個循環可以采用一種更優雅的縮寫方式。用for each循環可以更加簡練地表示同樣的循環操作:
for(String element:c){ do something with element }上面我們一共提到了兩個和迭代器相關的接口:Iterable接口和Iterator接口,從字面意義上來看,前者的意思是“可迭代的”,后者的意思是“迭代器”。所以我們可以這么理解這兩個接口:實現了Iterable接口的類是可迭代的;實現了Iterator接口的類是一個迭代器。
迭代器就是一個我們用來遍歷集合中的對象的東西。也就是說,對于集合,我們不是像對原始類型數組那樣通過直接訪問元素來迭代,而是通過迭代器來遍歷對象。這么做的好處是將對于集合類型的遍歷行為與被遍歷的集合對象分離,這樣一來我們無需關心該集合類型的具體實現是怎樣的。只要獲取這個集合對象的迭代器,便可以遍歷這個集合中的對象了。而像遍歷對象的順序這些細節,全部由它的迭代器來處理。現在我們來梳理一下前面提到的這些東西:首先,Collection接口實現了Iterable<E>接口,這意味著所有實現了Collection接口的具體集合類都是可迭代的。那么既然要迭代,我們就需要一個迭代器來遍歷相應集合中的對象,所以Iterable<E>接口要求我們實現iterator方法,這個方法要返回一個迭代器對象。一個迭代器對象也就是實現了Iterator<E>接口的對象,這個接口要求我們實現hasNext()、next()、remove()這三個方法。其中hasNext方法判斷是否還有下一個元素(即是否遍歷完對象了),next方法會返回下一個元素(若已經沒有下一個元素了,調用它會拋出一個NoSuchElementException異常),remove方法用于移除最近一次調用next方法返回的元素(若沒有調用next方法而直接調用remove方法會報錯)。我們可以想象在開始對集合進行迭代前,有一個指針指向集合第一個元素的前面,第一次調用next方法后,這個指針會“掃過”第一個元素并返回它,調用hasNext方法就是看這個指針后面還有沒有元素了。也就是說這個指針始終指向剛遍歷過的元素和下一個待遍歷的元素之間。
由于Collection與Iterator都是泛型接口,可以編寫操作任何集合類型的實用方法。Java類庫的設計者認為:這些實用方法中的某些方法非常有用,應該將它們提供給用戶使用,這樣,類庫的使用者就不必自己重構這些方法了。當然如果實現Collection接口的每一個類都提供如此多的例行方法將是一件很煩人的事情,為了能夠讓實現者更容易地實現這個接口,Java類庫提供了一個抽象類AbstractCollection,它將基礎方法size和iterator抽象化了,但是提供了通用例行方法。例如:
public abstract class AbstractCollection<E> implements Collection<E> { public abstract Iterator iterator(); public abstract int size(); public boolean isEmpty() { return size() == 0; } public boolean contains(Object o) { Iterator e = iterator(); if (o==null) { while (e.hasNext()){ if (e.next()==null) return true; } } else { while (e.hasNext()) { if (o.equals(e.next())) return true; } } return false; } public Object[] toArray() { Object[] result = new Object[size()]; Iterator e = iterator(); for (int i=0; e.hasNext(); i++) { result[i] = e.next(); } return result; } public boolean remove(Object o) { Iterator e = iterator(); if (o==null) { while (e.hasNext()) { if (e.next()==null) { e.remove(); return true; } } } else { while (e.hasNext()) { if (o.equals(e.next())) { e.remove(); return true; } } } return false; } ...... }AbstractCollection的直接子類有:AbstractList、AbstractQueue、AbstractSet。
3、Collection接口層次結構
下圖是關于Collection的類的層次結構:
Set接口:
一個不包括重復元素(包括可變對象)的Collection,是一種無序的集合。Set不包含滿足a.equals(b)的元素對a和b,并且最多有一個null。實現了Set接口的類有:EnumSet、HashSet、TreeSet等。有一種眾所周知的數據結構,可以快速查找所需要的對象,這就是散列表(hash table)。散列表為每個對象計算一個整數,稱為散列碼(hash code)。散列碼由對象的實例域產生的一個整數。準確地說,具有不同數據域的對象將產生不同的散列碼。如果自定義類,就要負責這個類的hashCode方法,注意,自己實現的hashCode方法應該與equals方法兼容,即若a.equals(b)為true,a與b必須具有相同的散列碼。散列碼可以是任何整數,包括整數或負數。在Java中,散列表用鏈表數組實現,每個列表稱為桶。計算對象的散列碼并對桶數取余,得到的結果就是保存這個對象的桶的索引。如果想要更多地控制散列表的性能,就要指定一個初始的桶數。通常,將桶數設置為預計元素個數的75%~150%。有些研究人員認為:盡管還沒有確鑿的證據,但最好將桶數設置為素數,以防鍵的集聚。
當然,并不是總能夠知道需要存儲多少個元素的,也有可能最初的估計過低。如果散列表太滿,就需要再散列(rehashed)。如果要對散列表再散列,就需要創建一個桶數更多的表,并將所有元素插入新表中,廢棄舊表。裝填因子(load factor)決定何時對散列表進行再散列。例如,如果裝填因子為0.75(默認值),而表中超過75%的位置已經填入元素,這個表就會用雙倍的桶數自動地進行再散列。
Java集合類庫提供了一個HashSet類,它實現了基于散列表的集。可以用add方法添加元素。contains方法已經被重新定義,用來快速查看是否某個元素已經出現在集中,它只在某個桶中查找元素,而不必查看集合中的所有元素。樹集(TreeSet)是一個有序集合,當前使用的數據結構是紅黑樹。將一個元素添加到樹中要比添加到散列表中慢,但是,與將元素添加到數組或鏈表的正確位置上相比還是快很多的。TreeSet中的對象需要實現Comparable接口,這樣才能進行元素之間的比較,如果一個類的創建者沒有實現Comparable接口,可以創建一個Comparator類來規定原類對象之間的比較規則,將一個Comparator對象實例傳遞給TreeSet的構造器即可。
List接口:
一個有序的Collection(也稱序列),元素可以重復。列表通常允許滿足e1.equals(e2)的元素對e1和e2,并且列表允許多個null元素。實現List的類有:ArrayList、LinkedList、Vector、Stack等。其中,最常用的是ArrayList和LinkedList。List接口是有序集合、元素可以重復,次序是List接口最重要的特點,它是以元素的添加的順序作為集合的順序,因此List的實現類中有可以通過來操作集合元素的方法。其中ArrayList底層是通過數組實現的,數組的初始長度為10,可以擴展數組。LinkedList底層是通過雙向鏈表實現的,因此LinkedList可以在首尾添加刪減元素,因此可以作為棧、隊列、雙端隊列使用。
Queue接口:
Queue接口的實現類在使用時要盡量避免Collection的add()和remove()方法,而是要使用offer()來加入元素,使用poll()來獲取并移出元素。它們的優點是通過返回值可以判斷成功與否,add()和remove()方法在失敗的時候會拋出異常。offer()方法向隊列中加入元素,不成功時返回false。而直接使用add()方法插入,若隊列已滿則拋出異常。remove()和poll()方法刪除并返回隊列一端的元素。前者隊列為空時拋出異常,后者返回null。
Java SE 6中引入了Deque接口,它是Queue接口的子接口,Deque的實現類為ArrayDeque,可以實現雙端隊列,底層是通過數組實現,ArrayDeque有兩個標志位分別指向數組的頭與尾,因此才可以實現雙端隊列。
當需要使用LIFO(后進先出)堆棧時。應優先使用Deque接口而不是遺留Stack類。在將雙端隊列用作堆棧時,元素被推入雙端隊列的開頭并從雙端隊列開頭彈出。堆棧的方法完全可以等效成Deque的某些方法,對應關系如下:
push(e)---addFirst(e) pop()---removeFrist() top()-----peekFirst()優先級隊列(priority queue)中的元素可以按照任意的順序插入,卻總是按照排序的順序進行檢索。也就是說,無論何時調用remove方法,總會獲得當前的優先級隊列中最小的元素。優先級隊列并沒有對所有的元素進行排序。優先級隊列使用了堆,堆是一個可以自我調整的二叉村,對樹執行添加和刪除操作時,總能保證最小的元素移動到根,而不必花費時間對元素進行排序。與TreeSet一樣,一個優先級隊列既可以保存實現了Comparable接口的類對象,也可以保存在構造器提供比較器的類對象。
優先級隊列的典型應用是任務調度。每一個任務有一個優先級,將優先級最高的任務從隊列中刪除(由于習慣上將1設為“最高”優先級,所以會將最小的元素刪除)。與TreeSet迭代不同,這里的迭代并不是按照元素的排列順序迭代的。刪除時總是刪掉剩余元素中優先級數最小的元素。
還有一種隊列是阻塞式隊列,隊列滿了以后再插入元素則會拋出異常,主要實現類包括:ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。雖然接口并未定義阻塞方法,但是實現類擴展了父接口,實現了阻塞方法。
4、Map接口的層次結構
下面的圖是Map接口的層次結構圖
Map是一個鍵值對的集合。也就是說,一個映射不能包含重復的鍵,每個鍵最多映射到一個值。該接口取代了Dictionary抽象類。實現map接口的類有:HashMap、TreeMap、HashTable、Properties、EnumMap。
散列映射表對鍵進行散列,樹映射表用鍵的整體順序對元素進行排序,并將其組織成搜索村。散列或比較函數只能作用于鍵,與鍵關聯的值不能進行散列或比較。
如何選擇散列映射表與樹映射表。散列稍微快一些,如果不需要按照排列順序訪問鍵,就最好選擇散列。映射表中鍵必須是唯一的,不能對同一個鍵存放兩個值。如果對同一個鍵兩次調用put方法,第二個值就會取代第一個值。實際上,put方法會返回用這個鍵參數存儲的上一個值。
集合框架并沒有將映射表本身視為一個集合(其他的數據結構框架則將映射表視為對pairs的集合,或者視為用鍵作為索引的值的集合)。然而,可以獲得映射表的視圖,這是一組實現了Collection接口或者它的子接口的視圖。有3個視圖,它們分別是:鍵集、值集和鍵/值對集,鍵與鍵/值對形成了一個集,這是因為在映射表中一個鍵只能有一個副本。下列方法將返回這3個視圖(條目集的元素是靜態內部類Map.Entry的對象)。
Set<K> keyset(); Collection<K> values(); Set<Map.Entry<K,V>> entrySet();注意,keySet既不是HashSet,也不是TreeSet,而是實現了Set接口的某個其他類的對象。初看起來,keyset方法創建了一個新集,并將映射表中的所有鍵都填進去,然后返回這個集。但是,情況并非如此。取而代之的是:keySet方法返回一個實現了Set接口的某個類的對象,這個類的方法對原映射表進行操作。Set接口擴展了Collection接口,因此可以與使用任何集合一樣使用keySet。例如,可以杖舉映射表中的所有鍵:
Set<String> keys = map.keySet(); for(String key: keys){ System.out.println(key); }如果想要同時查看鍵與值,可以通過杖舉各個條目(entries)查看,以避免對值進行查找。
for(Map.Entry<String, Employee> entry : staff.entrySet()){ String key = entry.getKey(); Employee value = entry.getValue(); System.out.println("key = " + key + ", value = " + value); }5、數組與集合之間的轉換
由于Java平臺API中大部分內容都是在集合框架創建之前設計的,所以,有時候需要在傳統的數組與現代的集合之間進行轉換。如果數組要轉換為集合,Arrays.asList的包裝器就可以實現這個目的。例如:
String[] values = ...; HashSet<String> staff = new HashSet<String>(Arrays.asList(values));反過來,將集合轉換為數組采用toArray()方法。轉化為Object[]類型數組方法如下:
Object[] listArray = list.toArray(); Object[] setArray = set.toArray();轉化為具體類型數組:
String[] array1 = (String[])list.toArray(new String[list.size()]); String[] array2 = (String[])list.toArray(new String[0]);在轉化為其它類型的數組時需要強制類型轉換,并且要使用帶參數的toArray方法,參數為對象數組,將list中的內容放入參數數組中,當參數數組的長度小于list的元素個數時,會自動擴充數組的長度以適應list的長度。因此,最好在數組構造時指明其長度。
6、List接口實現類
List接口繼承了Collection接口,并對父接口進行了簡單的擴充:同時List接口又有三個常用的實現類ArrayList、LinkedList和Vector。
1)ArrayList(數組線性表)
ArrayList數組線性表的特點為:用類似數組的形式進行存儲,因此它的隨機訪問速度極快。ArrayList數組線性表的缺點為:不適合于在線性表中間頻繁地進行插入和刪除操作。因為每次插入和刪除都需要移動數組中的元素。可以這樣理解ArrayList就是基于數組的一個線性表,只不過數組的長度可以動態改變而已。
對于使用ArrayList的開發者而言,下面幾點內容一定要注意啦,尤其找工作面試的時候經常會被問到。
①如果在初始化ArrayList的時候沒有指定初始化長度的話,默認的長度為10,源碼中就是這樣設置的:
/* * Constructs an empty list with an initial capacity of ten. **/ public ArrayList() { this(10); }②ArrayList在增加新元素的時候如果超過了原始的容量的話,ArrayList擴容的方案為:上一次的容量*1.5+1。代碼如下(Java1.8中ArrayList的擴容代碼已經變了,這里暫時沒有更新):
public void ensureCapacity(int minCapacity){ modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }③ArrayList是線程不安全的,在多線程的情況下不要使用。如果一定在多線程使用List,可以使用Vector,因為Vector和ArrayList基本一致,區別在于Vector中的絕大部分方法都使用了同步關鍵字修飾,這樣在多線程的情況下不會出現并發錯誤,還有就是它們的擴容方案不同,ArrayList是擴容方案是:原始容量*3/2+1,而Vector允許設置默認的增長長度,Vector的默認擴容方式為原來的2倍。切記Vector是ArrayList的多線程的一個替代品。
④ArrayList實現遍歷的幾種方法
public class Test{ public static void main(String[] args) { List<String> list=new ArrayList<String>(); list.add("Hello"); list.add("World"); list.add("HAHAHAHA"); //第一種遍歷方法使用foreach遍歷List,這是推薦的通用方法for (String str : list) { System.out.println(str); } //第二種遍歷,把list變為數組相關的內容進行遍歷 String[] strArray=new String[list.size()]; list.toArray(strArray); for(int i=0;i<strArray.length;i++) { System.out.println(strArray[i]); } //第三種遍歷 使用迭代器進行遍歷 Iterator<String> ite=list.iterator(); while(ite.hasNext()){ System.out.println(ite.next()); } } }2)LinkedList(鏈式線性表)
LinkedList的鏈式線性表的特點為:適用于需要在鏈表中間頻繁進行插入和刪除操作的場合。LinkedList的鏈式線性表的缺點為:隨機訪問速度較慢。查找一個元素需要從頭開始一個一個的找。LinkedList是用雙向循環鏈表實現的。對于使用LinkedList的開發者而言,下面幾點內容一定要注意啦,尤其找工作面試的過程時候經常會被問到。
①簡述LinkedList和ArrayList的區別和聯系。
ArrayList是實現了基于動態數組的數據結構,LinkedList是基于鏈表的數據結構。ArrayList數組線性表的特點為:類似數組的形式進行存儲,內存連續,因此它的隨機訪問速度極快。ArrayList數組線性表的缺點為:不適合于在線性表中間需要頻繁進行插入和刪除操作。因為每次插入和刪除都需要移動數組中的元素。LinkedList的鏈式線性表的特點為:適合于在鏈表中間需要頻繁進行插入和刪除操作。LinkedList的鏈式線性表的缺點為:隨機訪問速度較慢。查找一個元素需要從頭開始一個一個的找。
②LinkedList的內部實現是怎樣的
對于這個問題,最好看一下jdk中LinkedList的源碼。這樣會醍醐灌頂的。LinkedList的內部是用基于雙向循環鏈表的結構來實現的。在LinkedList中有一個類似于C語言中結構體的Entry內部類。在Entry的內部類中包含了前一個元素的地址引用和后一個元素的地址引用類似于C語言中指針
③LinkedList不是線程安全的
注意LinkedList和ArrayList一樣也不是線程安全的,如果要在多線程并發環境中使用LinkedList,需要在要求同步的方法上加上同步關鍵字synchronized。
3)Vector(向量)
Vector和ArrayList幾乎是完全相同的,唯一的區別在于Vector是同步類(synchronized),即線程安全的。因此,開銷就比ArrayList要大,正常情況下,大多數的Java程序員使用ArrayList而不是Vector,因為同步完全可以由程序員自己來控制。
引申:線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。不會出現數據不一致或者數據污染。線程不安全就是不提供數據訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據。
7、HashMap與HashTable
HashMap和HashTable的比較是Java面試中的常見問題,用來考驗程序員是否能夠正確使用集合類以及是否可以隨機應變使用多種思路解決問題。HashMap的工作原理、ArrayList與Vector的比較是有關Java集合框架的最經典的問題。HashTable是個過時的集合類,存在于Java API中很久了。在Java 4中被重寫了,它實現了Map接口,所以自此以后也成了Java集合框架中的一部分。HashTable和HashMap在Java面試中相當容易被問到,甚至成為了集合框架面試題中最常被考的問題。Key-Value鍵值存儲的示意圖如下圖所示:
Hashtable和HashMap的內部數據結構相似,如下圖所示:
其基本內部數據結構是一個Entry數組和鏈表的結合體。Entry數組的元素為實現了Map.Entry
Map map = new HashMap(); map.put("Rajib Sarma","100"); map.put("Rajib Sarma","200"); //The value "100" is replaced by "200". map.put("Sazid Ahmed","200"); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue(); }HashTable和HashMap區別主要集中在線程安全性、同步(synchronization)和速度上,分別有以下幾點:
1)繼承層次不同,二者都實現了Map接口,但HashTable繼承了Dictionary類,而HashMap繼承了AbstractMap類。
public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map2)在HashTable中,key和value都不允許出現null值。在HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為null。當get()方法返回null值時,既可以表示HashMap中沒有該鍵,也可以表示該鍵所對應的值就是null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個鍵,而應該用containsKey()方法來判斷。
3)HashMap是非synchronized,而HashTable是synchronized,這意味著HashTable是線程安全的,多個線程可以共享一個HashTable;而如果程序員沒有手工進行正確的同步的話,多個線程是不能共享HashMap的。由于HashTable是線程安全的也是synchronized,所以在單線程環境下它比HashMap要慢。如果你不需要同步,只應用于單線程,那么使用HashMap性能要好過HashTable。HashMap可以通過下面的語句進行同步:
Map m = Collections.synchronizeMap(hashMap);4)哈希值的使用不同,HashTable直接使用對象的HashCode,根據key值計算index的代碼是這樣的:
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length;當hash數組的長度較小,并且Key的hashCode低位數值分散不均勻時,不同的hash值計算得到相同下標值的幾率較高。HashMap不直接使用對象的HashCode,而是重新計算hash值,而且用與運算代替了求模運算,代碼如下:
static int indexFor(int h,int length) { return h & (length-1); } static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } int hash = hash(k); int i = indexFor(hash, table.length);這種計算方式優于HashTable,通過對Key的hashCode做移位運算和位的與運算,使其能更廣泛地分散到數組的不同位置上去。
5)HashTable中hash數組默認大小是11,初始化時可以指定initial capacity(數組初始長度),擴容方式是old*2+1。HashMap中hash數組的默認大小是16,而且長度始終保持為2的n次方,初始化時同樣可以指定initial capacity(數組初始長度),若不是2的次方,HashMap將選取第一個大于initial capacity的2n次方值作為其初始長度。
6)遍歷方式的內部實現上不同。HashTable與HashMap都使用了Iterator。而由于歷史原因(HashTable繼承了Dictionary類),HashTable還使用了Enumeration的方式。一般單線程情況下,HashMap能夠比HashTable工作得更好、更快,主要得益于它的散列算法,以及沒有作線程同步。應用程序一般在更高的層面上實現了保護機制,而不是依賴于這些底層數據結構的同步,因此,HashMap能夠在大多數應用中滿足需要。推薦使用HashMap,如果需要同步,可以使用同步工具類將其轉換成支持同步的HashMap。
總結
以上是生活随笔為你收集整理的Java集合框架完全解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么我们不再 Root 和刷机了?
- 下一篇: Java单元测试之Mock框架