什么是哈希表?
哈希表(Hash table,也叫散列表),是根據key而直接進行訪問的數據結構。也就是說,它通過把key映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
為什么要了解字典類?
1.HashMap的性能最棒查找,添加,刪除的時間復雜度都是常數。

Data Structure |
Add |
Find |
Delete |
GetByIndex |
Array (T[]) | O(n) |
O(n) |
O(n) |
O(1) |
Linked list (LinkedList<T>) | O(1) |
O(n) |
O(n) |
O(n) |
Resizable array list (List<T>) | O(1) |
O(n) |
O(n) |
O(1) |
Stack (Stack<T>) | O(1) |
- |
O(1) |
- |
Queue (Queue<T>) | O(1) |
- |
O(1) |
- |
Hash table (Dictionary<K,T>) | O(1) |
O(1) |
O(1) |
- |
Tree-based dictionary (SortedDictionary<K,T>) |
O(log n) |
O(log n) |
O(log n) |
- |
Hash table based set (HashSet<T>) |
O(1) |
O(1) |
O(1) |
- |
Tree based set (SortedSet<T>) |
O(log n) |
O(log n) |
O(log n) |
- |
2.舉一反三。HashSet是基於HashMap來實現的,操作很簡單,更像是對HashMap做了一次“封裝”,而且只使用了HashMap的key來實現各種特性()
HashMap的原理?
哈希表的做法其實很簡單,就是把key通過一個固定的算法函數即所謂的哈希函數轉換成一個整型數字,然后就將該數字對數組長度進行取余,取余結果就當作數組的下標,將value存儲在以該數字為下標的數組空間里。
而當使用哈希表進行查詢的時候,就是再次使用哈希函數將key轉換為對應的數組下標,並定位到該空間獲取value,如此一來,就可以充分利用到數組的定位性能進行數據定位
。
put方法:
public V put(K key, V value) {
// 處理key為null,HashMap允許key和value為null
if (key == null)
return putForNullKey(value);
// 得到key的哈希碼
int hash = hash(key);
// 通過哈希碼計算出bucketIndex
int i = indexFor(hash, table.length);
// 取出bucketIndex位置上的元素,並循環單鏈表,判斷key是否已存在
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 哈希碼相同並且對象相同時
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
// 新值替換舊值,並返回舊值
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// key不存在時,加入新元素
modCount++;
addEntry(hash, key, value, i);
return null;
}
數組的特點是:尋址容易,插入和刪除困難;而鏈表的特點是:尋址困難,插入和刪除容易。那么我們能不能綜合兩者的特性,做出一種尋址容易,插入刪除也容易的數據結構?答案是肯定的,這就是我們要提起的哈希表,哈希表有多種不同的實現方法,我接下來解釋的是最常用的一種方法——拉鏈法,我們可以理解為“鏈表的數組”。
與其他集合的比較:
1.特點比較:
|
有序否 |
允許元素重復否 |
|
Collection |
否 |
是 |
|
List |
是 |
是 |
|
Set |
AbstractSet |
否 |
否 |
HashSet |
|||
TreeSet |
是(用二叉樹排序) |
||
Map |
AbstractMap |
否 |
使用key-value來映射和存儲數據,Key必須惟一,value可以重復 |
HashMap |
|||
TreeMap |
是(用二叉樹排序) |
2.HashMap與Hashtable的區別:
HashMap是非synchronized,而Hashtable是synchronized(針對整張表的鎖,性能不佳),HashMap可以 可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。
3.由於HashMap不是線程安全的,在多線程環境中使用 ConcurrentHashMap。原因:其關鍵在於使用了鎖分離技術, ConcurrentHashMap使用鎖桶,默認將hash表分為16分桶,線程操作時鎖定一個桶,這樣,最多可以16個線程同時寫入。大大提升並發性。而讀方面,ConcurrentHashMap使用弱一致迭代器,當iterator被創建后數據改變時創新新的數據,完成后再將頭指針替換為新數據。這保證了寫的連續性和擴展性。
操作:
遍歷:
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}
刪除:
我們可以發現Dictionary在添加,刪除元素按照如下方法進行:
通過Hash算法來計算到指定的Bucket上,碰撞到同一個Bucket槽上所有數據形成一個單鏈表
默認情況Entries槽中的數據按照添加順序排列
刪除的數據會形成一個 LinkedList的鏈表,添加數據的時候,優先向 LinkedList鏈表中添加數據, LinkedList為空則按照count依次排列。 HashMap在每個LinkedList節點中儲存鍵值對對象。
參考: