HashTable & HashMap & ConcurrentHashMap 原理與區別


一.三者的區別

 
  HashTable HashMap ConcurrentHashMap
底層數據結構 數組+鏈表 數組+鏈表 數組+鏈表
key可為空
value可為空
線程安全
默認初始容量 11 16 16
擴容方式 (oldSize << 1)+1 oldSize << 1 桶的擴容
擴容時間 size超過(容量*負載因子) size超過(容量*負載因子) 桶超數超過(容量*負載因子)
hash key.hashCode() (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) (key.hashCode() ^ (key.hashCode() >>> 16)) & 0x7fffffff
index計算方式 (hash & 0x7FFFFFFF) % tab.length (tab.length - 1) & hash (tab.length - 1) & hash
默認負載因子 0.75f 0.75f 0.75f

二.源碼剖析

1.HashTable

構造函數解讀:
public Hashtable() { this(11, 0.75f);}//默認容量和負載因子
public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);//給定初始容量
}

public Hashtable(int initialCapacity, float loadFactor) {
//初始容量校驗
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
//負載因子校驗
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
//初始化表
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

重點方法解讀:
//直接對方法加鎖(鎖這個表)
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {//value不能為空
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();//key不能為空
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
//獲取當前Entry
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
//key-hash碰撞,查找是否有相同的key值
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//覆蓋舊值
return old;//完成
}
}
addEntry(hash, key, value, index);//添加新值(重點是這個方法)
return null;
}

private void addEntry(int hash, K key, V value, int index) {
modCount++;//此哈希表在結構上被修改的次數+1
Entry<?,?> tab[] = table;
if (count >= threshold) {//如果超過閾值,則重新刷新表
// Rehash the table if the threshold is exceeded
rehash();//擴容入口

tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;//刷新后重新計算index
}

// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);執行添加,將新增節點作為根節點
count++;//完成,數量+1
}

//內部類Entry構造方法
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;//設置next節點,與上面銜接
}

下面說一下擴容入口rehash方法
//此方法沒有加鎖,但由於只被addEntry調用,而調用addEntry的方法均添加了方法鎖,所以不存在線程安全問題
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;

// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1;//擴容
if (newCapacity - MAX_ARRAY_SIZE > 0) {//超出最大值
if (oldCapacity == MAX_ARRAY_SIZE)//非首次超出最大值
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;//首次擴容后超出最大值則設置為最大值
}
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];//新建Entry

modCount++;//表結構修改次數+1
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//設置閾值
table = newMap;

   //將原表數據復制到新表
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {//遍歷每一個Entry
Entry<K,V> e = old;
old = old.next;

int index = (e.hash & 0x7FFFFFFF) % newCapacity;//重新計算index
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
}

2.HashMap

構造函數解讀
public HashMap() {
    //設置默認的負載因子
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
//初始化容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
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);//設置閾值
}
//返回給定目標容量的二次冪
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
重要方法解讀:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);//實際調用方法
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//未初始化,則執行初始化容量
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//當前插入表位置為空,則直接插入
else {
    //當前插入表位置已存在元素,則在基礎上新增
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))//已存在相同key值
e = p;
else if (p instanceof TreeNode)
       當前節點屬於紅黑樹節點,則執行紅黑樹新增邏輯
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//查找鏈表尾節點
p.next = newNode(hash, key, value, null);//在鏈表尾部新增
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//超過紅黑樹轉化閾值,則轉為紅黑樹
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;//如果next節點匹配到key值/key為空,則直接跳出
p = e;
}
}
     //是否已存在key值,e!=null則表示存在
if (e != null) { // existing mapping for key
V oldValue = e.value;//獲取舊值
if (!onlyIfAbsent || oldValue == null)
e.value = value;//覆蓋值,條件:可覆蓋 或 舊值為空
afterNodeAccess(e);//將節點移動到最后
return oldValue;//返回舊值
}
}
++modCount;//結構修改次數+1
if (++size > threshold)//當前大小超出閾值
resize();//執行擴容
afterNodeInsertion(evict);//移除最年長的,依據代碼邏輯分析是刪除當前key值為空的
return null;
}

3.ConcurrentHashMap

 

構造函數解讀
public
ConcurrentHashMap() {}//無參構造
給定初始化容量
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();//總量參數校驗
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?//是否超出最大容量的一半
MAXIMUM_CAPACITY ://當前容量設為最大容量
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));//獲取下次擴容容量大小 :如果當前容量為2的n次方+x,則下下次擴容容量=2的n+1次方
this.sizeCtl = cap;
}
重置方法:
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;//有些不明白,為什么這么做
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//如果容量為負值,則容量置為1
}
//初始化元素
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;//下次擴容容量,后面添加的時候會被修改
putAll(m);//添加所有,至於添加,會在后面分析
}
設置容量和負載因子
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);//並發級別設置為1級
}

public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)//負載因子和並發級別校驗
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel) // Use at least as many bins(容量至少等於並發級別)
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);//計算大小 容量/負載因子+1
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);//獲取下次擴容容量大小(同上)
this.sizeCtl = cap;
}
重要方法解讀:
public V put(K key, V value) {
return putVal(key, value, false);//可覆蓋
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();//鍵值空校驗
int hash = spread(key.hashCode());//hash計算
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();//表為空則初始化表
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//獲取表中位置元素
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))//空處理
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)//哈希相同
tab = helpTransfer(tab, f);//協助傳輸
else {
V oldVal = null;
synchronized (f) {//對象鎖
if (tabAt(tab, i) == f) {
if (fh >= 0) {//hash值大於0
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {//發生碰撞
oldVal = e.val;//獲取舊值
if (!onlyIfAbsent)
e.val = value;//設置新值
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);//執行新增
break;
}
}
}
else if (f instanceof TreeBin) {//屬於紅黑樹
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {//添加樹節點
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)//超出樹轉化閾值
treeifyBin(tab, i);//轉化
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);//數量+1
return null;
}
hash計算
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
初始化表空間
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {//空表執行初始化
if ((sc = sizeCtl) < 0)//擴容小於0
Thread.yield(); // lost initialization race; just spin 放棄初始化權利,讓初cpu
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//CAS獲取執行權限
try {
if ((tab = table) == null || tab.length == 0) {//再次校驗
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//初始化容量
table = tab = nt;
sc = n - (n >>> 2);//取半
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
 
         
注:
U.compareAndSwapInt方法說明:
改方法是一個原子方法,通過反射根據字段偏移去修改對象的
此這個方法有四個參數
  第一個參數為需要改變的對象
  第二個為偏移量(即之前求出來的valueOffset的值)
  第三個參數為期待的值
  第四個為更新后的值。
整個方法的作用即為若調用該方法時,value的值與expect這個值相等,那么則將value修改為update這個值,並返回一個true,
如果調用該方法時,value的值與expect這個值不相等,那么不做任何操作,並返回false

 

 

 

Kevin原創


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM