您当前的位置:网站首页>understand,平安福-冻龄神颜+影后收割机,女生养成日记

understand,平安福-冻龄神颜+影后收割机,女生养成日记

2019-07-14 07:40:24 投稿作者:admin 围观人数:329 评论人数:0次

1、死磕 Java调集之ArrayList源码剖析

2、死磕 java调集之CopyOnWriteArrayList源码剖析

简介

HashMap选用key/value存储结构,每个key对应仅有的value,查询和修正的速度都很快,能到达O(1)的均匀时刻杂乱度。它对错线程安全的,且不确保元素存储的次序。

承继系统

HashMap完结了Cloneable,能够被克隆。

HashMap完结了Serializable,能够被序列化。

HashMap承继自AbstractMap,完结了Map接口,具有Map的一切功用。

存储结构

在Java中,HashMap的完结选用了(数组 + 链表 + 红黑树)的杂乱结构,数组的一个元素又称作桶。

在增加元素时,会依据hash值算出元素在数组中的方位,假设该方位没有元素,则直接把元素放置在此处,假设该方位有元素了,则把元素以链表的办法放置在链表的尾部。

当一个链表的元素个数到达必定的数量(且数组的长度到达必定的长度)后,则把链表转化为红黑树,然后进步功率。

数组的查询功率为O(1),链表的查询功率是O(k),红黑树的查询功率是O(log k),k为桶中的元素个数,所以当元素数量十分多的时分,转化为红黑树能极大地进步功率。

源码解析

特点

  1. /**

  2. * 默许的初始容量为16

  3. */

  4. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;


  5. /**

  6. * 最大的容量为2的30次方

  7. */

  8. static final int MAXIMUM_CAPACITY = 1 << 30;


  9. /**

  10. * 默许的装载因子

  11. */

  12. static final float DEFAULT_LOAD_FACTOR = 0.75f;


  13. /**

  14. * 当一个桶中的元素个数大于等于8时进行树化

  15. */

  16. static final int TREEIFY_THRESHOLD = 8;


  17. /**

  18. * 当一个桶中的元素个数小于等于6时把树转化为链表

  19. */

  20. static final int UNTREEIFY_THRESHOLD = 6;


  21. /**

  22. * 当桶的个数到达64的时分才进行树化

  23. */

  24. static final int MIN_TREEIFY_C单反相机APACITY = 64;


  25. /**

  26. * 数组,又叫作桶(bucket)

  27. */

  28. transient Node table;


  29. /**

  30. * 作为entrySet的缓存

  31. */

  32. transient Set> entrySet;


  33. /**

  34. * 元素的数量

  35. */

  36. transient int size;


  37. /**

  38. * understand,平安福-冻龄神颜+影后收割机,女生养成日记修正次数,用于在迭代的时分履行快速失利战略

  39. */

  40. transient int modCount;


  41. /**

  42. * 当桶的运用数量到达多少时进行扩容,threshold = capacity * loadFactor

  43. */

  44. int threshold;


  45. /**

  46. * 装载因子

  47. */

  48. final float loadFactor;

(1)容量

容量为数组的长度,亦即桶的个数,默许为16,最大为2的30次方,当容量到达64时才能够树化。

(2)装载因子

装载因子用来核算容量到达多少时才进行扩容,默许装载因子为0.75。

(3)树化

树化,当容量到达64且链表的长度到达8时进行树化,当链表的长度小于6时反树化。

Node内部类

Node是一个典型的单链表节点,其间,hash用来存储key核算得来的hash值。

  1. static class Node implements Map.Entry {

  2. final int hash;

  3. final K key;

  4. V value;

  5. Node next;

  6. }

TreeNode内部类

这是一个奇特的类,它承继自LinkedHashMap中的Entry类,关于LInkedHashMap.Entry这个类咱们后边再讲。

TreeNode是一个典型的树型节点,其间,prev是链表中的节点,用于在删去元素的时分能够快速找到它的前置节点。

  1. // 坐落HashMap中

  2. static final class TreeNode extends LinkedHashMap.Entry {

  3. TreeNode parent; // red-black tree links

  4. TreeNode left;

  5. TreeNode right;

  6. TreeNode prev; // needed to unlink next upon deletion

  7. boolean red;

  8. }


  9. // 坐落LinkedHashMap中,典型的双向链表节点

  10. static class Entry extends HashMap.Node {

  11. Entry before, after;

  12. Entry(int hash, K key, V value, Node next) {

  13. super(hash, key, value, next);

  14. }

  15. }

HashMap结构办法

空参结构办法,悉数运用默许值。

  1. public HashMap {

  2. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted

  3. }

