HashTable & HashMap & ConcurrentHashMap 原理与区别

时间:2022-11-11 15:59:47

一.三者的区别

 
  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原创