JAVA进阶篇——一行代码一行代码嚼烂之超详细注解手写HashMap(二)

x33g5p2x  于2021-11-22 转载在 Java  
字(4.1k)|赞(0)|评价(0)|浏览(570)

HashMap用得顺手吧?不知道让你手写一个HashMap你能不能写出来呢,今天为大家带来的是手写HashMap,一行代码一行代码地嚼一下,看完这个再去看HashMap的源码,帮助你深度理解HashMap的原理。🍭

这里还是很推荐大家去看一下HashMap的源码的,因为所涉及的知识面很广泛,可谓是受益匪浅。话不多说,咱们开始看。🐳

在此之前,推荐大家先去看完这一篇博客JAVA进阶篇——HashMap底层实现解析(一),这篇博客是基于手动写HashMap代码的底层理论,会更加帮助你加深理解。在JDK1.8中是使用数组+尾插链表+树(红黑树)组成的,以下代码是JDK1.7中的数组+头插链表的结构所实现的HashMap。🦕

当熟悉以后可在此基础上优化为JDK1.8的HashMap。🌴

实体类

  1. package com.gantiexia.hashmap;
  2. /** * 实体 * * @author GanTieXia * @date 2021/9/27 21:26 */
  3. public class Entry<K,V> {
  4. /** HASH值*/
  5. int hash;
  6. /** key*/
  7. K key;
  8. /** value*/
  9. V value;
  10. /** 链表的指针*/
  11. Entry<K, V> next;
  12. /** 消费者*/
  13. public Entry(){}
  14. public Entry(K key, V value){
  15. this.key = key;
  16. this.value = value;
  17. }
  18. public Entry(int hash, K key, V value, Entry<K, V> next) {
  19. this.hash = hash;
  20. this.key = key;
  21. this.value = value;
  22. this.next = next;
  23. }
  24. /** get/set方法*/
  25. public K getKey() {
  26. return key;
  27. }
  28. public void setKey(K key) {
  29. this.key = key;
  30. }
  31. public V getValue() {
  32. return value;
  33. }
  34. public void setValue(V value) {
  35. this.value = value;
  36. }
  37. }

HashMap类

