当前位置:   article > 正文

手写简易版HashMap_手写hashmap

手写hashmap

目录

前言

HashMap源码解析

1.hash值算法和幂等性

2. get()方法源码解析

3. put() 方法源码解析

手写HashMap

1. 定义Map接口

2. 定义Entry 对象

3 .定义HashMap 实现类

4. 测试


前言

    我们发现HashMap在jdk 1.8之后用了数组+链表+红黑树的结构,为了加深对HashMap底层设计的理解,自己也读了一些hashmap相关的源码,因此想通过手写hashmap去理其底层的操作原理。

    目标:  基于数组+链表结构实现一个HashMap,包含get()、put()、remove()方法。

    环境:  jdk1.8

    知识储备:  hashcode, 幂等性, 数组,链表,红黑树。

    原理:   Hashmap 是先通过key计算得到一个hash值,通过hash值%(length-1)找到桶的位置,找到位置通过比对key 和hash值就能得到key对应的value, 如果有链表遍历链表,如果有红黑树就遍历红黑树,然后再进行相应的操作,如get,put, remove等。

HashMap源码解析

1.hash值算法和幂等性

    通过此hash值和map的length计算得到key 在hashmap桶里的位置index, 使用到的hash算法也是采用散列思想,能够均匀分布在hashmap的桶里面。

    先看一段源代码, hash(object key)方法

  1. static final int hash(Object key) {
  2. int h;
  3. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  4. }

可以发现, HashMap计算hash值是依据hashcode实现的,由于hashcode的幂等性,每次结算得出来的结果是相等的, 写一个小demo,多次运行程序:

        发现每次结算出来的结果是相同的, 因为幂等性,存放到桶里的key 对应的hash值是不相同的,同时也是固定的。

2. get()方法源码解析

      看一下hashmap里get()里一个重要的方法:  getNode()

       1) 根据hash值计算出位置赋值给first, 然后比较first的key是否与传入的key 相等,如果相等就返回,如果不相等,那么就遍历first的next。

       2) 如果first是一颗红黑树,那么就执行getTreeNode()方法,如果first不是那么就遍历链表。

具体的流程如下: 

1. 通过key 进行hash 计算得到index
2. 根据index  判断是否为空,如果为空就直接返回null。
3. 如果不为空,有查询的key与当前key 进行比较,如果当前节点的next是否为空,那么就返回当前节点,如果为不为空那么就取遍历next,判断node是否为树,如果是树,那么遍历树,如果是链表那么遍历链表,通过比较hash值和key来判断,直到相等为止。
4. 如果相等就直接返回数据。

3. put() 方法源码解析

        在Put 到桶里的元素之前,每个元素可以看作为一个Node, Node里包含 hash值,hash值是必须要有的,因为在遍历链表的时候,不能仅通过hashcode计算出来的index来比较,还有需要比较hash,才能找到指定的key, hashmap中定义的Node的结构如下:

  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. final int hash;
  3. final K key;
  4. V value;
  5. Node<K,V> next;
  6. Node(int hash, K key, V value, Node<K,V> next) {
  7. this.hash = hash;
  8. this.key = key;
  9. this.value = value;
  10. this.next = next;
  11. }

  put 流程分析: 

   

具体的流程如下: 

1. 算出hashcode, 取模得到下标,然后通过下标拿到对象。

2. 如果为根据index取到的值为空那么可以直接赋值。

3.  如果不为空,那么使用链表进行存储, 每次存储的时候需要用新的节点的next指向之前的节点。

  看了几个方法的源码后,发现他们都有相似的规律:  通过hash值去找到node, 然后判断node是否是链表或者红黑树,然后遍历链表或TreeNode(红黑树),然后再操作。

手写HashMap

