HashMap和HashTable异同
1. 相同点:
- 基于哈希表实现,内部维护了一个存储数据的Entry数组,每一个元素都是key-value对,元素内部通过单链表解决冲突问题。
- Entry数组容量不够时,会进行resize扩容,size为底层数组已用槽的数量,threshold=容量*加载因子(默认是0.75)是阈值。当size大于threshold的时候会进行扩容。新建一个底层数组,然后把原来数组的全部元素重新添加到新的数组中,需要重新计算位置。
- 实现了Serializable接口,可以序列化;实现了Cloneable接口,能够被克隆
- 都含有containsKey和containerValue方法
- 都实现了Map接口
2.不同点:
- 继承父类不同,HashMap是继承AbstractMap类的,Hashtable继承Dictionary类(已废弃)
- Hashmap没有container方法,Hashtable是有的
- HashMap允许key和value都是null,Hashtable是不可以的
- 计算hash值的方式不同:
HashMap:(h == key.hashcode())^(h >>> 16)
Hashtable:key.hashcode() - 扩容的机制不同:
HashMap:默认容量是16,每次扩容都是原数组容量的2倍
Hashtable:默认容量是11,每次扩容都是原数组的2倍+1 - 计算索引位置不同:
HashMap:
int hash = hash(key)
int index = indexFor(hash, length)
int hash(object x){
int h = x.hashcode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
int indexFor(int h, int length){
return h&(length - 1);
}
Hashtable:
int index = (key.hashcode() & 0x7FFFFFFF) % length
- Hashmap是线程不安全的,Hashtable是线程安全的,Hashtable中大多数方法是synchronized
ConcurrentHashMap的诞生
由segment数组和HashEntry数组组成的,segment是一种可重入锁reantrantLock。
- 初始化:输入参数initialCapacity是初始化容量,默认16,loadFactor是每个segment的负载因子,默认0.75
- get操作:先哈希,使用该值通过哈希运算定位到segment,再通过哈希算法定位到元素。
注意:get操作不加锁。get方法里将要使用的共享变量都定义成volatile,如统计当前segment大小的count字段和用于存储值的HashEntry的value值。 定义成了volatile,线程之间保持可见性,被多线程同时读。 - put操作:操作共享变量时必须加锁,先定位到segment,再在segment里进行插入操作。
插入:1. 判断是否需要对segment里的HashEntry数组进行扩容:插入前先判断是否超过threshold,如超过则进行扩容。与HashMap有所不同,Hashmap是插入后判断是否达到threshold,如果达到了进行一次扩容,带来的问题是有可能是扩容之后再也没有新元素插入,本次扩容就是无效的。 2. 定位位置并插入。 - size操作:先尝试2次通过不锁住segment方式来统计segment大小,如果容器的count发生了变化,再采用加锁的方式来统计所有segment大小。那么如何判断统计的时候容器是否变化呢:使用modCount变量,在put,remove和clean等操作元素前会将变量modeCount+1,在统计size的前后比较modCount是否发生了变化。