参考:http://www.cnblogs.com/joemsu/p/7724623.htmlgit
jdk1.8:数组、链表/红黑树(jdk1.7的是数组+链表)github
节点数据类型:
Node<K,V>(hash,key,value,next)数组
static class Node<K,V> implements Map.Entry<K,V> { // key & value 的 hash值 final int hash; final K key; V value; //指向下一个节点 Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
/默认初始化map的容量:16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //map的最大容量:2^30 static final int MAXIMUM_CAPACITY = 1 << 30; //默认的填充因子:0.75,能较好的平衡时间与空间的消耗 static final float DEFAULT_LOAD_FACTOR = 0.75f; //将链表(桶)转化成红黑树的临界值 static final int TREEIFY_THRESHOLD = 8; //将红黑树转成链表(桶)的临界值 static final int UNTREEIFY_THRESHOLD = 6; //转变成树的table的最小容量,小于该值则不会进行树化 static final int MIN_TREEIFY_CAPACITY = 64; //上图所示的数组,长度老是2的幂次 transient Node<K,V>[] table; //map中的键值对集合 transient Set<Map.Entry<K,V>> entrySet; //map中键值对的数量 transient int size; //用于统计map修改次数的计数器,用于fail-fast抛出ConcurrentModificationException transient int modCount; //大于该阈值,则从新进行扩容,threshold = capacity(table.length) * load factor int threshold; //填充因子 final float loadFactor;
根据key的hashCode计算在数组里的下标,而后再判断该位置是否符合条件,不然根据链表/红黑树的方法继续找安全
计算下标的方法:
下标 = (n-1) & hash
hash = (h = key.hashCode()) & h>>>16数据结构
判断两个key是否相同:性能
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
先判断hash,若是hash不一样必定不一样;若是hash相同,则利用equals判断是否相同this
先检查容量(扩容resize)。
根据hashCode计算桶在数组里的位置,
插入该处的链表/树线程
两倍容量。若是容量已达最大值,则设置容量为int_max。
扩容以后要从新计算index,每一个桶里的节点都要从新计算位置,计算位置的方法是hash & (n-1)。
扩容以后因为newCap-1比oldCap-1多了一个高位(例如8-1=7=111,而4-1=3=11),所以节点的新位置取决于多出来的一个高位bit,若是该位是0,则仍是原位置;不然应该是原位置+旧容量code
扩容的过程对整个数组和桶进行了遍历,耗费性能,因此最好在建立HashMap的时候指定容量,不然在第一次put的时候会进行一次扩容(由于table==null)