• 手写HashMap 手撕哈希表


    下面编写的HashMap与JDK1.8源码的hHashMap来说:

    忽略了链表转化为红黑树的情况。

    1. public class HashMap implements Map{
    2. //是所有节点数量总大小
    3. private int size ;
    4. private static final boolean RED = false ;
    5. private static final boolean BLACK = true ;
    6. //table数组存储每一个插入的红黑树的根节点Node
    7. private Node[] table ;
    8. //table数组一开始默认给一个初始长度(哈希表数组最好长度为 2的n次幂)
    9. private static final int DEFAULT_CAPACITY = 1 << 4 ;
    10. public HashMap() {
    11. table = new Node[DEFAULT_CAPACITY] ;
    12. }
    13. @Override
    14. public int size() {
    15. return size;
    16. }
    17. @Override
    18. public boolean isEmpty() {
    19. return size == 0 ;
    20. }
    21. @Override
    22. public void clear() {
    23. if (size == 0) {
    24. return;//如果哈希表没有Node节点 那么直接返回
    25. }
    26. for(int i = 0 ; i
    27. table[i] = null ;
    28. }
    29. size = 0 ;
    30. }
    31. /**
    32. * 添加操作
    33. * @param key
    34. * @param value
    35. * @return 返回值为被覆盖的value值
    36. */
    37. @Override
    38. public V put(K key, V value) {
    39. resize() ;
    40. int index = index(key) ;
    41. //取出index 位置的红黑树根节点
    42. Node root = table[index] ;
    43. boolean searched = false ;
    44. if (root == null) {
    45. root = new Node(key,value, root.hash, null) ;
    46. table[index] = root ;
    47. size ++ ;
    48. afterPut(root);
    49. return null ;
    50. }
    51. //2.说明添加的不是树中第一个元素
    52. //2.1 确定根节点 root
    53. Node node = root ;
    54. //2.2 假设出待插入节点对应的父节点的值
    55. Node parent = null ;
    56. //2.3 记录最后一次比较器返回的值 来确定待插入节点插入到哪个方向
    57. int cmp = 0 ;
    58. K k1 = key ;
    59. int h1 = key == null ? 0 : key.hashCode() ;//找出传入的key对应的hash值
    60. //2.4 循环寻找待插入节点需要插入到的位置
    61. do {
    62. //记录cmp
    63. cmp = compare(node.key,key,h1,node.hash) ;
    64. //记录parent
    65. parent = node ;
    66. K k2 = node.key ;
    67. int h2 = node.hash ;
    68. Node result = null ;
    69. //和node方法思路基本一致 所以详细注释见node方法
    70. if (h1 > h2) {
    71. cmp = 1 ;
    72. } else if (h1 < h2) {
    73. cmp = -1 ;
    74. } else if (Objects.equals(k1,k2)) {//已经判断过内存地址相同的情况了
    75. cmp = 0 ;
    76. } else if (k1 != null && k2 != null
    77. && k1.getClass() == k2.getClass()
    78. && k1 instanceof Comparable
    79. && (cmp = ((Comparable)k1).compareTo(k2))!=0) {
    80. //排除compareTo方法返回结果为0的情况 因为为0也并不代表两个对象是相同的
    81. } else if (!searched) {//searched = false
    82. //先扫描红黑树 如果不存在待添加的key 那么再进行使用内存地址比较!
    83. if ((node.left != null && (result = node(node.left,k1))!=null)
    84. ||(node.right != null && (result = node(node.right,k1))!=null)) {
    85. //说明已经存在待添加的key,此时result为待添加的节点在红黑树中所找到的节点 。node为根节点
    86. //要新赋值node节点 重复利用之后的代码
    87. node = result ;
    88. cmp = 0 ;
    89. } else { //不存在这个key
    90. searched = true ;
    91. //说明不存在待添加节点对应的key值 那么只可以使用内存地址进行比较添加
    92. cmp = System.identityHashCode(k1) - System.identityHashCode(k2) ;
    93. }
    94. } else {
    95. //searched = true
    96. cmp = System.identityHashCode(k1) - System.identityHashCode(k2) ;
    97. }
    98. if (cmp > 0) {
    99. //说明已存在节点元素值大,那么插到左边
    100. node = node.right ;
    101. } else if (cmp < 0) {
    102. node = node.left ;
    103. } else {
    104. //cmp == 0 如果自定义比较器比较出相等时,我们这里的处理方式是覆盖值操作
    105. node.key = key ;
    106. V oldValue = node.value ;
    107. node.value = value ;
    108. return oldValue ;//返回的是被覆盖的value值
    109. }
    110. } while (node != null) ;
    111. //3.退出循环之后,根据记录的parent值 进行创建待插入节点对应的Node对象
    112. Node newNode = new Node<>(key,value,h1,parent) ;
    113. //4.根据记录的cmp值和parent值,进行确定把待插入节点插入到哪一个位置
    114. if (cmp > 0) {
    115. parent.left = newNode ;
    116. } else {
    117. //cmp < 0 的情况,cmp == 0 直接就返回了已经
    118. parent.right = newNode ;
    119. }
    120. size ++ ;
    121. //添加node节点后 进行的调整
    122. afterPut(node); ;
    123. return null ;
    124. }
    125. /**
    126. * 获取对应key的value
    127. * @param key 传入的key键值
    128. * @return 返回key对应的value值
    129. */
    130. @Override
    131. public V get(K key) {
    132. Node node = node(key) ;
    133. return node != null ? node.value : null ;
    134. }
    135. @Override
    136. public V remove(K key) {
    137. return remove(node(key));
    138. }
    139. @Override
    140. public boolean containsKey(K key) {
    141. return node(key) != null ;
    142. }
    143. /**
    144. * 对于value的值不可以直接查询到 需要遍历所有节点
    145. * @param value
    146. * @return
    147. */
    148. @Override
    149. public boolean containsValue(V value) {
    150. if (size == 0) {//如果没有节点 那么返回false
    151. return false ;
    152. }
    153. Queue> queue = new LinkedList<>() ;
    154. //这里的层序遍历和之前不同 需要遍历多个红黑树 所以使用for循环
    155. for (int i = 0 ;i< table.length ;i++) {
    156. if (table[i] == null) {
    157. return false ;
    158. }
    159. queue.offer(table[i]) ;
    160. while (!queue.isEmpty()) {
    161. Node node = queue.poll() ;
    162. if (Objects.equals(value,node.value)) {
    163. return true ;
    164. }
    165. if (node.left != null) {
    166. queue.offer(node.left) ;
    167. }
    168. if (node.left != null) {
    169. queue.offer(node.right) ;
    170. }
    171. }
    172. }
    173. return false;
    174. }
    175. @Override
    176. public void traversal(Visitor visitor) {
    177. if (size == 0 || visitor == null) {
    178. return;
    179. }
    180. Queue> queue = new LinkedList<>() ;
    181. for (int i = 0;i < table.length ;i++) {
    182. if (table[i] == null) {
    183. return;
    184. }
    185. queue.offer(table[i]) ;
    186. while (!queue.isEmpty()) {
    187. Node node = queue.poll() ;
    188. if (visitor.visit(node.key,node.value)) {
    189. return;
    190. }
    191. if (node.left != null) {
    192. queue.offer(node.left) ;
    193. }
    194. if (node.right != null) {
    195. queue.offer(node.right) ;
    196. }
    197. }
    198. }
    199. }
    200. private void resize() {
    201. //说明装填因子 <= 0.75 无需扩容 直接返回
    202. if (size / table.length <= DEFAULT_CAPACITY) {
    203. return;
    204. }
    205. // >0.75 扩容到原来的两倍
    206. Node []oldTable = table ;
    207. table = new Node[oldTable.length >> 1] ;
    208. Queue> queue = new LinkedList<>() ;
    209. //把原来table数组上的key-value键值对每一个节点都进行转移到新数组
    210. /**
    211. * 当扩容为原来容量的2倍时,节点的索引有2种情况:
    212. * 1.保持不变
    213. * 2.要么就是index = index + 旧容量
    214. */
    215. for (int i = 0 ; i< oldTable.length ;i++) {
    216. if (oldTable[i] == null) {
    217. return;
    218. }
    219. queue.offer(oldTable[i]) ;
    220. while (!queue.isEmpty()) {
    221. Node node = queue.poll() ;
    222. if (node.left != null) {
    223. queue.offer(node.left) ;
    224. }
    225. if (node.right != null) {
    226. queue.offer(node.right) ;
    227. }
    228. moveNode(node) ;//移动遍历到旧数组的每一个元素
    229. }
    230. }
    231. }
    232. private void moveNode(Node newNode) {
    233. //重置清空所有节点的父子关系以及左右关系
    234. newNode.parent = null ;
    235. newNode.left = null ;
    236. newNode.right = null ;
    237. int index = index(newNode.key) ;
    238. //取出index 位置的红黑树根节点
    239. Node root = table[index] ;
    240. if (root == null) {//说明该桶索引位置还没有节点插入
    241. root = newNode ;//所以直接插给root
    242. table[index] = root ;
    243. afterPut(root);
    244. return;
    245. }
    246. //2.说明添加的不是树中第一个元素
    247. //2.1 确定根节点 root
    248. Node node = root ;
    249. //2.2 假设出待插入节点对应的父节点的值
    250. Node parent = null ;
    251. //2.3 记录最后一次比较器返回的值 来确定待插入节点插入到哪个方向
    252. int cmp = 0 ;
    253. K k1 = newNode.key ;
    254. int h1 = newNode.hash ;//找出传入的key对应的hash值
    255. //2.4 循环寻找待插入节点需要插入到的位置
    256. do {
    257. //记录parent
    258. parent = node ;
    259. K k2 = node.key ;
    260. int h2 = node.hash ;
    261. Node result = null ;
    262. //和node方法思路基本一致 所以详细注释见node方法
    263. if (h1 > h2) {
    264. cmp = 1 ;
    265. } else if (h1 < h2) {
    266. cmp = -1 ;
    267. } else if (k1 != null && k2 != null
    268. && k1 instanceof Comparable
    269. && k1.getClass() == k2.getClass()
    270. && (cmp = ((Comparable) k1).compareTo(k2))!=0) {
    271. } else {
    272. //searched = true
    273. cmp = System.identityHashCode(k1) - System.identityHashCode(k2) ;
    274. }
    275. if (cmp > 0) {
    276. //说明已存在节点元素值大,那么插到左边
    277. node = node.right ;
    278. } else if (cmp < 0) {
    279. node = node.left ;
    280. }
    281. } while (node != null) ;
    282. //4.根据记录的cmp值和parent值,进行确定把待插入节点插入到哪一个位置
    283. if (cmp > 0) {
    284. parent.left = newNode ;
    285. } else {
    286. //cmp < 0 的情况,cmp == 0 直接就返回了已经
    287. parent.right = newNode ;
    288. }
    289. newNode.parent = parent ;
    290. //添加node节点后 进行的调整
    291. afterPut(node);
    292. }
    293. private V remove(Node node) {
    294. if (node == null) {
    295. return null;
    296. }
    297. V oldValue = node.value ;
    298. size -- ;
    299. //(1)删除的是度为2的节点
    300. if (node.hasTwoChildren()) {
    301. //找到该节点对应的前驱或后继节点
    302. Node s = succeed(node) ;//找到后继节点
    303. //使用后继节点的值进行覆盖度为2的节点的值
    304. node.key = s.key ;
    305. node.value = s.value ;
    306. //把待删除的节点进行赋值为前驱或后继节点,使用后面的逻辑进行删除前驱后继节点
    307. node = s ;
    308. }
    309. //必须搞出一个replacement进行记录删除的是根节点左子树还是右子树的节点
    310. Node replacement = node.left != null ? node.left : node.right ;
    311. int index = index(node.key) ;
    312. //(2)删除的是度为1的节点
    313. if (replacement != null) {
    314. //统一进行更新删除节点度为1的操作之后的parent指向值
    315. replacement.parent = node.parent ;
    316. //2.1 如果是度为1的节点并且是root根节点
    317. if (node.parent == null) {
    318. table[index] = replacement ;
    319. } else if (node == node.parent.left) {
    320. //2.2 如果是左子树上的度为1的节点
    321. node.parent.left = replacement ;
    322. } else if (node == node.parent.right) {
    323. //2.3 如果是右子树上的度为1的节点
    324. node.parent.right = replacement ;
    325. }
    326. //真正被删除的是度为1或2的前驱或后继节点
    327. afterRemove(node,replacement) ;
    328. } else if (node.parent == null) {// (3) 如果删除的是叶子节点并且为root根节点
    329. table[index] = null ;
    330. } else {//(4) 如果删除的是叶子节点但是不为root根节点
    331. if (node == node.parent.right) {
    332. //4.1 如果是左子树上的度为0的节点
    333. node.parent.right = null ;
    334. } else {//node = node.parent.left
    335. //4.2 如果是右子树上的度为0的节点
    336. node.parent.left = null ;
    337. }
    338. }
    339. return oldValue ;
    340. }
    341. /**
    342. * 找到后继节点
    343. * @param node
    344. * @return
    345. */
    346. private Node succeed(Node node) {
    347. if (node == null) {
    348. return null ;
    349. }
    350. //1,
    351. Node p = node ;
    352. if (p.right != null) {
    353. p = p.right ;
    354. while (p.left != null) {
    355. p = p.left ;
    356. }
    357. return p ;
    358. }
    359. //p.right == null
    360. //目的是找node节点的后继节点,所以要找第一个比它大的
    361. //所以在父节点不为空 并且一直在变化的父节点的右子树 、
    362. // 如果它发现在左子树后,那么就要退出循环,说明找到了第一个比它大的节点
    363. if (p.parent != null && p == p.parent.right) {
    364. p = p.parent ;
    365. }
    366. //p.parent == null
    367. //p == p.parent.left
    368. return p.parent ;
    369. }
    370. /**
    371. * HashMap中的key是不一定 也不太可能具备可比较性的
    372. * 所以使用hash值去进行决定出加到哪个位置
    373. * @param k1 键值1
    374. * @param k2 键值2
    375. * @param h1 k1对应的hash值
    376. * @param h2 k2对应的hash值
    377. * @return
    378. */
    379. private int compare(K k1,K k2,int h1,int h2) {
    380. //比较哈希值
    381. int result = h1 - h2 ;
    382. if (result != 0) {
    383. return result ;
    384. }
    385. // result == 0 说明哈希值相等 但是还要进行比较是否equals
    386. boolean equals = Objects.equals(k1, k2);//已经判断出k1==null && k2 == null的情况了。
    387. if (equals) {
    388. //说明k1,k2equals
    389. return 0 ;
    390. }
    391. //说明只是哈希值相等 但是不equals
    392. //但是总要分辨出谁大谁小 所以比较类名
    393. if (k1 != null && k2 != null) {
    394. String k1Cls = k1.getClass().getName() ;
    395. String k2Cls = k2.getClass().getName() ;
    396. result = k1Cls.compareTo(k2Cls) ;
    397. if (result != 0) {
    398. return result ;
    399. }
    400. //result == 0 说明是同一种类型并且具有可比较性
    401. if (k1 instanceof Comparable) {
    402. return ((Comparable)k1).compareTo(k2) ;
    403. }
    404. }
    405. //说明是同一种类型 哈希值相等 但是不具备可比较性
    406. //k1 != null && k2 == null
    407. //k1 == null && k2 != null
    408. //此时只可以进行两个key键值内存地址的比较
    409. return System.identityHashCode(k1) - System.identityHashCode(k2) ;
    410. }
    411. private void afterRemove(Node node, Node replacement) {
    412. //1。如果删除的节点是红色RED,删除之后无需处理
    413. if (isRed(node)) return;
    414. //2.用以取代被删除的node的子节点是RED时
    415. if (isRed(replacement)) {
    416. black(replacement) ;
    417. return;
    418. }
    419. Node parent = node.parent ;
    420. //删除的是根节点
    421. if (parent == null) return;
    422. //3.删除的是黑色叶子节点
    423. //判断被删除的node是左还有右:
    424. //parent.left == null说明被删除的node节点是parent的左子树,
    425. // 那么被删除的node节点的兄弟节点即是parent的右子树,反之同理即可
    426. boolean left = parent.left == null ;
    427. Node sibling = left ? parent.right : parent.left ;
    428. if (left) {//3.1被删除的节点在左边,那么兄弟节点sibling在右边
    429. if (isRed(sibling)) {
    430. //3.2.1删除的是黑色叶子节点并且sibling是红色,那么转换为sibling为黑色
    431. black(sibling) ;
    432. red(parent) ;
    433. rotateRight(parent) ;
    434. //更换兄弟
    435. sibling = parent.right ;
    436. }
    437. //3.2.2删除的是黑色叶子节点并且sibling为BLACK
    438. //3.2.2.1 兄弟节点没有一个是红色子节点,父节点要下根兄弟节点合并
    439. if (isBlack(sibling.right) && isBlack(sibling.left)) {
    440. //记录一下是否父节点也会下溢
    441. boolean parentBlack = isBlack(parent) ;
    442. black(parent) ;
    443. red(sibling) ;
    444. if (parentBlack) {
    445. afterRemove(parent,null) ;
    446. }
    447. } else { //3.2.2.2 兄弟节点至少有1个红色子节点,被删除节点向兄弟节点借用元素
    448. //兄弟节点的左边是黑色,说明右边是红色子节点
    449. if (isBlack(sibling.right)) {
    450. rotateLeft(sibling) ;
    451. sibling = parent.right ;//旋转后要更新兄弟节点
    452. color(sibling,colorOf(parent)) ;
    453. black(sibling.right) ;
    454. black(parent) ;
    455. }
    456. //兄弟节点的左边是红色,说明左边是红色子节点
    457. color(sibling,colorOf(parent)) ;
    458. black(sibling.right) ;
    459. black(parent) ;
    460. rotateRight(parent);
    461. }
    462. } else {//3.2被删除的节点在右边,那么兄弟节点sibling在左边
    463. if (isRed(sibling)) {
    464. //3.2.1删除的是黑色叶子节点并且sibling是红色,那么转换为sibling为黑色
    465. black(sibling) ;
    466. red(parent) ;
    467. rotateRight(parent) ;
    468. //更换兄弟
    469. sibling = parent.left ;
    470. }
    471. //3.2.2删除的是黑色叶子节点并且sibling为BLACK
    472. //3.2.2.1 兄弟节点没有一个是红色子节点,父节点要下根兄弟节点合并
    473. if (isBlack(sibling.left) && isBlack(sibling.right)) {
    474. //记录一下是否父节点也会下溢
    475. boolean parentBlack = isBlack(parent) ;
    476. black(parent) ;
    477. red(sibling) ;
    478. if (parentBlack) {
    479. afterRemove(parent,null) ;
    480. }
    481. } else { //3.2.2.2 兄弟节点至少有1个红色子节点,被删除节点向兄弟节点借用元素
    482. //兄弟节点的左边是黑色,说明右边是红色子节点
    483. if (isBlack(sibling.left)) {
    484. rotateLeft(sibling) ;
    485. sibling = parent.left ;//旋转后要更新兄弟节点
    486. color(sibling,colorOf(parent)) ;
    487. black(sibling.left) ;
    488. black(parent) ;
    489. }
    490. //兄弟节点的左边是红色,说明左边是红色子节点
    491. color(sibling,colorOf(parent)) ;
    492. black(sibling.left) ;
    493. black(parent) ;
    494. rotateRight(parent);
    495. }
    496. }
    497. }
    498. /**
    499. * 添加操作可能毁坏红黑树的性质
    500. * afterPut修复红黑树的性质
    501. * @param node
    502. */
    503. private void afterPut(Node node) {
    504. Node parent = node.parent ;
    505. //1.情况一中的四种添加位置,是无需进行修复处理红黑树的。
    506. //1.1添加的是根节点
    507. if (parent == null) {
    508. black(node) ;//根节点直接染成黑色
    509. return;
    510. }
    511. //1.2如果父节点是黑色,直接返回
    512. if (isBlack(parent)) return ;
    513. //2.情况二中的八种添加位置,是需要进行修复处理红黑树的。
    514. //2.1拿到uncle节点进行判断 uncle节点:叔父节点即是父节点的兄弟节点
    515. Node uncle = parent.sibling() ;
    516. //2.2祖父节点
    517. Node grand = parent.parent ;
    518. //2.3 叔父节点为RED,只需进行染色 然后进行递归grand作为新添加的节点进行处理
    519. if (isRed(uncle)) {
    520. black(parent) ;
    521. black(uncle) ;
    522. //把祖父节点当作是新添加的节点进行处理
    523. red(grand) ;
    524. afterPut(grand); ;
    525. return;
    526. }
    527. //2.4 叔父节点不为RED,需要进行旋转处理
    528. if (parent.isLeftChild()) { //parent是grand的左子树节点 L
    529. if (node.isLeftChild()) { //node是parent的左子树节点 LL
    530. //LL和RR为一类,parent染成BLack,grand染成RED
    531. black(parent) ;
    532. red(grand) ;
    533. rotateRight(grand);
    534. } else { //LR
    535. //LR 和 RL为一类,新添加的节点染成Black,grand染成RED
    536. black(node) ;
    537. red(grand) ;
    538. rotateLeft(parent) ;
    539. rotateRight(grand) ;
    540. }
    541. } else { //R
    542. if (node.isLeftChild()) { //RL
    543. //LR 和 RL为一类,新添加的节点染成Black,grand染成RED
    544. black(node) ;
    545. red(grand) ;
    546. rotateRight(parent); ;
    547. rotateLeft(grand); ;
    548. } else {//RR
    549. //LL和RR为一类,parent染成BLack,grand染成RED
    550. black(parent) ;
    551. red(grand) ;
    552. rotateLeft(grand);
    553. }
    554. }
    555. }
    556. /**
    557. * 对传入的节点进行染色处理
    558. * @param node 想要染色的节点
    559. * @param color 把该节点node染成color颜色
    560. * @return 返回染色后的节点
    561. */
    562. private Node color(Node node, boolean color) {
    563. if (node == null) {
    564. return node ;
    565. }
    566. node.color = color ;
    567. return node ;
    568. }
    569. /**
    570. * 把该node节点染成RED
    571. */
    572. private Node red(Node node) {
    573. return color(node,RED) ;
    574. }
    575. /**
    576. * 把该node节点染成BLACK
    577. */
    578. private Node black(Node node) {
    579. return color(node,BLACK) ;
    580. }
    581. /**
    582. * 传入一个node节点,进行返回该节点是什么颜色
    583. */
    584. private boolean colorOf(Node node) {
    585. //如果node为null,即是null节点 即是BLACK
    586. // 【对null节点进行特殊处理的原因是:我们一开始并没有对null节点的颜色进行设置】
    587. //如果不为空,那么返回node实际的颜色即可
    588. return node == null ? BLACK : node.color ;
    589. }
    590. /**
    591. * 判断该node节点颜色color是否为BLACK
    592. */
    593. private boolean isBlack(Node node) {
    594. return colorOf(node) == BLACK ;
    595. }
    596. /**
    597. * 判断该node节点颜色color是否为RED
    598. */
    599. private boolean isRed(Node node) {
    600. return colorOf(node) == RED ;
    601. }
    602. /**
    603. * 左旋转
    604. * @param grand 想要进行旋转的节点,传过来的是什么,什么节点就进行左旋转
    605. */
    606. private void rotateLeft(Node grand) {
    607. //进行左旋转,说明是RR,可以确定parent
    608. Node parent = grand.right ;
    609. //child即是T1
    610. Node child = parent.left ;
    611. grand.right = child; //1.
    612. parent.left = grand ; //2.
    613. //执行旋转之后的逻辑
    614. afterRotate(grand,parent,child);
    615. }
    616. /**
    617. * 右旋转
    618. * @param grand 想要进行旋转的节点,传过来的是什么,什么节点就进行右旋转
    619. */
    620. private void rotateRight(Node grand) {
    621. //1.进行右旋转,说明是LL,那么可以确定parent
    622. Node parent = grand.left ;
    623. //2.找出T2
    624. Node child = parent.right ;
    625. //3.对g执行右旋转
    626. grand.left = child ; //3.1
    627. parent.right = grand ;//3.2
    628. //执行旋转之后的逻辑
    629. afterRotate(grand,parent,child);
    630. }
    631. /**
    632. * 旋转之后统一要做的逻辑
    633. * @param grand (最低的失去平衡的节点)
    634. * @param parent (parent是grand的子节点)
    635. * @param child 旋转过程中父节点改变的节点(T1或T2)
    636. */
    637. private void afterRotate(Node grand, Node parent, Node child) {
    638. int index = index(grand.key) ;
    639. //3.让parent成为这颗子树的根节点
    640. //3.1parent先连上grand的父节点
    641. parent.parent = grand.parent ;
    642. //3.2 grand再去连上parent
    643. if (grand.isLeftChild()) {
    644. //如果grand是父节点的左子树
    645. grand.parent.left = parent ;
    646. } else if (grand.isRightChild()) {
    647. //如果grand是父节点的右子树
    648. grand.parent.right = parent ;
    649. } else {//既不是父节点的左子树又不是右子树,那么说明grand即是root根节点
    650. table[index] = parent ;
    651. }
    652. //4.更新child(T1)的parent
    653. if (child != null) {
    654. child.parent = grand ;
    655. }
    656. //5.更新grand的parent
    657. grand.parent = parent ;
    658. }
    659. /**
    660. * 根据key找到Node节点
    661. */
    662. public Node node(K key) {
    663. Node root = table[index(key)] ;
    664. return root == null ? null : node(root,key) ;//递归调用
    665. }
    666. /**
    667. * 根据key找到Node节点
    668. */
    669. private Node node(Node node,K k1) {
    670. int index = index(k1) ;
    671. int h1 = k1 == null ? 0 : k1.hashCode() ;
    672. Node result = null ;
    673. int cmp = 0 ;
    674. while (node != null) {
    675. K k2 = node.key ;
    676. int h2 = node.hash ;
    677. //先比较哈希值
    678. if (h1 > h2) {
    679. node = node.right ;
    680. } else if (h1 < h2) {
    681. node = node.left ;
    682. } else if (Objects.equals(k1,k2)) {
    683. //哈希值相等 并且equals相等 并且已经判断过内存地址相同的情况了 之后都是内存地址不相等的情况
    684. return node ;//那么找到了
    685. } else if (k1 != null && k2 != null
    686. && k1.getClass() == k2.getClass()
    687. && k1 instanceof Comparable
    688. && (cmp = ((Comparable)k1).compareTo(k2))!=0) { //哈希值相等 但是equals不相等 但是类相同 并且具备可比较性
    689. if (cmp > 0) {
    690. node = node.right ;
    691. } else if (cmp < 0) {
    692. node = node.left;
    693. }
    694. // } else { //cmp == 0 并不代表两个对象真的就是相等的,所以要执行后面遍历搜索的代码
    695. // return node ;
    696. // }
    697. } else if (node.right != null && (result = node(node.right,k1))!=null) {
    698. //哈希值相等 但是不具备可比较性 也不equals。之后进行递归 如果向右可以找到的话
    699. return result;
    700. // } else if (node.left != null && (result = node(node.left,k1)) !=null) {
    701. // //哈希值相等 但是不具备可比较性 也不equals。之后进行递归 如果向左可以找到的话
    702. // return result ;
    703. // } else {//找不到
    704. // return null ;
    705. // }
    706. } else {//只能往左边,减少递归
    707. node = node.left ;
    708. }
    709. }
    710. return null ;//找不到
    711. }
    712. /**
    713. * 根据key生成对应的索引(在桶数组的位置)
    714. */
    715. private int index(K key) {
    716. //1.使用自身定义的hashCode方法获取哈希值
    717. int hash = key.hashCode();
    718. //2.自身hashCode方法或许获取的哈希值是不稳定的 所以jdk再进行帮忙均匀计算一下
    719. hash = hash ^ (hash >>> 16) ;
    720. //3.计算出索引值并且返回
    721. return hash & (table.length - 1) ;
    722. }
    723. private static class Node {
    724. K key ;
    725. V value ;
    726. boolean color = RED;
    727. int hash ;
    728. Node left ;//左子节点
    729. Node right ;//右子节点
    730. Node parent ;//父节点
    731. int height ;
    732. public Node(K key, V value , int hash, Node parent) {
    733. this.key = key ;
    734. this.value = value ;
    735. this.hash = key == null ? 0 : key.hashCode() ;
    736. this.parent = parent ;//除了根节点,其余节点都具有父节点,所以要在构造方法传入
    737. }
    738. //判断是否为叶子节点
    739. public boolean isLeaf() {
    740. return left == null && right == null ;
    741. }
    742. //判断是否具有两个子节点
    743. public boolean hasTwoChildren() {
    744. return left != null && right != null ;
    745. }
    746. //说明是parent的左子树
    747. public boolean isLeftChild() {
    748. return parent != null && this == parent.left ;
    749. }
    750. //说明是parent的右子树
    751. public boolean isRightChild() {
    752. return parent != null && this == parent.right ;
    753. }
    754. /**
    755. * 找出兄弟节点
    756. * @return
    757. */
    758. public Node sibling() {
    759. if (isLeftChild()) {
    760. //说明该节点是父节点的左子树
    761. return parent.right ;//说明该节点的兄弟节点即是父节点的右子树
    762. }
    763. if (isRightChild()) {
    764. //说明该节点是父节点的右子树
    765. return parent.left ;//说明该节点的兄弟节点即是父节点的左子树
    766. }
    767. //说明该节点既不是父节点的左子树 也不是父节点的右子树
    768. //那么说明该节点没有父节点,那么就没有兄弟节点
    769. return null ;
    770. }
    771. }
    772. }

  • 相关阅读:
    密码安全策略
    Vue极简教程,相关指令详解
    Apache、Nginx、IIS文件解析漏洞
    百度智能云正式上线Python SDK版本并全面开源!
    verilog实现分频(奇数分频,偶数分频,且50%占空比,通用版本)
    mysql运行报错:
    RunnerGo:让你的性能测试变得轻松简单
    java源码系列:HashMap源码验证,自己手写一个HashMap之03-写get方法以及思路等
    测试框架到底是什么,如何定义?
    使用原生html<table>构造复杂table表
  • 原文地址:https://blog.csdn.net/m0_61784000/article/details/126582188