HashMap(int initialCapacity)结构办法

调用HashMap(int initialCapacity, float loadFactor)结构办法,传入默许装载因子。

  1. public HashMap(int initialCapacity) {

  2. this(initialCapacity, DEFAULT_LOAD_FACTOR);

  3. }

HashMap(int initialCapacity)结构办法

判别传入的初始容量和装载因子是否合法,并核算扩容门槛,扩容门槛为传入的初始容量往上取最近的2的n次方。

  1. public HashMap(int initialCapacity, float loadFactor) {

  2. // 检查传入的初始容量是否合法

  3. if (initialCapacity < 0)

  4. throw new IllegalArgumentException("Illegal initial capacity: " +

  5. initialCapacity);

  6. if (initialCapacity > MAXIMUM_CAPACITY)

  7. initialCapacity = MAXIMUM_CAPACITY;

  8. // 检查装载因子是否合法

  9. if (loadFactor <= 0 || Float.isNaN(loadFactor))

  10. throw new IllegalArgumentException("Illegal load factor: " +

  11. loadFactor);

  12. this.loadFactor = loadFactor;

  13. // 核算扩容门槛

  14. this.threshold上一任攻略 = tableSizeFor(initialCapacity);

  15. }


  16. static final int tableSizeFor(int cap) {

  17. // 扩容门槛为传入的初始容量往上取最近的2的n次方

  18. int n = cap - 1;

  19. n |= n >>> 1;

  20. n |= n >>> 2;

  21. n |= n >>> 4;

  22. n |= n >>> 8;

  23. n |= n >>> 16;

  24. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

  25. }

put(K key, V value)办法

增加元素的进口。

  1. public V put(K key, V value) {

  2. // 调用hash(key)核算出key的hash值

  3. return putVal(hash(key), key, value, false, true);

  4. }


  5. static final int hash(Object key) {

  6. int h;

  7. // 假设key为,则hash值为0,不然调用key的hashCode办法

  8. // 并让高16位与整个ha胸痛是怎么回事sh异或,这样做是为了使核算出的hash更涣散

  9. return (key == ) ? 0 : (h = key.hashCode) ^ (h >>> 16);

  10. }


  11. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

  12. boolean evict) {

  13. Node tab;

  14. Node p;

  15. int n, i;

  16. // 假设桶的数量为0,则初始化

  17. if ((tab = table) == || (n = tab.length) == 0)

  18. // 调用resize初始化

  19. n = (tab = resize).length;

  20. // (n - 1) & hash 核算元素在哪个桶中

  21. // 假设这个桶中还没有元素,则把这个元素放在桶中的榜首个方位

  22. if ((p = tab[i = (n - 1) & hash]) == )

  23. // 新建一个节点放在桶中

  24. tab[i] = newNode(hash, key, value, );

  25. else {

  26. // 假设桶中已经有元素存在了

  27. Node e;

  28. K k;

  29. // 假设桶中榜首个元素的key与待刺进元素的key相同,保存到e中用于后续修正value值

  30. if (p.hash == hash &&

  31. ((k = p.key) == key || (key != && key.equals(k))))

  32. e = p;

  33. else if (p instanceof TreeNode)

  34. // 假设榜首个元素是树节点,则调用树节点的putTreeVal刺进元素

  35. e = ((TreeNode) p).putTreeVal(this, tab, hash, key, value);

  36. else {

  37. // 遍历这个桶对应的链表,binCount用于存储链表中元素的个数

  38. for (int binCount = 0; ; ++binCount) {

  39. // 假设链表遍历完了都没有找到相同key的元素,阐明该key对应的元素不存在,则在链表最终刺进一个新节点

  40. if ((e = p.next) == ) {

  41. p.next = newNode(hash, key, value, );

  42. // 假设刺进新节点后链表长度大于8,则判别是否需求树化,由于榜首个元素没有加到binCount中,所以这儿-1

  43. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

  44. treeifyBin(tab, hash);

  45. break;

  46. }

  47. // 假设待刺进的key在链表中找到了,则退出循环

  48. if (e.hash == hash &&

  49. ((k = e.key) == key || (key != && key.equals(k))))

  50. break;

  51. p = e;

  52. }

  53. }

  54. // 假设找到了对应key的元素

  55. if (e != ) { // existing mapping for key

  56. // 记录下旧值

  57. V oldValue = e.value;

  58. // 判别是否需求替换旧值

  59. if (!onlyIfAbsent || oldValue == )

  60. // 替换旧值为新值

  61. e.value = value;

  62. // 在节点被拜访后做点什么事,在LinkedHashMap中用到

  63. afterNodeAccess(e);

  64. // 回来旧值

  65. return oldValue;

  66. }

  67. }

  68. // 到这儿了阐明没有找到元素

  69. // 修正次数加1

  70. ++modCount;

  71. // 元素数量加1,判别是否需求扩容

  72. if (++size > threshold)

  73. // 扩容

  74. resize;

  75. // 在节点刺进后做点什么事,在LinkedHashMap中用到

  76. afterNodeInsertion(evict);

  77. // 没找到元素回来

  78. return ;

  79. }

(1)核算key的hash值;

(2)假设桶(数组)数量为0,则初始化桶;

(3)假设key地点的桶没有元素,则直接刺进;

(4)假设key地点的桶中的第understand,平安福-冻龄神颜+影后收割机,女生养成日记一个元素的key与待刺进的key相同,阐明找到了元素,转后续流程(9)处理;

(5)假设榜首个元素是树节点,则调用树节点的putTreeVal寻觅元素或刺进树节点;

(6)假设不是以上三种状况,则遍历桶对应的链表查找key是否存在于链表中;

(7)假设找到了对应key的元素,则转后续流程(9)处理;

(8)假设没找到对应key的元素,则在链表最终刺进一个新节点并判别是否需求树化;

(9)假设找到了对应key的元素,则判别是否需求替换旧值,并直接回来旧值;

(10)假设刺进了元素,则数量加1并判别是否需求扩容;

resize办法

扩容办法。

  1. final Node resize {

  2. // 旧数组

  3. Node oldTab = table;

  4. // 旧容量

  5. int oldCap = (oldTab == ) ? 0 : oldTab.length;

  6. // 旧扩容门槛

  7. int oldThr = threshold;

  8. int newCap, newThr = 0;

  9. if (oldCap > 0) {

  10. if (oldCap >= MAXIMUM_CAPACITY) {

  11. // 假设旧容量到达了最大容量,则不再进行扩容

  12. threshold = Integer.MAX_VALUE;

  13. return oldTab;

  14. } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

  15. oldC搬迁ap >= DEFAULT_INITIAL_CAPACITY)

  16. // 假设旧容量的两倍小于最大容量而且旧容量大于默许初始容量(16),则容量扩大为两部,扩容门槛也扩大为两倍

  17. newThr = oldThr << 1; // double threshold

  18. } else if (oldThr > 0) // initial capacity was placed in threshold

  19. // 运用非默许结构办法创立的map,榜首次刺进元素会走到这儿

  20. // 假设旧容量为0且旧扩容门槛大于0,则把新容量赋值为旧门槛

  21. newCap = oldThr;

  22. else { // zero initial threshold signifies using defaults

  23. // 调用默许结构办法创立的map,榜首次刺进元素会走到这儿

  24. // 假设旧容量旧扩容门槛都是0,阐明还未初始化过,则初始化容量为默许容量,扩容门槛为默许容量*默许装载因子

  25. newCapunderstand,平安福-冻龄神颜+影后收割机,女生养成日记 = DEFAULT_INITIAL_CAPACITY;

  26. newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

  27. }

  28. if (newThr == 0) {

  29. // 假设新扩容门槛为0,则核算为容量*装载因子,但不能超越最大容量

  30. floatdetail ft = (float) newCap * loadFactor白色巨塔;

  31. newThrmpv销量排行榜 = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?

  32. (int) ft : Integer.MAX_VALUE);

  33. }

  34. // 赋值扩容门槛为新门槛

  35. threshold = newThr;

  36. // 新建一个新容量的数组

  37. @SuppressWarnings({"rawtypes", "unchecked"})

  38. Node newTab = (Node[]) new Node[newCap];

  39. // 把桶赋值为新数组

  40. table = newTab;

  41. // 假设旧数组不为空,则搬移元素

  42. if (oldTab != ) {

  43. // 遍历旧数组

  44. for (int j = 0; j < oldCap; ++j) {

  45. Node e;

  46. // 假设桶中榜首个元素不为空,赋值给e

  47. if ((e = oldTab[j]) != ) {

  48. // 清空旧桶,便于GC收回

  49. oldTab[j] = ;

  50. // 假设这个桶中只要一个元素,则核算它在新桶中的方位并把它搬移到新桶中

  51. // 由于每次都扩容两倍,所以这儿的榜首个元素搬移到新桶的时分新桶必定还没有元素

  52. if (e.next == )

  53. newTab[e.hash & (newCap - 1)] = e;

  54. else if (e instanceof TreeNode)

  55. // 假设榜首个元素是树节点,则把这颗树打散成两颗树刺进到新桶中去

  56. ((TreeNode) e).split(this, newTab, j, oldCap);

  57. else { // preserve order

  58. // 假设这个链表不止一个元素且不是一颗树

  59. // 则分解成两个链表刺进到新的桶中去

  60. // 比方,假设本来容量为4,3、7、11、15这四个元素都在三号桶中

  61. // 现在扩容到8,则3和11仍是在三号桶,7和15要搬移到七号桶中去

  62. // 也便是分解成了两个链表

  63. Node loHead = understand,平安福-冻龄神颜+影后收割机,女生养成日记, loTail = ;

  64. Node hiHead = , hiTail = ;

  65. Node next;

  66. do {

  67. next = understand,平安福-冻龄神颜+影后收割机,女生养成日记e.next;

  68. // (e.hash & oldCap) == 0的元素放在低位链表中

  69. // 比方,3 & 4 == 0

  70. if ((e.hash & oldCap) == 0) {

  71. if (loTail == )

  72. loHead = e;

  73. else

  74. loTail.next = e;

  75. loTail = e;

  76. } else {

  77. // (e.hash & oldCap) != 0的元素放在高位链表中

  78. // 比方,7 & 4 != 0

  79. if (hiTail == )

  80. hiHead = e;

  81. else

  82. hiTail.next = e;

  83. hiTail = e;

  84. }

  85. } while ((e = next) != );

  86. // 遍历完结分解成两个链表了

  87. // 低位链表在新桶中的方位与understand,平安福-冻龄神颜+影后收割机,女生养成日记旧桶相同(即3和11还在三号桶中)

  88. if (loTail != ) {

  89. loTail.next = ;

  90. newTab[j] = loHead;

  91. }

  92. // 高位链表在新桶中的方位正好是本来的方位加上旧容量(即7和15搬移到七号桶了)

  93. if (hiTail != ) {

  94. hiTail.next = ;

  95. newTab[j + oldCap] = hiHead;

  96. }

  97. }

  98. }

  99. }

  100. }

  101. return newTab;

  102. }

(1)假设运用是默许结构办法,则榜首次刺进元素时初始化为默许值,容量为16,扩容门槛为12;

(2)假设运用的对错默许结构办法,则榜首次刺进元素时初始化容量等于扩容门槛,扩容门槛在结构办法里等于传入容量向上最近的2的n次方;

(3)假设旧容量大于0,则新容量等于旧容量的2倍,但不超越最大容量2的30次方,新扩容门槛为旧扩容门槛的2倍;

(4)创立一个新容量的桶;

(5)搬移元素,原链表分解成两个链表,低位链表存储在本来桶的方位,高位链表搬移到本来桶的方位加旧容量的方位;

TreeNode.putTreeVal(...)办法

刺进元素到红黑树中的办法。

  1. final TreeNode putTreeVal(HashMap map, Node[] tab,

  2. int h, K k, V v) {

  3. Class

  4. // 符号是否找到这个key的节点

  5. boolean searched = false;

  6. // 找到树的根节点

  7. TreeNode root = (parent != ) ? root : this;

  8. // 从树的根节点开端遍历

  9. for (TreeNode p = root; ; ) {

  10. // dir=direction,符号是在左面仍是右边

  11. // ph=p.hash,当时节点的hash值

  12. int dir, ph;

  13. // pk=p.key,当时节点的key值

  14. K pk;

  15. if ((ph = p.hash) > h) {

  16. // 当时hash比方针hash大,阐明在左面

  17. dir = -1;

  18. }

  19. else if (ph < h)

  20. // 当时hash比方针hash小,阐明在右边

  21. dir = 1;

  22. else if ((pk = p.key) == k || (k != && k.equals(pk)))

  23. // 两者hash相同且key持平,阐明找到了节点,直接回来该节点

  24. // 回到putVal中判别是否需求修正其value值

  25. return p;

  26. else if ((kc == &&

  27. // 假设k是Comparable的子类则回来其实在的类,不然回来

  28. (kc = comparableClassFor(k)) == ) ||

  29. // 假设k和pk不是相同的类型则回来0,不然回来两者比较的成果

  30. (油炸花生米dir = compareComparables(kc, k, pk)) == 0) {

  31. // 这个条件表明两者hash相同可是其间一个不是Comparable类型或许两者类型不同

  32. // 比方key是Object类型,这时能够传String也能够传Integer,两者has枫桥经历h值或许相同

  33. // 在红黑树中把相同hash值的元素存储在同一颗子树,这儿相当于找到了这颗子树的极点

  34. // 从这个极点别离遍历其左右子树去寻觅有没有跟待刺进的key相同的元素

  35. if (!searched) {

  36. TreeNode q, ch;

  37. searched = true;

  38. // 遍历左右子树找到了直接回来

  39. if (((ch 戊= p.left) != &&

  40. (q = ch.find(h, k, kc)) != ) ||

  41. ((ch = p.right) != &&

  42. (q = c第八套广播体操h.find(h, k, kc)) != ))

  43. return q;

  44. }

  45. // 假设两者类型相同,再依据它们的内存地址核算hash值进行比较

  46. dir = tieBreakOrder(k, pk);

  47. }


  48. TreeNode xp = p;

  49. if ((p = (dir <= 0) ? p.left : p.right) == ) {

  50. // 假设最终的确没找到对应key的元素,则新建一个节点

  51. Node xpn = xp.next;

  52. TreeNode x = map.newTreeNode(h, k, v, xpn);

  53. if (dir <= 0)

  54. xp.left = x;

  55. else

  56. xp.right = x;

  57. xp.next = x;

  58. x.parent = x.prev = xp;

  59. if (xpn != )

  60. ((TreeNode) xpn).prev = x;

  61. // 刺进树节点后平衡

  62. // 把root节点移动到链表的榜首个节点

  63. moveRootToFront(tab, balanceInsertion(root, x));

  64. return ;

  65. }

  66. }

  67. }

