HashMap: (JDK1.8)
HashMap是基于map接口实现的,允许使用null值,null键,由于key不允许重复,因此只有一个key为null,另外HashMap不保存元素放入顺序,是线程不安全的。
HashMap底层是基于数组实现, 数组中每一项是单向链表,即数据和链表结合体,当链表长度大于一定阀值,转为红黑树
HashMap在底层将key-value当成一个整体,就是一个node对象,采用一个node[]数组保存
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
* 构建一个空HashMap 具有指定的初始容量和负荷因子
* @param initialCapacity the initial capacity 初始容量
* @param loadFactor the load factor 负荷因子
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive (异常)
* MAXIMUM_CAPACITY = 1<<30 == 1073741824
*/
继承关系
基础属性: static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 // 初始容量 16 static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认负荷系数 static final int TREEIFY_THRESHOLD = 8; static final int UNTREEIFY_THRESHOLD = 6; static final int MIN_TREEIFY_CAPACITY = 64;
static class Node<K,V> implements Map.Entry<K,V> { final int hash;//用于定位数组索引的位置 final K key; V value; Node<K,V> next; // 下一个node 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; } }
计算 hashMap hash
hashMap put方法
put函数大致的思路为:
- 对key的hashCode()做hash,然后再计算index;
- 如果没碰撞直接放到bucket里;
- 如果碰撞了,以链表的形式存在buckets后;
- 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
- 如果节点已经存在就替换old value(保证key的唯一性)
- 如果bucket满了(超过load factor*current capacity),就要resize。
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
//生成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<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;//创建一个新的table数组,并且获取该数组的长度
//根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加
if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { // 如果对应节点存在 Node<K,V> e; K k; //判断table[i]的首个元素是否和key一样,如果相同直接覆盖value if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) //判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {// 该链为链表 //遍历table[i],判断链表长度是否大于TREEIFY_THRESHOLD(默认值为8), //大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可; 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; p = e; } } 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; }
1.7中 HashMap put方法
public V put(K key, V value) { // 如果table引用指向成员变量EMPTY_TABLE,那么初始化HashMap(设置容量、临界值,新的Entry数组引用) if (table == EMPTY_TABLE) { inflateTable(threshold); } // 若“key为null”,则将该键值对添加到table[0]处,遍历该链表,如果有key为null,则将value替换。没有就创建新Entry对象放在链表表头 // 所以table[0]的位置上,永远最多存储1个Entry对象,形成不了链表。key为null的Entry存在这里 if (key == null) return putForNullKey(value); // 若“key不为null”,则计算该key的哈希值 int hash = hash(key); // 搜索指定hash值在对应table中的索引 int i = indexFor(hash, table.length); // 循环遍历table数组上的Entry对象,判断该位置上key是否已存在 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 哈希值相同并且对象相同 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // 如果这个key对应的键值对已经存在,就用新的value代替老的value,然后退出! V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 修改次数+1 modCount++; // table数组中没有key对应的键值对,就将key-value添加到table[i]处 addEntry(hash, key, value, i); return null; }
hashMap get方法
public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; }
final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { 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; }
扩容机制
void resize(int newCapacity) { //传入新的容量
Entry[] oldTable = table; //引用扩容前的Entry数组
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) { //扩容前的数组大小如果已经达到最大(2^30)了
threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
return;
}
Entry[] newTable = new Entry[newCapacity]; //初始化一个新的Entry数组
transfer(newTable); //!!将数据转移到新的Entry数组里
table = newTable; //HashMap的table属性引用新的Entry数组
threshold = (int)(newCapacity * loadFactor);//修改阈值
}
LinkedHashMap(JDK1.8)
继承与HashMap,底层使用hash表和双向链表来保存所有元素,允许使用null值和null键
/ /双向链表的头部(eldest)
transient LinkedHashMap.Entry<K,V> head;
//双向链表的尾部(youngest)
transient LinkedHashMap.Entry<K,V> tail;
//accessOrder为true时,按访问顺序排序,false时,按插入顺序排序
final boolean accessOrder
LinkedHashMap的Entry是继承与Node类,也就是LinkedHashMap与HashMap的根本区别所在,Node是链表形式,只有next与下一个元素进行连接,而Entry的链表有before和after两个连接点。
区别是将节点变成Entry,并且按照链表方式将元素有序连接
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; // 若访问顺序为true,且访问的对象不是尾结点 if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } } 就是说在进行put之后就算是对节点的访问了,那么这个时候就会更新链表,把最近访问的放到最后,保证链表。关键参数是accessOrder, 这个参数只有在public LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder) 中可以手动设置为true,其余时候都默认为false
LinkedHashMap get方法
public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder)//安装访问顺序而不是插入的顺序,此时会发生结构改变 afterNodeAccess(e); return e.value; }
ConcurrentHashMap
多线程安全的,底层数据与HashMap数据结构相同。
ConcurrentHashMap在底层将key-value当成一个整体处理,也就是一个entry
// 表的最大容量 private static final int MAXIMUM_CAPACITY = 1 << 30; // 默认表的大小 private static final int DEFAULT_CAPACITY = 16; // 最大数组大小 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 默认并发数 private static final int 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; // 每次进行转移的最小值 private static final int MIN_TRANSFER_STRIDE = 16; // 生成sizeCtl所使用的bit位数 private static int RESIZE_STAMP_BITS = 16; // 进行扩容所允许的最大线程数 private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1; // 记录sizeCtl中的大小所需要进行的偏移位数 private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; // 一系列的标识 static final int MOVED = -1; // hash for forwarding nodes static final int TREEBIN = -2; // hash for roots of trees static final int RESERVED = -3; // hash for transient reservations static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
ConcurrentHashMap 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(); // 键或值为空,抛出异常 // 键的hash值经过计算获得hash值 int hash = spread(key.hashCode()); int binCount = 0; for (Node<K,V>[] tab = table;;) { // 无限循环 Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) // 表为空或者表的长度为0 // 初始化表 tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 表不为空并且表的长度大于0,并且该桶不为空 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) // 比较并且交换值,如tab的第i项为空则用新生成的node替换 break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) // 该结点的hash值为MOVED // 进行结点的转移(在扩容的过程中) tab = helpTransfer(tab, f); else { V oldVal = null; synchronized (f) { // 加锁同步 if (tabAt(tab, i) == f) { // 找到table表下标为i的节点 if (fh >= 0) { // 该table表中该结点的hash值大于0 // binCount赋值为1 binCount = 1; for (Node<K,V> e = f;; ++binCount) { // 无限循环 K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { // 结点的hash值相等并且key也相等 // 保存该结点的val值 oldVal = e.val; if (!onlyIfAbsent) // 进行判断 // 将指定的value保存至结点,即进行了结点值的更新 e.val = value; break; } // 保存当前结点 Node<K,V> pred = e; if ((e = e.next) == null) { // 当前结点的下一个结点为空,即为最后一个结点 // 新生一个结点并且赋值给next域 pred.next = new Node<K,V>(hash, key, value, null); // 退出循环 break; } } } else if (f instanceof TreeBin) { // 结点为红黑树结点类型 Node<K,V> p; // binCount赋值为2 binCount = 2; if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { // 将hash、key、value放入红黑树 // 保存结点的val oldVal = p.val; if (!onlyIfAbsent) // 判断 // 赋值结点value值 p.val = value; } } } } if (binCount != 0) { // binCount不为0 if (binCount >= TREEIFY_THRESHOLD) // 如果binCount大于等于转化为红黑树的阈值 // 进行转化 treeifyBin(tab, i); if (oldVal != null) // 旧值不为空 // 返回旧值 return oldVal; break; } } } // 增加binCount的数量 addCount(1L, binCount); return null; }