1. 程式人生 > >Java中最常用的集合類框架之 HashMap

Java中最常用的集合類框架之 HashMap

tor nbsp 兩種 lca 元素 adf boolean 線程 進行

一、HashMap的概述

HashMap可以說是Java中最常用的集合類框架之一,是Java語言中非常典型的數據結構。
HashMap是基於哈希表的Map接口實現的,此實現提供所有可選的映射操作。存儲的是的映射,允許多個null值和一個null鍵。但此類不保證映射的順序,特別是它不保證該順序恒久不變。
  除了HashMap是非同步以及允許使用null外,HashMap 類與 Hashtable大致相同。
   此實現假定哈希函數將元素適當地分布在各桶之間,可為基本操作(get 和 put)提供穩定的性能。叠代collection 視圖所需的時間與 HashMap 實例的“容量”(桶的數量)及其大小(鍵-值映射關系數)成比例。所以,如果叠代性能很重要,則不要將初始容量設置得太高(或將加載因子設置得太低)。
  HashMap 的實例有兩個參數影響其性能:初始容量
加載因子容量 是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。加載因子 是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數。

通常,默認加載因子 (0.75) 在時間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地減少 rehash 操作次數。如果初始容量大於最大條目數除以加載因子,則不會發生 rehash 操作。
  註意,此實現不是同步
的。 如果多個線程同時訪問一個HashMap實例,而其中至少一個線程從結構上修改了列表,那麽它必須保持外部同步。這通常是通過同步那些用來封裝列表的 對象來實現的。但如果沒有這樣的對象存在,則應該使用{@link Collections#synchronizedMap Collections.synchronizedMap}來進行“包裝”,該方法最好是在創建時完成,為了避免對映射進行意外的非同步操作。

Map m = Collections.synchronizedMap(new HashMap(...));

二、構造函數

HashMap提供了三個構造函數:
HashMap():構造一個具有默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity):構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor):構造一個帶指定初始容量和加載因子的空 HashMap。
這裏提到了兩個參數:初始容量
加載因子。這兩個參數是影響HashMap性能的重要參數,其中容量表示哈希表中桶的數量,初始容量是創建哈希表時的容量,加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度,它衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。對於使用鏈表法的散列表來說,查找一個元素的平均時間是O(1+a),因此如果負載因子越大,對空間的利用更充分,然而後果是查找效率的降低;如果負載因子太小,那麽散列表的數據將過於稀疏,對空間造成嚴重浪費。系統默認負載因子為0.75,一般情況下我們是無需修改的。
HashMap是一種支持快速存取的數據結構,要了解它的性能必須要了解它的數據結構。

三、數據結構

我們知道在Java中最常用的兩種結構是數組和模擬指針(引用),幾乎所有的數據結構都可以利用這兩種來組合實現,HashMap也是如此。實際上HashMap是一個“鏈表散列”,如下是它數據結構:

技術分享

// Entry是單向鏈表。 它是 “HashMap鏈式存儲法”對應的鏈表。
// 實現了Map.Entry接口,即getKey(),getValue(),setValue(V value),equals(Object o),hashCode()這些函數
static class Entry implements Map.Entry {
final K key;
V value;
// 指向下一個節點
Entry next;
final int hash;

// 構造函數
// 輸入參數包括"哈希值(h)", "鍵(k)", "值(v)", "下一節點(n)"
Entry(int h, K k, V v, Entry n) {
value = v;
next = n;
key = k;
hash = h;
}

public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
// 判斷兩個Entry是否相等
// 若兩個Entry的“key”和“value”都相等,則返回true。
// 否則,返回false
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
// 實現hashCode()
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
// 當向HashMap中添加元素時,繪調用recordAccess()。
// 這裏不做任何處理
void recordAccess(HashMap m) {
}
// 當從HashMap中刪除元素時,繪調用recordRemoval()。
// 這裏不做任何處理
void recordRemoval(HashMap m) {
}
}
從上圖我們可以看出HashMap底層實現還是數組,只是數組的每一項都是一條鏈。其中參數initialCapacity就代表了該數組的長度。下面為HashMap構造函數的源碼:
// 找出“大於Capacity”的最小的2的冪,使Hash表的容量保持為2的次方倍
// 算法的思想:通過使用邏輯運算來替代取余,這裏有一個規律,就是當N為2的次方(Power of two),那麽X%N==X&(N-1)。
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1; // >>> 無符號右移,高位補0
n |= n >>> 2; // a|=b的意思就是把a和b按位或然後賦值給a
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
// 構造一個帶指定初始容量和加載因子的空HashMap
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
// 構造一個帶指定初始容量和默認加載因子(0.75)的空 HashMap
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 構造一個具有默認初始容量 (16)和默認加載因子 (0.75)的空 HashMap
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 構造一個映射關系與指定 Map相同的新 HashMap,容量與指定Map容量相同,加載因子為默認的0.75
public HashMap(Map m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}

從源碼中可以看出,每次新建一個HashMap時,都會初始化一個table數組。table數組的元素為Entry節點。

// Entry是單向鏈表。
// 它是 “HashMap鏈式存儲法”對應的鏈表。
// 它實現了Map.Entry 接口,即實現getKey(), getValue(), setValue(V value), equals(Object o), hashCode()這些函數
static class Entry implements Map.Entry {
final K key;
V value;
// 指向下一個節點
Entry next;
final int hash;
// 構造函數。
// 輸入參數包括"哈希值(h)", "鍵(k)", "值(v)", "下一節點(n)"
Entry(int h, K k, V v, Entry n) {
value = v;
next = n;
key = k;
hash = h;
}
......
}
其中Entry為HashMap的內部類,它包含了鍵key、值value、下一個節點next,以及hash值,這是非常重要的,正是由於Entry才構成了table數組的項為鏈表。 上海尚學堂java培訓原作,陸續有HashMap等java技術文章奉獻,請多關註。

Java中最常用的集合類框架之 HashMap