(1)寻觅根节点;

(2)从根节点开端查找;

(3)比较hash值及key值,假设都相同,直接回来,在putVal办法中决议是否要替换value值;

(4)依据hash值及key值确定在树的左子树仍是右子树查找,找到了直接回来;

(5)假设最终没有找到则在树的相应方位刺进元素,并做平衡;

treeifyBin办法

假设刺进元素后链表的长度大于等于8则判别是否需求树化。

  1. final void treeifyBin(Node[] tab, int hash) {

  2. int n, index;

  3. Node e;

  4. if (tab == || (n = tab.length) < MIN_TREEIFY_CAPACITY)

  5. // 假设桶数量小于64,直接扩容而不必树化

  6. // 由于扩容之后,链表会分解成两个链表,到达削减元素的效果

  7. // 当然也不必定,比方容量为4,里边存的满是除以4余数等于3的元素

  8. // 这样即便扩容也无法削减链表的长度

  9. resize;

  10. else if ((e = tab[index = (n - 1) & hash]) != ) {

  11. TreeNode hd = , tl = ;

  12. // 把一切节点换成树节点

  13. do {

  14. TreeNode p = replacementTreeNode(e, );

  15. if (tl == )

  16. hd = p;

  17. else {

  18. p.prev = tl;

  19. tl.next = p;

  20. }

  21. tl = p;

  22. } while ((e = e.next) != );

  23. // 假设进入过上面的循环,则从头节点开端树化

  24. if ((tab[index] = hd) != )

  25. hd.treeify(tab);

  26. }

  27. }

TreeNode.treeify办法

真实树化的办法。

  1. final void treeify(Node[] tab) {

  2. TreeNode root = ;

  3. for (TreeNode x = this, next; x != ; x = next) {

  4. next = (TreeNode) x.next;

  5. x.left = x.right = ;

  6. // 榜首个元素作为根节点且为黑节点,其它元素顺次刺进到树中再做平衡

  7. if (root == ) {

  8. x.parent = ;

  9. x.red = false;

  10. root = x;

  11. } else {

  12. K k = x.key;

  13. int h = x.hash;

  14. Class

  15. // 从根节点查找元素刺进的方位

  16. for (TreeNode p = root; ; ) {

  17. int dir, ph;

  18. K pk = p.key;

  19. if ((ph = p.hash) > h)

  20. dir = -1;

  21. else if (ph < h)

  22. dir = 1;

  23. else if ((kc == &&

  24. (kc = comparableClassFor(k)) == ) ||

  25. (dir = compareComparables(kc, k, pk)) == 0)

  26. dir = tieBreakOrder(k, pk);


  27. // 假设最终没找到元素,则刺进

  28. TreeNode xp = p;

  29. if ((p = (dir <= 0) ? p.left : p.right) == ) {

  30. x.parent = xp;

  31. if (dir <= 0)

  32. xp.left = x;

  33. else

  34. xp.right = x;

  35. // 刺进后平衡,默许刺进的是红节点,在balanceInsertion办法里

  36. root = balanceInsertion(root, x);

  37. break;

  38. }

  39. }

  40. }

  41. }

  42. // 把根节点移动到链表的头节点,由于经过平衡之后本来的榜首个元素不必定是根节点了

  43. moveRootToFront(tab, root);

  44. }

(1)从链表的榜首个元素开端遍历;

(2)将榜首个元素作为根节点;