1. 定义Map接口

           HashMap实现了Map接口,可以定义如下常用方法,另外需要添加一个Entry<K,V>接口,此接口用来获取节点(Entry)的key 和value。

  1. package com.example.hashmap;
  2. /**
  3. * @author bingbing
  4. * @date 2021/5/15 0015 15:51
  5. */
  6. public interface MyMap<K, V> {
  7. int size();
  8. V put(K k, V v);
  9. V get(K k);
  10. V remove(K k);
  11. interface Entry<K, V> {
  12. K getKey();
  13. V getValue();
  14. }
  15. }

2. 定义Entry 对象

         主要包含当前节点的key、value、next指针以及hash值,另外实现Map里的Entry接口。

  1. class Entry<K, V> implements MyMap.Entry<K, V> {
  2. int hash;
  3. K k;
  4. V v;
  5. Entry<K, V> next;
  6. public Entry(K k, V v, Entry<K, V> next, int hash) {
  7. this.k = k;
  8. this.v = v;
  9. this.hash = hash;
  10. this.next = next;
  11. }
  12. @Override
  13. public K getKey() {
  14. return k;
  15. }
  16. @Override
  17. public V getValue() {
  18. return v;
  19. }
  20. }

3 .定义HashMap 实现类

        注意: 

             1)  put()方法,在new Node时将计算得到的hash值和当前的entry 当作next赋赋值给node。  table[index] = new Entry<>(k, v, entry, hash);

             2)  get()方法,在遍历链表的时候,需要比较hash值和next,key 。

             3)  remove()方法,如果在链表里,那么用前一个节点的next指向当前节点next即可删除。

  1. package com.example.hashmap;
  2. /**
  3. * @author bingbing
  4. * @date 2021/5/15 0015 15:52
  5. */
  6. public class MyHashMap<K, V> implements MyMap<K, V> {
  7. Entry[] table = null;
  8. int size = 0;
  9. public MyHashMap() {
  10. table = new Entry[16];
  11. }
  12. @Override
  13. public int size() {
  14. return size;
  15. }
  16. /**
  17. * 算出hashcode, 取模得到下标,然后通过下标拿到对象。
  18. * 如果为空那么可以直接赋值。
  19. * 如果不为空,那么使用链表进行存储
  20. *
  21. * @param k
  22. * @param v
  23. * @return
  24. */
  25. @Override
  26. public V put(K k, V v) {
  27. int index = hash(k);
  28. int hash = k.hashCode();
  29. Entry<K, V> entry = table[index];
  30. if (null == entry) {
  31. table[index] = new Entry<>(k, v, null, hash);
  32. size++;
  33. } else {
  34. // 链表
  35. table[index] = new Entry<>(k, v, entry, hash);
  36. }
  37. return (V) table[index].getValue();
  38. }
  39. private int hash(K k) {
  40. int index = k.hashCode() % (table.length - 1);
  41. if (index < 0) {
  42. // 如果取的到为负数
  43. return -index;
  44. }
  45. return index;
  46. }
  47. /**
  48. * 1. 通过key 进行hash 计算得到index
  49. * 2. 根据index 判断是否为空,如果为空就直接返回null。
  50. * 3. 如果不为空,有查询的key与当前key 进行比较,如果当前节点的next是否为空,那么就返回当前节点,如果为不为空那么就取遍历next,直到相等为止。
  51. * 4. 如果相等就直接返回数据。
  52. *
  53. * @param k
  54. * @return
  55. */
  56. @Override
  57. public V get(K k) {
  58. int index = hash(k);
  59. int hash = k.hashCode();
  60. Entry<K, V> entry = table[index];
  61. if (null == entry) {
  62. return null;
  63. } else {
  64. if (entry.getKey() == k && hash == entry.hash) {
  65. return entry.getValue();
  66. }
  67. Entry<K, V> next = entry.next;
  68. while (null != next) {
  69. if (next.getKey() == k&& hash == next.hash) {
  70. return next.getValue();
  71. }
  72. next = next.next;
  73. }
  74. }
  75. return null;
  76. }
  77. @Override
  78. public V remove(K k) {
  79. int index = hash(k);
  80. int hash = k.hashCode();
  81. Entry<K, V> entry = table[index];
  82. if (null == entry) {
  83. return null;
  84. } else {
  85. if (entry.getKey() == k && entry.hash == hash) {
  86. // 直接移除该元素
  87. entry = entry.next;
  88. table[index] = entry;
  89. return (V) table[index];
  90. }
  91. if (null != entry.next) {
  92. // 链表
  93. Entry<K, V> head = entry;
  94. Entry<K, V> p = head;
  95. Entry<K, V> next = head.next;
  96. do {
  97. if (next.getKey() == k && next.hash == hash) {
  98. // 删除该节点, 前一个节点的next 指向该节点的next
  99. p.next = next.next;
  100. break;
  101. }
  102. p = next;
  103. next = next.next;
  104. } while (next != null);
  105. } else {
  106. // 数组
  107. table[index] = null;
  108. }
  109. return table[index] == null ? null : (V) table[index].getValue();
  110. }
  111. }
  112. class Entry<K, V> implements MyMap.Entry<K, V> {
  113. int hash;
  114. K k;
  115. V v;
  116. Entry<K, V> next;
  117. public Entry(K k, V v, Entry<K, V> next, int hash) {
  118. this.k = k;
  119. this.v = v;
  120. this.hash = hash;
  121. this.next = next;
  122. }
  123. @Override
  124. public K getKey() {
  125. return k;
  126. }
  127. @Override
  128. public V getValue() {
  129. return v;
  130. }
  131. }
  132. }

