標籤:
前言:
今天看Hashtable源碼,開始以為Hashtable就是一個Entry[]
int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } }
看到上面這段代碼時,天真的以為就是在Entry[]上加個鏈式關係,如所示(這是錯誤的):
百事不得其解,int index = (hash & 0x7FFFFFFF) % tab.length;這個有什麼用
本文:轉載於http://www.blogjava.net/fhtdy2004/archive/2009/07/03/285330.html
Java Hashtable分析
Hashtable的結構,採用的是資料結構中所說的鏈地址法處理衝突的方法
從上面的結構圖可以看出,Hashtable的實質就是一個數組+鏈表。圖中的Entry就是鏈表的實現,Entry的結構中包含了對自己的另一個執行個體的引用next,用以指向另外一個Entry。而圖中標有數位部分是一個Entry數組,數字就是這個Entry數組的index。那麼往Hashtable增加索引值對的時候,index會根據鍵的hashcode、Entry數組的長度共同決定,從而決定索引值對存放在Entry數組的哪個位置。從這種意義來說,當鍵一定,Entry數組的長度一定的情況下,所得到的index肯定是相同的,也就是說插入順序應該不會影響輸出的順序才對。然而,還有一個重要的因素沒有考慮,就是計算index出現相同值的情況。譬如代碼中 "sichuan" 和 "anhui",所得到的index是相同的,在這個時候,Entry的鏈表功能就發揮作用了:put方法通過Entry的next屬性獲得對另外一個Entry的引用,然後將後來者放入其中。根據debug得出的結果,"sichuan", "anhui"的index同為2,"hunan"的index為6,"beijing"的index為1,在輸出的時候,會以index遞減的方式獲得索引值對。很明顯,會改變的輸出順序只有"sichuan"和"anhui"了,也就是說輸出只有兩種可能:"hunan" - "sichuan" - "anhui" - "beijing"和"hunan" - "anhui" - "sichuan" - "beijing"。以下是運行了範例程式碼之後,Hashtable的結果:
在Hashtable的實現代碼中,有一個名為rehash的方法用於擴充Hashtable的容量。很明顯,當rehash方法被調用以後,每一個索引值對相應的index也會改變,也就等於將索引值對重新排序了。這也是往不同容量的Hashtable放入相同的索引值對會輸出不同的索引值對序列的原因。在Java中,觸發rehash方法的條件很簡單:hahtable中的索引值對超過某一閥值。預設情況下,該閥值等於hashtable中Entry數組的長度×0.75。
自 Java 2 平台 v1.2 以來,此類已經改進為可以實現 Map,因此它變成了 Java Collections Framework 的一部分。與新集合的實現不同,Hashtable 是同步的。
由迭代器返回的 Iterator 和由所有 Hashtable 的“collection 視圖方法”返回的 Collection 的 listIterator 方法都是快速失敗 的:在建立 Iterator 之後,如果從結構上對 Hashtable 進行修改,除非通過 Iterator 自身的移除或添加方法,否則在任何時間以任何方式對其進行修改,Iterator 都將拋出 ConcurrentModificationException。因此,面對並發的修改,Iterator 很快就會完全失敗,而不冒在將來某個不確定的時間發生任意不確定行為的風險。由 Hashtable 的鍵和值方法返回的 Enumeration 不 是快速失敗的。
注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步並發修改做出任何硬性保證。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException。因此,為提高這類迭代器的正確性而編寫一個依賴於此異常的程式是錯誤做法:迭代器的快速失敗行為應該僅用於檢測程式錯誤。
TestHashTableHashtable中定義幾個內部類(包括靜態嵌套類和普通的內部類)
Hashtable中的Entry資料結構
Entry
put方法:key的hash值不同但是可能放入的index相同,並且在放入之前需要判斷
put方法
1 public synchronized Enumeration<K>
keys() {
2 return this.<K>
getEnumeration(KEYS);
3 }
4
5 private <T> Enumeration<T>
getEnumeration(
int
type) {
6 if (count == 0) {
7 return (Enumeration<T>)emptyEnumerator;
8 } else {
9 return new
Enumerator<T>(type, false
);
10 }
11 }
12
13 public synchronized Enumeration<V>
elements() {
14 return this.<V>
getEnumeration(VALUES);
15 }
16
17Enumerator是Hashtable定義的一個內部類
18 private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
19
Entry[] table
= Hashtable.this
.table;//訪問宿主類的成員變數
20 int index = table.length;
21 Entry<K,V> entry = null;
22 Entry<K,V> lastReturned = null;
23 int type;
24
25 /**
26 * Indicates whether this Enumerator is serving as an Iterator
27 * or an Enumeration. (true -> Iterator).
28 */
29 boolean iterator;
30
31 /**
32 * The modCount value that the iterator believes that the backing
33 * Hashtable should have. If this expectation is violated, the iterator
34 * has detected concurrent modification.
35 */
36
protected int expectedModCount =
modCount;
37}
38
內部類中提供了訪問了hashtable中的entry數組的方法.
39在實現Iterator介面中的方法時使用了expectedModCount變數來判斷是否有並發修改而導致fast-fail,而在Enumeration的介面方法實現中沒有判斷
40
41
42
43 public Set<K>
keySet() {
44 if (keySet == null)
45 keySet = Collections.synchronizedSet(new
KeySet(), this);
46 return keySet;
47 }
48 private class KeySet extends AbstractSet<K> {
49
public Iterator<K> iterator()
{
50 return
getIterator(KEYS);
51 }
52 public int size() {
53 return count;
54 }
55 public boolean contains(Object o) {
56 return containsKey(o);
57 }
58 public boolean remove(Object o) {
59
return Hashtable.this.remove(o) != null
;
60 }
61 public void clear() {
62
Hashtable.
this
.clear();
63 }
64 }
65內部類KeySet中有iterator介面方法的實現,調用的宿主類的getIterator(KEYS)
66 private <T> Iterator<T> getIterator(int type) {
67 if (count == 0) {
68 return (Iterator<T>) emptyIterator;
69 } else {
70 return new Enumerator<T>(type, true);
71 }
72 }
73getIterator中new 了一個新的內部類Enumerator的對象,最終使用Enumerator來訪問hashtable的entry數組,能不能在內部類中直接建立一個內部類的的執行個體???
74
75
76
77 public Collection<V> values() {
78 if (values==null)
79 values = Collections.synchronizedCollection(new ValueCollection(),
80 this);
81 return values;
82 }
83
ValueCollection也是一個內部類,結構和KeySet功能差不多
84 public Set<Map.Entry<K,V>> entrySet() {
85 if (entrySet==null)
86 entrySet = Collections.synchronizedSet(new EntrySet(), this);
87 return entrySet;
88 }
89
EntrySet也是內部類,結構和KeySet功能差不多
posted on 2009-07-03 13:24 Frank_Fang 閱讀(5958) 評論(1) 編輯 收藏 所屬分類: Java編程
評論: # re: Java Hashtable分析 2009-07-15 00:11 | Frank_Fang
public class HashMap<K,V>extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
基於雜湊表的 Map 介面的實現。此實現提供所有可選的映射操作,並允許使用 null 值和 null 鍵。(除了不同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證映射的順序,特別是它不保證該順序恒久不變。
此實現假定雜湊函數將元素正確分布在各桶之間,可為基本操作(get 和 put)提供穩定的效能。迭代集合視圖所需的時間與 HashMap 執行個體的“容量”(桶的數量)及其大小(鍵-值對應關係數)的和成比例。所以,如果迭代效能很重要,則不要將初始容量設定得太高(或將載入因子設定得太低)。
HashMap 的執行個體有兩個參數影響其效能:初始容量 和載入因子。容量 是雜湊表中桶的數量,初始容量只是雜湊表在建立時的容量。載入因子 是雜湊表在其容量自動增加之前可以達到多滿的一種尺度。當雜湊表中的條目數超出了載入因子與當前容量的乘積時,通過調用 rehash 方法將容量翻倍。
通常,預設載入因子 (.75) 在時間和空間成本上尋求一種折衷。載入因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設定初始容量時應該考慮到映射中所需的條目數及其載入因子,以便最大限度地降低 rehash 操作次數。如果初始容量大於最大條目數除以載入因子,則不會發生 rehash 操作。
如果很多映射關係要儲存在 HashMap 執行個體中,則相對於按需執行自動的 rehash 操作以增大表的容量來說,使用足夠大的初始容量建立它將使得映射關係能更有效地儲存。
注意,此實現不是同步的。如果多個線程同時訪問此映射,而其中至少一個線程從結構上修改了該映射,則它必須 保持外部同步。(結構上的修改是指添加或刪除一個或多個映射關係的操作;僅改變與執行個體已經包含的鍵關聯的值不是結構上的修改。)這一般通過對自然封裝該映射的對象進行同步操作來完成。如果不存在這樣的對象,則應該使用 Collections.synchronizedMap 方法來“封裝”該映射。最好在建立時完成這一操作,以防止對映射進行意外的不同步訪問,如下所示:
Map m = Collections.synchronizedMap(new HashMap(...));
由所有此類的“集合視圖方法”所返回的迭代器都是快速失敗 的:在迭代器建立之後,如果從結構上對映射進行修改,除非通過迭代器自身的 remove 或 add 方法,其他任何時間任何方式的修改,迭代器都將拋出 ConcurrentModificationException。因此,面對並發的修改,迭代器很快就會完全失敗,而不冒在將來不確定的時間任意發生不確定行為的風險。
注意,迭代器的快速失敗行為不能得到保證,一般來說,存在不同步的並發修改時,不可能作出任何堅決的保證。快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴於此異常程式的方式是錯誤的,正確做法是:迭代器的快速失敗行為應該僅用於檢測程式錯誤。
public class LinkedHashMap<K,V>extends HashMap<K,V> implements Map<K,V>
Map 介面的雜湊表和連結清單實現,具有可預知的迭代順序。此實現與 HashMap 的不同之處在於,後者維護著一個運行於所有條目的雙重連結清單。此連結清單定義了迭代順序,該迭代順序通常就是將鍵插入到映射中的順序(插入順序)。注意,如果在映射中重新插入 鍵,則插入順序不受影響。(如果在調用 m.put(k, v) 前 m.containsKey(k) 返回了 true,則調用時會將鍵 k 重新插入到映射 m中。)
此實現可以讓客戶避免未指定的、由 HashMap
(及 Hashtable
)所提供的通常為雜亂無章的排序工作,同時無需增加與 TreeMap
相關的成本。使用它可以產生一個與原來順序相同的映射副本,而與原映射的實現無關:
void foo(Map m) {Map copy = new LinkedHashMap(m);...}
如果模組通過輸入得到一個映射,複製這個映射,然後返回由此副本確定其順序的結果,這種情況下這項技術特別有用。(客戶通常期望返回的內容與其出現的順序相同。)
提供特殊的構造方法
來建立連結雜湊映射,該雜湊映射的迭代順序就是最後訪問其條目的順序,從近期訪問最少到近期訪問最多的順序(訪問順序)。這種映射很適合構建 LRU 緩衝。調用 put 或 get方法將會訪問相應的條目(假定調用完成後它還存在)。putAll 方法以指定映射的條目集合迭代器提供的鍵-值對應關係的順序,為指定映射的每個映射關係產生一個條目訪問。任何其他方法均不產生條目訪問。特別是,collection 視圖上的操作不 影響底層映射的迭代順序。
可以重寫 removeEldestEntry(Map.Entry)
方法來實施策略,以便在將新映射關係添加到映射時自動移除舊的映射關係。
此類提供所有可選的 Map 操作,並且允許 null 元素。與 HashMap 一樣,它可以為基本操作(add、contains 和 remove)提供穩定的效能,假定雜湊函數將元素正確分布到桶中。由於增加了維護連結清單的開支,其效能很可能比 HashMap 稍遜一籌,不過這一點例外:LinkedHashMap 的 collection 視圖迭代所需時間與映射的大小 成比例。HashMap 迭代時間很可能開支較大,因為它所需要的時間與其容量 成比例。
連結的雜湊映射具有兩個影響其效能的參數:初始容量和載入因子。它們的定義與 HashMap 極其相似。要注意,為初始容量選擇非常高的值對此類的影響比對 HashMap 要小,因為此類的迭代時間不受容量的影響。
注意,此實現不是同步的。如果多個線程同時訪問連結的雜湊映射,而其中至少一個線程從結構上修改了該映射,則它必須 保持外部同步。這一般通過對自然封裝該映射的對象進行同步操作來完成。如果不存在這樣的對象,則應該使用 Collections.synchronizedMap 方法來“封裝”該映射。最好在建立時完成這一操作,以防止意外的非同步訪問:
Map m = Collections.synchronizedMap(new LinkedHashMap(...));
結構修改是指添加或刪除一個或多個映射關係,或者在按訪問順序連結的雜湊映射中影響迭代順序的任何操作。在按插入順序連結的雜湊映射中,僅更改與映射中已包含鍵關聯的值不是結構修改。
在按訪問順序連結的雜湊映射中,僅利用 get 查詢映射不是結構修改。)
Collection(由此類的所有 collection 視圖方法所返回)的 iterator 方法返回的迭代器都是快速失敗 的:在迭代器建立之後,如果從結構上對映射進行修改,除非通過迭代器自身的移除方法,其他任何時間任何方式的修改,迭代器都將拋出 ConcurrentModificationException。因此,面對並發的修改,迭代器很快就會完全失敗,而不冒將來不確定的時間任意發生不確定行為的風險。
注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步並發修改做出任何硬性保證。快速失敗迭代器會盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴於此異常的程式的方式是錯誤的,正確做法是:迭代器的快速失敗行為應該僅用於檢測程式錯誤。
此類是 Java Collections Framework 的成員。
Java Hashtable 資料結構