(3)其它元素顺次刺进到红黑树中,再做平衡;裤子尺码

(4)将根节点移到链表榜首元素的方位(由于平衡的时分根节点会改动);

get(Object key)办法

  1. public V get(Object key) {

  2. Node e;

  3. return (e = getNode(hash(key), key)) == ? : e.value;

  4. }


  5. final Node getNode(int hash, Object key) {

  6. Node tab;

  7. Node first, e;

  8. int n;

  9. K k;

  10. // 假设桶的数量大于0而且待查找的key地点的桶的榜首个元素不为空

  11. if ((tab = table) != && (n = tab.length) > 0 &&

  12. (first = tab[(n - 1) & hash]) != ) {

  13. // 检查榜首个元素是不是要查的元素,假设是直接回来

  14. if (first.hash == hash && // always check first node

  15. ((k = first.key) == key || (key != && key.equals(k))))

  16. return first;

  17. if ((e = first.next) != ) {

  18. // 假设榜首个元素是树节点,则按树的办法查找

  19. if (first instanceof TreeNode)

  20. return ((TreeNode) first).getTreeNode(hash, key);


  21. // 不然就遍历整个链表查找该元素

  22. do {

  23. if (e.hash == hash &&

  24. ((k = e.key) == key || (key != && key.equals(k))))

  25. return e;

  26. } while ((e = e.next) != );

  27. }

  28. }

  29. return ;

  30. }

(1)核算key的hash值;

(2)找到key地点的桶及其榜首个元素;

(3)假设榜首个元素的key等于待查找的key,直接回来;

(4)假设榜首个元素是树节点就按树的办法来查找,不然按链表办法查找;

TreeNode.getTreeNode(int h, Object k)办法

  1. final TreeNode getTreeNode(int h, Object k) {

  2. // 从树的根节点开端查找

  3. return ((parent != ) ? root : this).find(h, k, );

  4. }


  5. final TreeNode find(int h, Object k, Class

  6. TreeNode p = this;

  7. do {

  8. int ph, dir;

  9. K pk;

  10. TreeNode pl = p.left, pr = p.right, q;

  11. if ((ph = p.hash) > h)

  12. // 左子树

  13. p = pl;

  14. else if (ph < h)

  15. // 右子树

  16. p = pr;

  17. else if ((pk = p.key) == k || (k != && k.equals(pk)))

  18. // 找到了直接回来

  19. return p;

  20. else if (pl == )

  21. // hash相同但key不同,左子树为空查右子树

  22. p = pr;

  23. else if (pr == 游戏大厅)

  24. // 右子树为空查左子树

  25. p = pl;

  26. else if ((kc != ||

  27. (kc = comparableClassFor(k)) != ) &&

  28. (dir = compareComparables(kc, k, pk)) != 0)

  29. // 经过compare办法比较key值的巨细决议运用左子树仍是右子树

  30. p = (dir < 0) ? pl : pr;

  31. else if ((q = pr.find(h, k, kc)) != )

  32. // 假设以上条件都不经过,则测验在右子树查找

  33. return q;

  34. else

  35. // 都没找到就在左子树查找

  36. p = pl;

  37. } while (p != 死神来了2);

  38. return ;

  39. }

经典二叉查找树的查找进程,先依据hash值比较,再依据ke兴盛世界9xy值比较决议是查左子树仍是右子树。

remove(Object key)办法

  1. public V remove(Object key) {

  2. Node e;

  3. return (e = removeNode(hash(key), key, , false, true)) == ?

  4. : e.value;

  5. }


  6. final Node removeNode(int hash, Object key, Object value,

  7. boolean matchValue, boolean movable) {

  8. Node tab;

  9. Node p;

  10. int n, index;

  11. // 假设桶的数量大于0且待删去的元素地点的桶的榜首个元素不为空

  12. if ((tab = table) != && (n = tab.length) > 0 &&

  13. (p = tab[index = (n - 1) & hash]) != ) {

  14. Node node = , e;

  15. K k;

  16. V v;

  17. if (p.hash == hash &&

  18. ((k = p.key) == key || (key != && key.equals(k))))

  19. // 假设榜首个元素正好便是要找的元素,赋值给node变量后续删去运用

  20. node = p;

  21. else if ((e = p.next) != ) {

  22. if (p instanceof TreeNode)

  23. // 假设榜首个元素是树节点,则以树的办法查找节点

  24. node = ((TreeNode) p).getTreeNode(hash, key);

  25. else {

  26. // 不然遍历整个链表查找元素

  27. do {

  28. if (e.hash == hash &&

  29. ((k = e.key) == key ||

  30. (key != && key.equals(k)))) {

  31. node = e;

  32. break;

  33. }

  34. p = e;

  35. } while ((e = e.next) != );

  36. }

  37. }

  38. // 假设找到了元素,则看参昏嫁数是否需求匹配value值,understand,平安福-冻龄神颜+影后收割机,女生养成日记假设不需求匹配直接删去,假设需求匹配则看value值是否与传入的value持平

  39. if (node != && (!matchValue || (v = node.value) == value ||

  40. (value != && value.equals(v)))) {

  41. if廖雪峰 (node instanceof TreeNode)

  42. // 假设是树节点,调用树的删去办法(以node调用的,是删去自己)

  43. ((TreeNode) node).removeTreeNode(this, tab, movable);

  44. else if (node == p)

  45. // 假设待删去的元素是榜首个元素,则把第二个元素移到榜首的方位

  46. tab[index] = node.next;

  47. else

  48. // 不然删去node节点

  49. p.next = node.next;

  50. ++modCount;

  51. --size;

  52. // 删去节点后置处理

  53. afterNodeRemoval(node);

  54. return node;

  55. }

  56. }

  57. return ;

  58. }