这里只实现了HashMap的 put / get / size / 扩容 方法:

  1. package com.gantiexia.hashmap;
  2. /** * 这个 HashMap 支持 put / get / 扩容 * * @author GanTieXia * @date 2021/9/27 21:27 */
  3. public class HashMap<K,V> {
  4. /** 初始容量值*/
  5. static final int INITIAL_CAPACITY = 4;
  6. /** 加载因子*/
  7. float LOAD_FACTOR = 0.75f;
  8. /** 记录map集合的容量*/
  9. int COUNT = 0;
  10. Entry<K, V>[] table;
  11. public K put(K key, V value) {
  12. Entry<K, V> newEntry = new Entry(key, value);
  13. // 这里我们没有写可以手动输入初始值的方法,所以直接设置默认容量
  14. if (table == null) {
  15. table = new Entry[INITIAL_CAPACITY];
  16. }
  17. // 数组下标位置
  18. int hash = hash(key);
  19. // 链表模型,如果Hash值相等,则形成链表
  20. // head代表链表头,此处用的头插法
  21. Entry<K, V> head = table[hash];
  22. // 当容量达到初始容量的加载因子时扩容
  23. if(COUNT > table.length * LOAD_FACTOR){
  24. resize();
  25. }
  26. // 注意数组+链表的概念模型将会再这里产生
  27. // 如果table[hash]这个位置没有元素,直接赋值
  28. if (head == null) {
  29. table[hash] = newEntry;
  30. COUNT++;
  31. return key;
  32. } else {
  33. // 如果table[hash]这个位置有元素
  34. Entry tail = new Entry<K, V>();
  35. // 如果这个元素的next指针为空,设置指针指向新的键值
  36. if (head.next == null) {
  37. head.next = newEntry;
  38. } else {
  39. // 如果指针next不为空,找到指针为空的链表上的那一个元素,插入
  40. do {
  41. tail = head;
  42. } while ( (head = head.next) != null );
  43. // 该链表上此对象的指针指向新的对象
  44. tail.next = newEntry;
  45. }
  46. COUNT++;
  47. return key;
  48. }
  49. }

到这里中途打断一下,为了更深度地帮助各位理解HashMap数组+链表的结构,这里我画了一张图帮助理解:

继续:

  1. /** 扩容*/
  2. public int resize() {
  3. // 扩容, << 2 代表 4倍 扩容
  4. int newCapacity = INITIAL_CAPACITY << 2;
  5. // 新建一个数组
  6. Entry[] newTable = new Entry[newCapacity];
  7. // 数组的copy方法
  8. System.arraycopy(table, 0, newTable, 0, table.length);
  9. // 新数组赋值给老数组
  10. this.table = newTable;
  11. // 返回新容量
  12. return newCapacity;
  13. }
  14. /** 获取 key 的键值*/
  15. public V get(K key) {
  16. Entry<K, V> entry;
  17. // 二元表达式通过getEntry()方法寻找键值
  18. return (entry = getEntry(hash(key), key)) == null ? null : entry.value;
  19. }
  20. /** 传入 hash 获取 key 值*/
  21. public Entry<K, V> getEntry(int hash, K key) {
  22. // 新建对象做逻辑判断
  23. Entry<K, V> entry = table[hash];
  24. // 如果 table[hash] 为空,证明不存在此 key 的键值
  25. if (entry == null) {
  26. return null;
  27. } else if (entry != null && entry.next == null) {
  28. // 如果此元素不为空,并且此元素的next指针为空,证明此链表上只有一个元素,直接返回
  29. return entry;
  30. } else if (entry.next != null) {
  31. // 如果此对象的 next 指针不为空,就要在此链表上去找到 hash 值相等的数据
  32. do {
  33. // 如果此元素的hash与该对象的key值的hash相等
  34. // 并且
  35. // key相等 或者 key不为空并且key相等
  36. if (hash == hash(entry.key) && (key == entry.key || (key != null && key.equals(entry.key)))) {
  37. // 返回此链表上的对象
  38. return entry;
  39. }
  40. // 条件是此对象的指针 next 不为空
  41. } while ( (entry = entry.next) != null );
  42. // 如果通过以上循环没有找到对象的对像的话,返回空
  43. return null;
  44. }
  45. return null;
  46. }
  47. /** 计算hash*/
  48. public final int hash(K key) {
  49. // &运算:4 & 5
  50. // 转化为二进制: 100 & 101 = 100 (同时为1才为1,否则为0)
  51. // 十进制:4
  52. // 所以: 4 & 5 = 4
  53. //
  54. // 0x7FFFFFFF代表int的最大值
  55. //
  56. // 此操作主要是为了取得正整数
  57. //
  58. // 后面 %运算 是为了使得返回值数组下标不会越界,即返回值小于数组或者扩容后数组的大小
  59. return (key == null) ? 0 : ( key.hashCode() & 0x7FFFFFFF % table.length);
  60. }
  61. /** 返回 map 集合大小的方法*/
  62. public int size(){
  63. return COUNT;
  64. }
  65. }

测试类

  1. package com.gantiexia.hashmap;
  2. /** * 测试 MyHashMap * * @author GanTieXia * @date 2021/9/27 21:29 */
  3. public class TestMyHashMap {
  4. public static void main(String[] args) {
  5. HashMap<String,String> map = new HashMap<>();
  6. map.put("GanTieXia","肝铁侠");
  7. map.put("ZhuZhuXia","猪猪侠");
  8. map.put("ShanDianXia","闪电侠");
  9. System.out.println("{GanTieXia" + ":" + map.get("GanTieXia")+"}");
  10. System.out.println("{ZhuZhuXia" + ":" + map.get("ZhuZhuXia")+"}");
  11. System.out.println("{ShanDianXia" + ":" + map.get("ShanDianXia")+"}");
  12. System.out.println("-----------------------");
  13. System.out.println("map集合的大小:" + map.size());
  14. }
  15. }

运行结果✔:

到这里手写的1.7版本JDK版本的手写HashMap就完成了,相信会手写以后的你再返回去看HashMap的源码的时候,会更加条理清晰。💖

相关文章

最新文章

更多