字典查找 其实做 Java
的应该都知道 Map
和 HashMap
,这个类其实就是字典查找实现。他的前身是 Dictionary
以及 Hashtable
。因为之前的类库,大部分方法都被加上 synchronized
标记,导致这些类库在运行的时候,效率都不高。所以后期都是使用 HashMap
这一类来进行操作。 不过现在我们要看的是 字典
是什么,我们看书的时候基本都有一个东西叫做 目录
,这个东西就可以用来查询哪个标题大概在哪一页开始,而我们程序所说的就是这个东西,一个类似于 目录
的 Key-Value
对应的结构,当我们为了实现一些需求的时候需要将一类数据作为 Key
,另外一类数据作为 Value
存入从而实现计算的时候,就可以使用 字典
这种数据结构了。 而实现 字典
也有不同方式,我们还是慢慢的一步一步接近,目的就是为了设计一个高性能的字典。 那 API
我们可以定义类似于 Map
接口,然后使用不同的结构来实现我们的功能,首先我们先定义我想要的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 public interface Dictionary <K extends Comparable <K>, V> { void put (K key, V val) ; V get (K k) ; V delete (K key) ; boolean contains (K key) ; boolean isEmpty () ; int size () ; K min () ; K max () ; K floor (K key) ; K ceiling (K key) ; int rank (K key) ; K select (int k) ; Iterable<K> keys () ; }
顺序查找 那最简单的就是使用一个 数组
或者 链表
来做存储,然后我们需要的所有操作,都需要通过遍历这个存储来做。由于操作数量还是蛮多的,用 链表
可以很简单的操作,所以我们直接使用链表来做了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class UnorderedDictionary <K extends Comparable <K>, V> implements Dictionary <K, V> { private Node first; private class Node { K key; V val; Node next; public Node (K key, V val, Node next) { this .key = key; this .val = val; this .next = next; } } public void put (K key, V val) { Node newNode = new Node (key, val, null ); if (first != null ) { for (Node f = first; f != null ; f = f.next) { if (f.key.equals(key)) { f.val = val; return ; } } Node f = first; while (f.next != null ) { f = f.next; } f.next = newNode; } else { first = newNode; } } public V get (K k) { if (first == null ) { return null ; } for (Node f = first; f != null ; f = f.next) { if (f.key.equals(k)) { return f.val; } } return null ; } public static void main (String[] args) { Dictionary<Integer, Integer> unordered = new UnorderedDictionary <>(); for (int i = 0 ; i < 100_000 ; i++) { unordered.put(i, i); } System.out.println("开始查找..." ); long start = System.nanoTime(); Integer val = unordered.get(10_000 - 1 ); System.out.println("查找到:" + val + " 共用时" + (System.nanoTime() - start)); } } 开始查找... 查找到:999 共用时143015
OK,没有任何辅助的结构,单纯简单粗暴的进行循环遍历,get(K k)
每次都需要通过遍历整个链表,这种实现方式无疑效率是最低的,因为随着链表的长度增加,时间成本将会原来越长。这种好比 选择排序
一样,遍历每个元素与后面的元素依次遍历。这就可以通过证明,我们查找第一个元素需要比较 1次
,查找第二个元素需要比较 2次
,而如果我们元素刚好在最后一位的话就需要 1 + 2 + 3 + ... + N = N / 2
而 put(K key, V val)
更加离谱,每次加入都需要循环整个链表。
二分查找 二分查找比较简单,我们可以使用一个 Key[]
和 Value[]
来存储 Key和Value
。然后先查询 Key
对应的下标,然后直接命中 Value[indexOf(Key)]
即可,查找 Key
的过程我们就可以使用 二分查找
的方法来查找。至于插入,我们需要根据顺序来进行整容,将原有的 Key和Value数组
进行整理再将新的值插入到对应的位置,查找的过程也是使用到了 二分查找法
。 实现并不太难,不写了-,-
二叉查找树 那么,通过上面的实践,我们可以再进一步优化成树形结构(其实编程貌似万物都可用树-,-),首先我们先用简单的 二叉查找树
来做,二叉查找
具有以下的特点:
所有节点的左节点的值都比当前节点小;
所有节点的右节点的值都比当前节点大;
左右子树也是二叉查找树;
没有键值相等的节点。
一颗典型的 二叉查找树
:
那么我们可以很简单的写出实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class BSTDictionary <K extends Comparable <K>, V> implements Dictionary <K, V> { private Node root; private class Node { K key; V val; Node left, right; public Node (K key, V val) { this .key = key; this .val = val; } } @Override public void put (K key, V val) { root = put(root, key,val); } private Node put (Node n, K key, V val) { if (n == null ) { return new Node (key, val); } int compare = key.compareTo(n.key); if (compare == 0 ) { n.val = val; } else if (compare > 0 ) { n.right = put(n.right, key, val); } else { n.left = put(n.left, key, val); } return n; } @Override public V get (K k) { return get(root, k); } private V get (Node n, K key) { if (n == null ) { return null ; } int compare = key.compareTo(n.key); if (compare == 0 ) { return n.val; } else if (compare > 0 ) { return get(n.right, key); } else { return get(n.left, key); } } public static void main (String[] args) { Dictionary<Integer, Integer> unordered = new BSTDictionary <>(); for (int i = 0 ; i < 1_000 ; i++) { unordered.put((int ) (i + 100 * Math.random()), i); } System.out.println("开始查找..." ); long start = System.nanoTime(); Integer val = unordered.get(1_000 - 1 ); System.out.println("查找到:" + val + " 共用时" + (System.nanoTime() - start)); } } 开始查找... 查找到:915 共用时73567
上面的代码是一个及其简单的实现,我们也加入了假设,假设每次插入的 Key
总是均匀分布的。不然这棵树就会变成这样的:
不仅仅插入的时候会导致这种情况,当我们调用 del
删除节点的时候,也可能会出现这种情况,所以怎么维护这棵树的最佳查找性能,就成了现在的话题了。接下来的结构基本都会围绕这个问题来展开。
平衡查找树(2-3树) 从上面的 二叉查找树
我们可以看到,即使随机的情况下树的性能还是不错的,但是如果我们顺序插入就会导致上面最后一个图中最坏的情况,当我们需要查询 节点4
的时候,我们一直在右树查找,这么看来性能已经达到了线性的程度了。那这样下去肯定是不行的(带去河边?) 所以有了 平衡查找树
,又称为 2-3树
,为啥,因为在上面 二叉查找树
中,节点都是 2节点
,何为 2
,因为每个节点只有2个子节点。那么我们现在插入一个概念就是 3节点
,意思就是有些节点他有 3个
子节点。如图:
我们可以看到,在上面这棵树中,存在 2
个 3节点
,3节点
左边的节点是比节点左边的值小的节点,中间则是介于 3节点
两个数值之间,右边就是比 右边的数
大的节点了。那么这种有什么好处,答案就是整容方便。那么怎么整容呢,那就出现两种情况了:
如果查找插入的节点是一个 2节点
如果插入的节点是一个 2节点
,那么操作显然要简单很多,我们只需要简单把原来 2节点
转换为 3节点
即可。
如果查找插入的节点是一个 3节点
那如果我们需要插入的数是 60
,就会碰到 59-69
这个节点,那能咋办,往上生长呗:
那如果碰上父父父节点到根节点都是 3节点
的情况,树的高度就会增加 1
:
红黑树 直接进入红黑树,红黑树如果没有上面 2-3树
的理解的话,完全不知道是什么鬼东西,不过现在看来一切将会清晰: 首先我取上面动图一颗左子树来示例:
用红黑树表示:
然后再把红线拉平:
那么很显然,她就是一颗 2-3树
了,只不过通过连接线的颜色来表示他是 2节点
还是 3节点
。 那么很显然为了编码方便,红黑树
加入一些定义:
红链接
均为 左链接
;
没有任何一个节点同时和 两个红链接
相连;
所有叶子都是黑色(叶子是NUIL节点);
从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点;
根节点是黑色。
同样的,我们需要一个内部 Node
类来表示节点:
1 2 3 4 5 6 7 8 private class Node { K key; V val; Node left, right; boolean isRed; }
红黑树的增删 当我们对树进行插入操作或者删除操作的时候,在 2-3树
说到这棵树就需要通过变型,让树来达到平衡,那么 红黑树
同样也需要进行变型,而变型就是通过 旋转
来做的。 先来解释什么是旋转,旋转又分为 左旋转
和 右旋转
:
那么最后这棵子树就变成这个样子:
那右旋转基本和左旋一样,这个动图比较好画-,-:
什么时候需要旋转: 其实每次插入的时候,一般都需要旋转,而旋转的地方,就是我们插入的那一小部分树 : 那我分别插入最小值、最大值和中间值来看这棵树:
那么我们现在要插入 9
:
出现了两个红色的左连接,那么我们右旋转一次:
出现了 10节点
有两个红色链接,那么我们只需要将 10节点
和 根节点
转换颜色即可:
对应的转换颜色的代码:
1 2 3 4 5 private void changeColor (Node node) { node.isRed = true ; node.left.isRed = false ; node.right.isRed = false ; }
那 20节点
和 25节点
之间应该是红色才对?没关系,如果我们插入的值是 10 - 20
之间的数值的时候,他不就平衡了咩~
那先在刚好,在上一节我们说插入 10 - 20
之间的数值的时候就会平衡,那我们现在就来插入 15
:
比方说插入 101
:
出现了右红色链接,这时候需要对 100节点
进行一次左旋转:
即可达到稳定。 那现在我们就可以来编写 put
的代码了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 public class RBTDictionary <K extends Comparable <K>, V> implements Dictionary <K, V> { private Node root; private class Node { K key; V val; Node left, right; boolean isRed; public Node (K key, V val, boolean isRed) { this .key = key; this .val = val; this .isRed = isRed; } } private Node routateLeft (Node node) { Node right = node.right; node.right = right.left; right.left = node; right.isRed = node.isRed; node.isRed = true ; return right; } private Node rotateRight (Node node) { Node left = node.left; node.left = left.right; left.right = node; left.isRed = node.isRed; node.isRed = true ; return left; } private void changeColor (Node node) { node.isRed = true ; node.left.isRed = false ; node.right.isRed = false ; } private boolean isRed (Node node) { if (node == null ) { return false ; } return node.isRed; } @Override public void put (K key, V val) { root = put(root, key, val); root.isRed = false ; } private Node put (Node node, K key, V val) { if (node == null ) { return new Node (key, val, true ); } int compare = key.compareTo(node.key); if (compare < 0 ) { node.left = put(node.left, key, val); } else if (compare > 0 ) { node.right = put(node.right, key, val); } else { node.val = val; } if (isRed(node.right) && !isRed(node.left)) { node = routateLeft(node); } if (isRed(node.left) && isRed(node.left.left)) { node = rotateRight(node); } if (isRed(node.left) && isRed(node.right)) { changeColor(node); } return node; } @Override public V get (K k) { return get(root, k); } private V get (Node n, K key) { if (n == null ) { return null ; } int compare = key.compareTo(n.key); if (compare == 0 ) { return n.val; } else if (compare > 0 ) { return get(n.right, key); } else { return get(n.left, key); } } public static void main (String[] args) { Dictionary<Integer, Integer> dic = new RBTDictionary <>(); for (int i = 0 ; i < 20 ; i++) { dic.put(i, i); } } }
红黑树的查找 红黑树的查找过程上面代码已经体现,跟 普通二叉查找树
的算法一致。
红黑树的删除 红黑树的删除,可能会使一个 2节点
的子节点是 NULL
,我们为了避免这种事情的发生,需要对树进行调整,分为以下几种情况:
如果根节点不是 2节点
,完成;
如果当前节点的左节点是 2节点
而兄弟节点不是,则可以从 兄弟节点
借一个过来,然后再做删除处理;
如果当前节点和兄弟节点都是 2节点
,则需要从父节点中借出一个节点来辅助变成 4节点
然后再进行删除。
优秀点 那么 红黑树
我们可以看到跟 普通查找二叉树
的查找算法是一致的,那他优秀在哪里。因为规定了 红黑链接
,所以他在插入删除的时候,也需要进行特定操作,但是具体的性能基本上都是 logN
。即使在最差的情况下,也可以将保持树形的平衡,并且需要移动的节点很少。
散列表 散列表的说法相信用 Java
的多少有听说过 HashMap
的实现原理,那就是通过一个对象,通过 jvm
给定的 hashCode
进行一系列操作得出来一个数组的下标,那么这个 Key对象
就会被散落在对应的数组下标。
1 2 3 4 5 6 7 8 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); } if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null );
那么首先将 hashCode
向右移动 16
位,目的是打乱 hashCode
低位,然后再跟数组长度进行与操作,尽可能的散落均匀。 但是,无论再怎么均匀,总会出现 hash碰撞
的情况。那么就有以下两种方式来进行解决:
HASH碰撞拉链法 我们假设,node[]
是用来存储对象的数组,那么如果我们插入的时候通过计算,得到的数组下标那个位置已经有对象存在了,那这个位置就会形成一个单向链表,即 node.next = newObj
,这种就是拉链法,HashMap
在树形化之前也是采用这种方式来解决 Hash碰撞
的问题,那搜索的时候我们就需要拿到下标,然后一步一步 next
的去 equals
再取出对象的 Key对象
和 Value值
。
HASH碰撞线性探测法 如果不使用链表来解决碰撞问题的话,那么可以使用数组探测的形式来做,如果插入一个值,这个值的 hash
过以后对应数组的位置上已经存在其他值了,那么将 hash
后的 数组下标
自增1(遇到数组尾部折回去头部),然后再插入。查找的时候就需要先得到 hash
后的数组下标,然后使用比较 equals
接下去数组位去探测是否相同,相同则返回。 删除的时候会显得麻烦很多,因为我们在使用线性探测的时候,会使用 null
来标记查找结束,如果简单粗暴的设置为 null
,则被删除后面的元素也会顺带着被删除,正确的做法则是:我删除了这个位置上的元素,那后面的相同 hash
的键值都需要被重新排位。
HashMap解析 HashMap
的面试题如此之多,网络上的文章也很多时候我们只看一半,就我目前来说,就知道达到一定阈值的时候,Hash散列表
会被转换成 红黑树
,但是怎么转的,我居然是简单的认为将整个 Hash表
都进行树化,目前看来,这是错误的看法哈 ,其实是 Hash碰撞
的时候,当这个碰撞位的链表达到阈值的时候,只有 这个链表被转化为 红黑树
。 也就是有时候,你的 HashMap
结构是这样的:
不得不说,jdk
开发者的脑袋真的不是 人做的
orz。
默认容量和平衡因子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class HashMap <K,V> extends AbstractMap <K,V> implements Map <K,V>, Cloneable, Serializable { static final float DEFAULT_LOAD_FACTOR = 0.75f ; static final int DEFAULT_INITIAL_CAPACITY = 1 << 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); } public HashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap () { this .loadFactor = DEFAULT_LOAD_FACTOR; } 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 使用的时候:Maps.newHashMapWithExpectedSize(list.size()); public static <K, V> HashMap<K, V> newHashMapWithExpectedSize (int expectedSize) { return new HashMap <>(capacity(expectedSize)); } static int capacity (int expectedSize) { if (expectedSize < 3 ) { checkNonnegative(expectedSize, "expectedSize" ); return expectedSize + 1 ; } if (expectedSize < Ints.MAX_POWER_OF_TWO) { return (int ) ((float ) expectedSize / 0.75F + 1.0F ); } return Integer.MAX_VALUE; }
HashMap拉链法 那么我们要实现 Hash冲突
,也不是很难,首先我们准备一个类,然后重写 hashCode()
方法总是让他返回一个常亮即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class ABean { private String s; public ABean (String s) { this .s = s; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null getClass () != o.getClass()) return false ; ABean aBean = (ABean) o; return Objects.equals(s, aBean.s); } @Override public int hashCode () { return 1 ; } public static void main (String[] args) { Map<ABean, Integer> map = new HashMap <>(); for (int i = 0 ; i < 16 ; i++) { map.put(new ABean (i + "" ), i); } } }
接下来看看 put
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class HashMap <K,V> extends AbstractMap <K,V> implements Map <K,V>, Cloneable, Serializable { public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); } 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; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key (key != null && key.equals(k)))) e = p; 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 ) { p.next = newNode(hash, key, value, null ); if (binCount >= TREEIFY_THRESHOLD - 1 ) treeifyBin(tab, hash); break ; } if (e.hash == hash && ((k = e.key) == key (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent oldValue == null ) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null ; } }
HashMap树化 在上面的 putVal
方法中有个很重要的分支,就是 if (binCount >= TREEIFY_THRESHOLD - 1)
这个分支,他表示当 链表
的长度大于 8
的时候,进入树化,不过表面说是树化,其实…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 final void treeifyBin (Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1 ) & hash]) != null ) { TreeNode<K,V> hd = null , tl = null ; do { TreeNode<K,V> p = replacementTreeNode(e, null ); if (tl == null ) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null ); if ((tab[index] = hd) != null ) hd.treeify(tab); } } TreeNode<K,V> replacementTreeNode (Node<K,V> p, Node<K,V> next) { return new TreeNode <>(p.hash, p.key, p.value, next); }
这个树化函数就都封装在 HashMap$TreeNode
的内部类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 static final class TreeNode <K,V> extends LinkedHashMap .Entry<K,V> { TreeNode<K,V> parent; TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super (hash, key, val, next); } final void treeify (Node<K,V>[] tab) { TreeNode<K,V> root = null ; for (TreeNode<K,V> x = this , next; x != null ; x = next) { next = (TreeNode<K,V>)x.next; x.left = x.right = null ; if (root == null ) { x.parent = null ; x.red = false ; root = x; } else { K k = x.key; int h = x.hash; Class<?> kc = null ; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1 ; else if (ph < h) dir = 1 ; else if ((kc == null && (kc = comparableClassFor(k)) == null ) (dir = compareComparables(kc, k, pk)) == 0 ) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0 ) ? p.left : p.right) == null ) { x.parent = xp; if (dir <= 0 ) xp.left = x; else xp.right = x; root = balanceInsertion(root, x); break ; } } } } moveRootToFront(tab, root); } static int tieBreakOrder (Object a, Object b) { int d; if (a == null b == null (d = a.getClass().getName(). compareTo(b.getClass().getName())) == 0 ) d = (System.identityHashCode(a) <= System.identityHashCode(b) ? -1 : 1 ); return d; } 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; } else if (!xp.red (xpp = xp.parent) == null ) return root; if (xp == (xppl = xpp.left)) { if ((xppr = xpp.right) != null && xppr.red) { xppr.red = false ; xp.red = false ; xpp.red = true ; x = xpp; } else { if (x == xp.right) { root = rotateLeft(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null ) { xp.red = false ; if (xpp != null ) { xpp.red = true ; root = rotateRight(root, xpp); } } } } else { if (xppl != null && xppl.red) { xppl.red = false ; xp.red = false ; xpp.red = true ; x = xpp; } else { if (x == xp.left) { root = rotateRight(root, x = xp); xpp = (xp = x.parent) == null ? null : xp.parent; } if (xp != null ) { xp.red = false ; if (xpp != null ) { xpp.red = true ; root = rotateLeft(root, xpp); } } } } } } }
那这个情况的搜索或者是旋转跟上面简单的红黑树示例差不多,我也就不继续画图了。那我们现在要跳出去,也就是看看 moveRootToFront(tab, root);
做了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static <K,V> void moveRootToFront (Node<K,V>[] tab, TreeNode<K,V> root) { int n; if (root != null && tab != null && (n = tab.length) > 0 ) { int index = (n - 1 ) & root.hash; TreeNode<K,V> first = (TreeNode<K,V>)tab[index]; if (root != first) { Node<K,V> rn; tab[index] = root; TreeNode<K,V> rp = root.prev; if ((rn = root.next) != null ) ((TreeNode<K,V>)rn).prev = rp; if (rp != null ) rp.next = rn; if (first != null ) first.prev = root; root.next = first; root.prev = null ; } assert checkInvariants (root) ; } }
HashMap扩容 HashMap
在扩容的时候,jdk7
和 jdk8
使用的方式是不一样的,jdk7
要好理解一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 void addEntry (int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0 ; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void resize (int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return ; } Entry[] newTable = new Entry [newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int )Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1 ); } void transfer (Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while (null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
那我们再看看 JDK8
的版本实现,Emm,orz
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 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 ; } else if (oldThr > 0 ) newCap = oldThr; else { 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 ) { for (int j = 0 ; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null ) { oldTab[j] = 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); else { 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; }