(1)先查找元素地点的节点;

(2)假设找到的节点是树节点,则按树的移除节点处理;

(3)假设找到的节点是桶中的榜首个节点,则把第二个节点移到榜首的方位;

(4)不然按链表删去节点处理;

(5)修正size,调用移除节点后置处理等;

TreeNode.removeTreeNode(...)办法

  1. final void removeTreeNode(HashMap map, Node[] tab,

  2. boolean movable) {

  3. int n;

  4. // 假设桶的数量为0直接回来

  5. if (tab == || (n = tab.length) == 0)

  6. return;

  7. // 节点在桶中的索引

  8. int index = (n - 1) & hash;

  9. // 榜首个节点,根节点,根左子节点

  10. TreeNode first = (TreeNode) tab[index], root = first, rl;

  11. // 后继节点,前置节点

  12. TreeNode succ = (TreeNode) next, pred = prev;


  13. if (pred == )

  14. // 假设前置节点为空,阐明当时节点是根节点,则把后继节点赋值到榜首个节点的方位,相当于删去了当时节点

  15. tab[index] = first = succ;

  16. else

  17. // 不然把前置节点的下个节点设置为当时节点的后继节点,相当于删去了当时节点

  18. pred.next = succ;


  19. // 假设后继节点不为空,则让后继节点的前置节点指向当时节点的前置节点,相当于删去了当时节点

  20. if (succ != )

  21. succ.prev = pred;


  22. // 假设榜首个节点为空,阐明没有后继节点了,直接回来

  23. if (first == )

  24. return;


  25. // 假设根节点的父节点不为空,则从头查找父节点

  26. if (root.parent != )

  27. root = root.root;


  28. // 假设根节点为空,则需求反树化(将树转化为链表)

  29. // 假设需求移动节点且树的高度比较小,则需求反树化

  30. if (root ==

  31. || (movable

  32. && (root.right ==

  33. || (rl = root.left) ==

  34. || rl.left == ))) {

  35. tab[index] = first.untreeify(map); // too small

  36. return;

  37. }


  38. // 分割线,以上都是删去链表中的节点,下面才是直接删去红黑树的节点(由于TreeNode自身便是链表节点又是树节点)


  39. // 删去红黑树节点的大致进程是寻觅右子树中最小的节点放到删去节点的方位,然后做平衡,此处不过多注释

  40. TreeNode p = this, pl = left, pr = right, replacement;

  41. if (pl != && pr != ) {

  42. TreeNode s = pr, sl;

  43. while ((sl = s.left) != ) // find successor

  44. s = sl;

  45. boolean c = s.red;

  46. s.red = p.red;

  47. p.red = c; // swap colors

  48. TreeNode sr = s.right;

  49. TreeNode pp = p.parent;

  50. if (s == pr) { // p was s's direct parent

  51. p.parent = s;

  52. s.right = p;

  53. } else {

  54. TreeNode sp = s.parent;

  55. if ((p.parent = sp) != ) {

  56. if (s == sp.left)

  57. sp.left = p;

  58. else

  59. sp.right = p;

  60. }

  61. if ((s.right = pr) != )

  62. pr.parent = s;

  63. }

  64. p.left = ;

  65. if ((p.right = sr) != )

  66. sr.parent = p;

  67. if ((s.left = pl) != )

  68. pl.parent = s;

  69. if ((s.parent = pp) == )

  70. root = s;

  71. else if (p == pp.left)

  72. pp.left = s;

  73. else

  74. pp.right = s;

  75. if (sr != )

  76. replacement = sr;

  77. el最终的兵士se

  78. replacement = p;

  79. } else if (pl != )

  80. replacement = pl;

  81. else if (pr != )

  82. replacement = pr;

  83. else

  84. replacement = p;

  85. if (replacement != p) {

  86. TreeNode pp = replacement.parent = p.parent;

  87. if (pp == )

  88. root = replacement;

  89. else if (p == pp.left)

  90. pp.left = replacement;

  91. else

  92. pp.right = replacement;

  93. p.left = p.right = p.parent = ;

  94. }


  95. TreeNode r = p.red ? root : balanceDeletion(root, replacement);


  96. if (replacement == p) { // detach

  97. TreeNode pp =男同直播 p.parent;

  98. p.parent = ;

  99. if (pp != ) {

  100. if (p == pp.left)

  101. pp.left = ;

  102. else if (p == pp.right)

  103. pp.right = ;

  104. }

  105. }

  106. if (movable)

  107. moveRootToFront(tab, r);

  108. }

