java.util.HashMap
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
1.7
===================================================================
1.8
数组+链表
===================================================================
数组+链表+红黑树的存储结构存储的 Node<K,V> 节点
维护的 Node<K,V>[] table 数组
通过 Node<K,V> nex 维护链表
通过 TreeNode<K,V> 维护红黑树
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //tips.1.维护 key的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 int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
}
hashMap 初始化容量 16
通过扩容因子DEFAULT_LOAD_FACTOR=0.75f。
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
// threshold 阈值, 初始化table未分配时,该值就是array的初始化容量
int threshold;
hashMap 流程
1、HashMap<String,String> map = new HashMap<String,String>();
无参构造定义默认加载因子。此时 table = null;
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
上面一段很牛逼, 1.7中要求 capacity 值为2的倍数,但是怎么确保呢?
jdk1.8 通过以上的方法将数字转换为最近较大的2的幂次
map.put();
jdk1.7 hashmap 在put key-value 键值对时,1:判断容量大小,是否需要扩容,2、找到hash对应的数组索引,3、挂载到响应位置
在remove key-value 键值对时,1:仅仅是对 pre节点和next节点的转换,并没有减少容量的操作
===================================================================
jdk1.8 hashmap 在put key-value 键值对时,1:判断table.length ,是否需要扩容,如果达到max,则可以一直在下面进行挂载节点
2:判断高度是否到了tree化高度 8 ,到了则指定链表转化成红黑树
3:往红黑树里 put值 涉及到旋转和变色
remove 键值对 涉及到红黑树的自平衡,红黑树——》 链表
//table = null 或者长度为0时, 初始化table
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold 有参构造传参capacity时初始化
newCap = oldThr;
else { // zero initial threshold signifies using defaults threshold
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) { //oldTab 有值即为扩容时,
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null; //先将 原数组的值 置为null
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e; // 插入新数组中
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap); //红黑树节点的转换 这一段最好先看懂 put中红黑树节点的插入来理解
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
/**
* Tree version of putVal.
*/
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) { // 根据hash 作为红黑树排序的值 left.hash < root.hash < right.hash
int dir, ph; K pk; // 通过二叉树插入节点的方法 找到对应的节点
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk))) //排除 hash相等 key相同的情况 验证 类如果作为set或者hashMap的key要重写 hashCod和equals 方法
return p;
else if ((kc == null && //key 不为自定义对象的情况, key的类要实现comparable接口
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) { //直到找到nil java中的null 叶子节点 执行插入插入操作
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x)); //自平衡 并将根节点赋值到tab[index]上
return null;
}
}
}
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
// 这是一层树形结构,也就是只有一个节点
// 直接进行着色操作
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// 这里是二层树形结构,xp 存在,或者xpp 为空
// 这里需要说明一下,有人会认为这不仅仅包含二层树形结构,可能还有三层
// 从条件上来说是的,可能包含三层,但是这里有一个我们上面说的前提,那就是插入之前必须是红黑树,
// 那也就是说!xp.red 意思就是 xp 为黑色节点,而且xp 是符合红黑树的,那么插入一个红色节点是不会改变它是红黑树的特性
// 反过来想插入之前的问题
// 这里我们把问题缩小一点,就插入点的三层树形结构。而且插入之前还必须符合红黑树
// 如果xp 插入之前是黑色的并且符合红黑树,这种特性是不是跟只有一个根节点是一样的
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 三层结构,如果是左分支
if (xp == (xppl = xpp.left)) {
// 这里是判断前两层是否是满二叉树,也就是是否满足h <= log2(n+1)
// 如果满足是不需要进行 旋转的,因为无法对其旋转(左旋,右旋),
// 也不需要旋转,只需要进行重新着色就可以满足红黑树的五大特性了
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
// 接下来就是 不满足满二叉树,或者说 不满足h <= log2(n+1)
// 这个时候我们就需要考虑旋转了,怎样旋转呢,这里我先说结果,后面分析原因,或者为什么
if (x == xp.right) {
// 树形结构是 L — R --> 左旋,变成 L— L树形结构
// x、xp、xpp 重新赋值
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
// xp 着色
xp.red = false;
if (xpp != null) {
// xpp 着色
xpp.red = true;
// L — L --> 右旋
// 将三层树形结构 右旋 变成一个 二层结构,满足 h <= log2(n+1) 特性
root = rotateRight(root, xpp);
}
}
}
}
else {
// 这里也是一样的
// 判断前两层是否是满二叉树,也就是是否满足h <= log2(n+1)
// 如果满足是不需要进行 旋转的,因为无法对其旋转(左旋,右旋),
// 也不需要旋转,只需要进行重新着色就可以满足红黑树的五大特性了
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
// 接下来也是 不满足满二叉树,或者说 不满足h <= log2(n+1)
if (x == xp.left) {
// 树形结构是 R — L --> 右旋,变成 R— R树形结构
root = rotateRight(root, x = xp);
// x、xp、xpp 重新赋值
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
// R — R --> 右旋
// 将三层树形结构 左旋 变成一个 二层结构,满足 h <= log2(n+1) 特性
root = rotateLeft(root, xpp);
}
}
}
}
}
}
https://www.jianshu.com/p/e136ec79235c 红黑二叉树机制
https://www.jianshu.com/p/c67284ef9e00 HashMap—红黑树算法结构详解