4. 测试

      测试get(), put () ,remove()方法

  1. package com.example.hashmap;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. /**
  5. * @author bingbing
  6. * @date 2021/5/15 0015 15:19
  7. */
  8. public class HashMapDemo {
  9. private static MyInnerMap myInnerMap = new MyInnerMap(16);
  10. public static void main(String[] args) {
  11. Map<Object, Object> map = new HashMap<>(16);
  12. map.put("张三", "张三");
  13. map.put("李四", "李四");
  14. map.put("王五", "王五");
  15. map.put("刘六", "刘六");
  16. System.out.println(map);
  17. myInnerMap.put("张三", "张三");
  18. myInnerMap.put("李四", "李四");
  19. myInnerMap.put("王五", "王五");
  20. myInnerMap.put("刘六", "刘六");
  21. myInnerMap.put("小二", "小二");
  22. myInnerMap.put("李七", "李七");
  23. myInnerMap.put("赵九", "赵九");
  24. myInnerMap.put("周十", "周十");
  25. myInnerMap.put("孙一", "孙一");
  26. // 使用自定义的Map
  27. MyMap<Object, Object> myMap = new MyHashMap<>();
  28. myMap.put("张三", "张三");
  29. myMap.put("李四", "李四");
  30. Object o = myMap.put("周十", "周十");
  31. System.out.println(myMap.get("张三"));
  32. System.out.println(myMap.get("李四"));
  33. System.out.println(myMap.get("周十"));
  34. myMap.remove("周十");
  35. System.out.println(myMap.get("周十"));
  36. myMap.remove("李四");
  37. System.out.println(myMap.get("李四"));
  38. }
  39. static class MyInnerMap {
  40. int length;
  41. MyInnerMap(int capacity) {
  42. this.length = capacity;
  43. }
  44. // 利用数组的特性去取模,得到的hashcode 遵循幂等性
  45. public void put(Object key, Object value) {
  46. System.out.println("key:" + key + ",hashcode:" + key.hashCode() + ",位置:" + Math.abs(key.hashCode()) % (length - 1));
  47. }
  48. }
  49. }

    打印结果如下:

 

   源码地址:  java-source-learining: java框架源码学习

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/227361
推荐阅读
相关标签
  

闽ICP备14008679号