承接上篇《Java深刻研究Collection集合框架》文章中的HashMap、ConcurrentHashMap源码分析,在Java中经常使用的四个实现Map接口的类,分别是HashMap、TreeMap、LinkedHashMap以及继承自Dictionary抽象类的Hashtable,下面简单概述下各实现类的特色 :node
根据键的hashcode存储数据,容许null键/值(null键只容许一条,value能够有多条null),非synchronized、元素无序,顺序也可能随时改变,底层基于链表+红黑树实现【JDK1.8】算法
实现SortedMap接口,能够根据键排序,默认按键值升序排序,也能够指定排序的比较器,在使用时key必须实现Comparable接口,TreeMap在Iterator遍历是排过序的数组
属于HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先获得的记录确定是先插入的,也能够在构造时带参数,按照访问次序排序安全
经常使用功能跟HashMap相似,不支持null键/值,synchronized线程安全,Hashtable默认的初始大小为11,以后每次扩充,容量变为原来的2n+1.HashMap默认的初始化大小为16.以后每次扩充,容量变为原来的2倍,并发性不如ConcurrentHashMap,由于ConcurrentHashMap引入了分段锁bash
DEFAULT_INITIAL_CAPACITY =16 默认容量
MAXIMUM_CAPACITY =1 << 30 最大容量
DEFAULT_LOAD_FACTOR = 0.75f 默认负载因子
TREEIFY_THRESHOLD=8 链表转换红黑树的阀值
UNTREEIFY_THRESHOLD=6 红黑树转换链表的阀值
MIN_TREEIFY_CAPACITY=64 桶中bin最小hash容量,若是大于这个值会进行resize扩容操做,
此值至少是TREEIFY_THRESHOLD的4倍
复制代码
首先看初始化容量、负载因子的有参函数源码数据结构
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);
}
复制代码
常规的边界判断、赋值操做,经过tableSizeFor方法计算初始容量多线程
方法调用
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
传入key的hash计算
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
实际调用方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//局部node节点tab
Node<K,V>[] tab; Node<K,V> p; int n, i;
//将初始化的table赋值给tab并判null,若是为空则进行tab初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据hash计算tab[i]位置,判断若是为空则调用newNode()存储新的node<K,V>中
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//根据hash值和equals判断key,若是key相同就把老的node赋值给变量e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//key不一样,判断是否时红黑树,若是是则调用putTreeVal()放在树中
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) {
//没有下一个元素,则把当前元素传入newNode()做为下一个元素
p.next = newNode(hash, key, value, null);
//链表长度超过阈值TREEIFY_THRESHOLD=8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//转换成红黑树
break;
}
//判断key相同则赋值替换
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//判断value是否替换
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)//判断扩容阈值
resize();//扩容方法
afterNodeInsertion(evict);
return null;
}
复制代码
Initializes or doubles table size. If null, allocates in accord with initial capacity target held in field threshold. Otherwise, because we are using power-of-two expansion, the elements from each bin must either stay at same index, or move with a power of two offset in the new table并发
实现方法在【JDK1.7】和【JDK1.8】中有差别(1.8引入红黑树),感兴趣能够研究JDK源码对reszie()的实现
复制代码
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//hash值同put操做
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//判断tab节点是否为空,根据hash算出下标
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//在第一个node中查找
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//若是有下一个元素
if ((e = first.next) != null) {
//若是是树,调用getTreeNode()在红黑树中查找
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
//循环链表
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
复制代码
经过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而得到buckets的位置。若是比较的key相同,则利用key.equals()方法去链表或树中去查找对应的节点app
ConcurrentHashMap在Java 8中取消了Segment分段锁的数据结构,采用数组+链表+红黑树的数据结构,而对于锁的粒度,调整为对每一个数组元素加锁(Node节点),简化定位节点的hash算法,这样带来的弊端是hash碰撞会增大,所以在链表节点数量大于8时,会将链表转化为红黑树进行存储。这样一来,查询的时间复杂度就会由原先的O(n)变为O(logN)框架
CAS的全称叫"Compare And Swap",也就是比较并交换,使用时主要涉及到三个操做数,内存值V
、预期值A
、新值B
,若是在执行时发现内存值V
与预期值A
相匹配,那么他会将内存值V
更新为新值B
,相反处理器就不会执行任何操做
//用于table[]的初始化和扩容操做,-1表示正在初始化,-N表示有N个线程正在扩容,非负数时,表示初始化table[]的大小,已经初始化则表示扩容阈值,默认为table[]容量的0.75倍
private transient volatile int sizeCtl;
//表示默认的并发级别,也就是table[]的默认大小
private static finalint DEFAULT_CONCURRENCY_LEVEL = 16;
//默认的负载因子
private static final float LOAD_FACTOR = 0.75f;
//链表转红黑树的阀值
static final int TREEIFY_THRESHOLD = 8;
//红黑树转链表的阀值,
static final int UNTREEIFY_THRESHOLD = 6;
//哈希表的最小树形化容量
static final int MIN_TREEIFY_CAPACITY = 64;
复制代码
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);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
//主要是初始化map容量size、concurrencyLevel并发级别
}
复制代码
//常规put入口
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());//计算key 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();//常规初始化tab[]
//根据hash值与运算确认下标并将节点赋值给f,而后判null
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//若是为空,采用CAS算法将新值插入Node节点
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//hash值==-1,说明正在扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);//扩容后返回最新tab[]
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;//key相同,进行value替换,退出循环
}
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;
}
}
else if (f instanceof ReservationNode)//空节点,占位符
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
//链表转换红黑树阈值判断
if (binCount >= TREEIFY_THRESHOLD)
//与HashMap类中转换红黑树有区别,当hash表长度小于MIN_TREEIFY_CAPACITY属性值时尝试扩容操做,相反进行树形化
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
复制代码
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());//计算key hash
//判断table[]是否为null,根据下标确认table[i]节点并作非null约束
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//比较头部元素是否相同,相同则直接返回该键对应的值
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
//若是头结点的 hash 小于 0,说明正在扩容,或者该位置是红黑树
else if (eh < 0)
//e.find可对比查看ForwardingNode类的find()、TreeBin类的find()源码
return (p = e.find(h, key)) != null ? p.val : null;
//遍历链表
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
复制代码
加入星球一块儿讨论项目、研究新技术,共同成长!