HashTable | HashMap | |
---|---|---|
继承父类的不同 | Dictionary | AbstractMap |
都同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口 | 都同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口 | |
默认容量 | 11 | 16 |
Table的初始化时机 | 构造函数中初始化 | 第一次put |
并发操作 | 使用同步机制, 实际应用程序中,仅仅是Hashtable本身的同步并不能保证程序在并发操作下的正确性,需要高层次的并发保护。 下面的代码试图在key所对应的value值等于x的情况下修改value为x+1 { value = hashTable.get(key); if(value.intValue()== x){ hashTable.put(key, new Integer(value.intValue()+1)); } } 如2个线程同时执行以上代码,可能放入不是x+1,而是x+2. | 没有同步机制,需要使用者自己进行并发访问控制 |
数据遍历的方式 | Iterator 和 Enumeration | Iterator |
是否支持fast-fail | 用Iterator遍历,支持fast-fail 用Enumeration不支持fast-fail. | 支持fast-fail |
是否接受值为null的Key 或Value? | 不接受 | 接收 |
根据hash值计算数组下标的算法 | 当数组长度较小,并且Key的hash值低位数值分散不均匀时,不同的hash值计算得到相同下标值的几率较高 hash = key.hashCode(); index=(hash&0x7FFFFFFF) % tab.length; //去掉其符号位置 | 优于hashtable,通过对Key的hash做移位运算和位的与运算,使其能更广泛地分散到数组的不同位置 final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); hash = hash (k); index = indexFor(hash, table.length); static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; }把后面几位移动到前面 static int indexFor(int h, int length) { return h & (length-1); } |
Entry数组的长度 | 缺省初始长度为11,初始化时可以指定initial capacity | 缺省初始长度为16,长度始终保持2的n次方初始化时可以指定initial capacity,若不是2的次方,HashMap将选取第一个大于initial capacity 的2n次方值作为其初始长度 |
LoadFactor负荷因子 | 0.75 | 0.75 |
负荷超过(loadFactor * 数组长度)时,内部数据的调整方式 | 扩展数组:2*原数组长度+1 | 扩展数组: 原数组长度 * 2 |
– | 两者都会重新根据Key的hash值计算其在数组中的新位置,重新放置。算法相似,时间、空间效率相同 | 两者都会重新根据Key的hash值计算其在数组中的新位置,重新放置。算法相似,时间、空间效率相同 |
ConcurrentHashMap
继承关系
讲解的并发集合源码是基于JDK 1.7实现讲解
ConcurrentHashMap的继承关系如下:
通过上图可知,ConcurrentHashMap是属于ConcurrentMap接口,而ConcurrentMap接口又是Map接口的子接口,ConcurrentHashMap是实现了Map提供的所有方法
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable
ConcurrentMap接口提供方法如下:
public interface ConcurrentMap<K, V> extends Map<K, V> {
//如果指定键已经不再与某个值相关联,则将它与给定值关联 ->key不存在该集合中,将进行put插入操作,key存在则直接返回
V putIfAbsent(K key, V value);
//只有目前将键的条目映射到给定值时,才移除该键的条目 ->key和value键值对同时存在则删除,否则不进行操作
boolean remove(Object key, Object value);
//只有目前将键的条目映射到给定值时,才替换该键的条目 ->key和oldvalue键值对存在时,才替换为key和newvalue键值对
boolean replace(K key, V oldValue, V newValue);
//只有目前将键的条目映射到某一值时,才替换该键的条目 ->key存在集合中,才进行key和value键值对的更新
V replace(K key, V value);
}
构造函数
ConcurrentHashMap提供了5个构造函数,主要的构造函数是ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
其中initialCapacity叫做初始化容量,不是整个COncurrenthashmap的容量,而是table属性的初始数组容量,大于给定值的最小的2的幂
loadFactor是加载因子,
concurrencyLevel是并发度,指的是外层数组的大小,即segments数组的大小,大于给定值的最小的2的幂
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//参数校验
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; /4
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
内部类Segment声明
static final class Segment<K,V> extends ReentrantLock implements Serializable
Segment是继承ReentrantLock类,该类是重入锁
属性和默认值
//默认的初始容量 默认16
static final int DEFAULT_INITIAL_CAPACITY = 16;
默认的加载因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认的并发度
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
容量的最大值 2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
最小的segment的表容量 2
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
最大的segment的容量 2^16
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
锁重试次数2
static final int RETRIES_BEFORE_LOCK = 2;
数据存储在segment数组中
final Segment<K,V>[] segments;
static final class Segment<K,V> {
transient volatile HashEntry<K,V>[] table //数据存储在table属性中
}
class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
底层的数据结构是数据+Hash表结构
ConcurrentHashMap存储数据在segments属性上,该属性是一个Segment数组,该数组存储都是Segment类型的数据,而Segment类中存储数据在table属性上,table属性是HashEntry类型的数组,其数据通过数组加链表来存储数据,具体存储格式示意图如下:
ConcurrenthashMap的特点
1、ConcurrentHashMap的扩容针对是是table扩容,segment不进行扩容
2、ConcurrenthashMap的扩容是原table大小的2倍
3、ConcurrentHashMap中key和value都不能为空
4、ConcurrentHashMap的加锁针对的是每一个Segment加锁(tryLock(), unlock())