底层数据结构:数组,链表
支持动态扩容

支持以下操作

put(K key, V value)
getIndex(K k, int length)
get(K k)
getNode(Node node, K k)

MyMap接口

  1. public interface MyMap<K, V> {
  2. // 向集合中插入数据
  3. public V put(K k, V v);
  4. // 根据k 从Map集合中查询元素
  5. public V get(K k);
  6. // 获取集合元素个数
  7. public int size();
  8. // Entry的作用 === Node节点
  9. interface Entry<K, V> {
  10. K getKey();
  11. V getValue();
  12. V setValue(V value);
  13. }
  14. }

具体实现MyHashMap

  1. public class MyHashMap<K, V> implements MyMap<K, V> {
  2. // 定义table 存放HasMap 数组元素 默认是没有初始化容器 懒加载
  3. Node<K, V>[] table = null;
  4. // 实际用到table 存储容量 大小
  5. int size;
  6. // HashMap默认负载因子,负载因子越小,hash冲突机率越低
  7. float DEFAULT_LOAD_FACTOR = 0.75f;
  8. // table默认初始大小 16
  9. static int DEFAULT_INITIAL_CAPACITY = 16;
  10. public V put(K key, V value) {
  11. // 1.判断table 数组大小是否为空(如果为空的情况下 ,做初始化操作)
  12. if (table == null) {
  13. table = new Node[DEFAULT_INITIAL_CAPACITY];
  14. }
  15. // 2. 判断是否需要扩容
  16. // 实际存储大小=负载因子*初始容量=DEFAULT_LOAD_FACTOR0.75*DEFAULT_INITIAL_CAPACITY16=x
  17. // 如果size>x的时候就需要开始扩容数组,扩容数组大小之前两倍
  18. if (size > (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)) {
  19. // 需要开始对table进行属数组扩容
  20. resize();
  21. }
  22. // 3.取出hash值指定下标位置对应的那个Node
  23. int index = getIndex(key, DEFAULT_INITIAL_CAPACITY);
  24. Node<K, V> indexNode = table[index];
  25. Node<K, V> newNode = null;
  26. // 4.没有发生hash冲突(index冲突)问题
  27. if (indexNode == null) {
  28. newNode = new Node<K, V>(key, value, null);
  29. size++;
  30. // 5.发生hash冲突(index冲突)问题
  31. } else {
  32. Node<K, V> curNode = indexNode;
  33. //遍历链表的元素
  34. while (curNode != null) {
  35. // 1.key相同,修改值
  36. //这里即用到了equals和==是因为 k有可能被重写hashCode方法,并且k有可能是基本数据类型(用==比较)。
  37. //equals和==位置不能调换,因为如果k被重写hashCode,假如==在前,比较的是对象的地址
  38. if (curNode.getKey().equals(key) || curNode.getKey() == key) {
  39. return curNode.setValue(value);
  40. //2 key不同,添加到链表末尾
  41. // 两种情况: 1.hascode不同但取模余数相同 2.或者hashCode 相同
  42. } else if (curNode.next == null){
  43. // 说明遍历到最后一个node且该操作不是修改值。创建新的node,下一个结点为indexNode
  44. newNode = new Node<K, V>(key, value, indexNode);
  45. size++;
  46. }
  47. //取链表下一个node
  48. curNode = curNode.next;
  49. }
  50. }
  51. //将新创建的node放在table[index]
  52. table[index] = newNode;
  53. return null;
  54. }
  55. // 对table进行扩容
  56. private void resize() {
  57. // 1.生成新的table 是之前的两倍的大小 DEFAULT_INITIAL_CAPACITY*2
  58. Node<K, V>[] newTable = new Node[DEFAULT_INITIAL_CAPACITY << 1];
  59. // 2.重新计算index索引,存放在新的table里面
  60. for (int i = 0; i < table.length; i++) {
  61. //存放在之前的table的Node
  62. Node<K, V> oldNode = table[i];
  63. //遍历该链表,将oldTable的值一个个放进新的table中
  64. while (oldNode != null) {
  65. //table[i] = null;// 赋值为null---为了垃圾回收机制能够回收 将之前的node删除
  66. //1.重新计算该Node在新table的index
  67. K oldK = oldNode.key;
  68. int newIndex = getIndex(oldK, newTable.length);
  69. //2.这里记录oldNext,因为下面将oldNodex.next指向新table的结点
  70. Node<K, V> oldNext = oldNode.next;
  71. //3.oldNode每次都插入newTable[newIndex],如果newTable[newIndex]已经有值,则形成链表
  72. oldNode.next = newTable[newIndex];
  73. newTable[newIndex] = oldNode;
  74. //4.继续取oldNext
  75. oldNode = oldNext;
  76. }
  77. }
  78. // 3.将newtable赋值给老的table
  79. table = newTable;
  80. DEFAULT_INITIAL_CAPACITY = newTable.length;
  81. newTable = null;// 赋值为null---为了垃圾回收机制能够回收
  82. }
  83. public int getIndex(K k, int length) {
  84. int hashCode = k.hashCode();
  85. int index = hashCode % length;
  86. return index;
  87. }
  88. public V get(K k) {
  89. Node<K, V> node = getNode(table[getIndex(k, DEFAULT_INITIAL_CAPACITY)], k);
  90. return node == null ? null : node.value;
  91. }
  92. public Node<K, V> getNode(Node<K, V> node, K k) {
  93. while (node != null) {
  94. if (node.getKey().equals(k)) {
  95. return node;
  96. }
  97. node = node.next;
  98. }
  99. return null;
  100. }
  101. public int size() {
  102. return size;
  103. }
  104. // 测试方法.打印所有的链表元素
  105. void print() {
  106. for (int i = 0; i < table.length; i++) {
  107. Node<K, V> node = table[i];
  108. System.out.print("下标位置[" + i + "]");
  109. while (node != null) {
  110. System.out.print("[ key:" + node.getKey() + ",value:" + node.getValue() + "]");
  111. node = node.next;
  112. }
  113. System.out.println();
  114. }
  115. }
  116. // 定义节点
  117. class Node<K, V> implements Entry<K, V> {
  118. // 存放Map 集合 key
  119. private K key;
  120. // 存放Map 集合 value
  121. private V value;
  122. // 下一个节点Node
  123. private Node<K, V> next;
  124. public Node(K key, V value, Node<K, V> next) {
  125. super();
  126. this.key = key;
  127. this.value = value;
  128. this.next = next;
  129. }
  130. public K getKey() {
  131. return this.key;
  132. }
  133. public V getValue() {
  134. return this.value;
  135. }
  136. public V setValue(V value) {
  137. // 设置新值的返回老的 值
  138. V oldValue = this.value;
  139. this.value = value;
  140. return oldValue;
  141. }
  142. }
  143. }

总结:

在put时再进行容器的初始化,整个过程下来,我觉得最难的那部分就是扩容,当出现索引冲突(hash冲突)时如何处理,这个流程各位朋友自己尝试实现一遍相信会理解很多。