(1)TreeNode自身既是链表节点也是红黑树节点;

(2)先删去链表节点;

(3)再删去红黑树节点并做平衡;

总结

(1)HashMap是一种散列表,选用(数组 + 链表 + 红黑树)的存储结构;

(2)HashMap的默许初始容量为16(1<<4),默许装载因子为0.75f,容量总是2的n次方;

(3)HashMap扩容时每次容量变为本来的两倍;

(4)当桶的数量小于64时不会进行树化,只会扩容;

(5)当桶的数量大于64且单个桶中元素的数量大于8时,进行树化;

(6)当单个桶中元素数量小于6时,进行反树化;

(7)HashMap对错线程安全的容器;

(8)HashMap查找增加元素的时刻杂乱度都为O(1);

带具体注释的源码地址

https://gitee.com/alan-tang-tt/yuan/blob/master/%E6%AD%BB%E7%A3%95%20java%E9%9B%86%E5%90%88%E7%B3%BB%E5%88%97/code/HashMap.java

彩蛋

红黑树知多少?

红黑树具有以下5种性质:

(1)节点是赤色或黑色。

(2)根节点是黑色。

(3)每个叶节点(NIL节点,空节点)是黑色的。

(4)每个赤色节点的两个子节点都是黑色。(从每个叶子到根的一切途径上不能有两个接连的赤色节点)

(5)从任一节点到其每个叶子的一切途径都包括相同数目的黑色节点。

红黑树的时刻杂乱度为O(log n),与树的高度成正比。

红黑树每次的刺进、删去操作都需求做平衡,平衡时有或许会改动根节点的方位,色彩转化,左旋,右旋等。

重视我

大众号(zhisheng)里回复 面经、ES、Flink、 Spring、Java、Kafka、监控 等关键字能够检查更多关键字对应的文章!

引荐文章

1、《从0到1学习Flink》—— Apache Flink 介绍

2、《从0到1学习Flink》—— Mac 上建立 Flink 1.6.0 环境并构建运转简略程序入门

3、《从0到1学习Flink》—— Flink 装备文件详解

4、《从0到1学习Flink》—— Data Source 介绍

5、《从0到1学习Flink》—— 怎么自定义 Data Source ?

6、《从0到1学习Flink》—— Data Sink 介绍

7、《从0到1学习Flink》—— 怎么自定义 Data Sink ?

8、《从0到1学习Flink》—— Flink Data transformation(转化)

9、《从0到1学习Flink》—— 介绍 Flink 中的 Stream Windows

10、《从0到1学习Flink》—— Flink 中的几种 Time 详解

11、《从0到1学习Flink》—— Flink 读取 Kafka 数据写入到 ElasticSearch

12、《从0到1学习Flink》—— Flink 项目怎么运转?

13、《从0到1学习Flink》—— Flink 读取 Kafka 数据写入到 Kafka

14、《从0到1学习Flink》—— Flink JobManager 高可用性装备

15、《从0到1学习Flink》—— Flink parallelism 和 Slot 介绍

16、《从0到1学习Flink》—— Flink 读取 Kafka 数据批量写入到 MySQL

17、《从0到1学习Flink》—— Flink 读取 Kafka 数据写入到 RabbitMQ

18、《从0到1学习Flink》—— 你上传的 jar 包藏到哪里去了

19、大数据“重磅炸弹”——实时核算结构 Flink

20、《Flink 源码解析》—— 源码编译运转

21、为什么说流处理即未来?

在星球更新的源码解析文章有:

更多源码解析的文章和 Flink 材料请加常识星球!

喜爱这篇文章

就点下在看哦▼

the end
冻龄神颜+影后收割机,女生养成日记