[Java] 手写一个简单的HashMap
1 前言
阅读此篇博客,你需要学习:
- HashMap原理以及版本的更替
- Object的hashCode原理
- 哈希冲突的解决方法
- Java泛型
若内容存在错误,欢迎指正。
2 知识
记忆技巧:
- 本质上,HashMap就是一个Node<K, V>数组,Node内部的next实现链表。
- 多个操作存在重复的操作。
- putVal(K key, V value, Node<K, V>[] table)方法:将key-value放入指定的Node数组中。put()方法和rehash时都需要调用,不要写死在put()函数中。
- getIndex(K key, int length)方法:获取key的index。get()和put()方法都会使用。
- put时,不要只专注于添加Node,一定需要注意key.hashCode()相同的情况,即覆盖value。
- key的对比要使用hashCode。
- 注意链表的遍历写法。
编写策略:
- 散列函数:hashCode() + 除留求余法
- 冲突解决:链地址法
- 链式插入策略:头插法
- 检查策略:先检查后放入
- 扩容策略:节点重新hash
3 代码
public class MyHashMap<K, V> {
private static final class Node<K, V> {
K key;
V value;
Node<K, V> next;
public Node(K key, V value, Node<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
public Node(K key, V value) {
this.key = key;
this.value = value;
}
}
private int size;
private int capacity;
private Node<K, V>[] table;
private final static int DEFAULT_CAPACITY = 16;
private final static float LOAD_FACTOR = 0.75f;
public MyHashMap() {
capacity = DEFAULT_CAPACITY;
table = new Node[capacity];
size = 0;
}
public MyHashMap(int capacity) {
this.capacity = capacity;
table = new Node[capacity];
size = 0;
}
public void put(K key, V value) {
if (size >= capacity * LOAD_FACTOR) rehash();
putVal(key, value, table);
}
private void putVal(K key, V value, Node<K, V>[] table) {
int hashCode = key.hashCode();
int index = getIndex(hashCode, capacity);
if (table[index] == null) {
table[index] = new Node<>(key, value);
} else {
Node<K, V> node = table[index];
while (node != null) {
if (node.key.hashCode() == hashCode) {
node.value = value;
return;
}
node = node.next;
}
Node<K, V> newNode = new Node<>(key, value, table[index]);
table[index] = newNode;
}
size++;
}
private int getIndex(int hashCode, int length) {
return hashCode % length;
}
public V get(K key) {
int hashCode = key.hashCode();
int index = getIndex(hashCode, capacity);
if (table[index] != null) {
Node<K, V> node = table[index];
while (node != null) {
if (node.key.hashCode() == hashCode) return node.value;
node = node.next;
}
}
return null;
}
private void rehash() {
size = 0;
capacity *= 2;
Node<K, V>[] newTable = new Node[capacity];
for (Node<K, V> node : table) {
while (node != null) {
Node<K, V> next = node.next;
node.next = null;
putVal(node.key, node.value, newTable);
node = next;
}
}
table = newTable;
}
public int getSize() {
return size;
}
}
4 测试
在MyHashMap类中编写简单的函数,用于获取HashMap内部信息:
public void showContext() {
System.out.println("{size=" + size + ", capacity=" + capacity + "}");
System.out.println("-------------------");
for (Node<K, V> node : table) {
if (node == null) System.out.println("[null]");
else {
while (node != null) {
System.out.print("[" + node.value + "]");
if (node.next != null) System.out.print("-->");
node = node.next;
}
System.out.println();
}
}
System.out.println("-------------------");
}
测试方向:
- 链地址法是否正常
- 扩容是否正常
- get是否正常
public static void main(String[] args) {
MyHashMap<Integer, Integer> hashMap = new MyHashMap<>(2);
System.out.println("Notice: Init hashMap");
hashMap.showContext();
hashMap.put(1, 1);
hashMap.put(3, 3);
System.out.println("Notice: Has been put 1=1 3=3");
hashMap.showContext();
hashMap.put(2, 2);
hashMap.put(4, 4);
System.out.println("Notice: Has been put 1=1 3=3, 2=2, 4=4");
hashMap.showContext();
System.out.println("Notice: get(3): " + hashMap.get(3));
System.out.println("Notice: get(5): " + hashMap.get(5));
}
Notice: Init hashMap
{size=0, capacity=2}
-------------------
[null]
[null]
-------------------
Notice: Has been put 1=1 3=3
{size=2, capacity=2}
-------------------
[null]
[3]-->[1]
-------------------
Notice: Has been put 1=1 3=3, 2=2, 4=4
{size=4, capacity=8}
-------------------
[null]
[1]
[2]
[3]
[4]
[null]
[null]
[null]
-------------------
Notice: get(3): 3
Notice: get(5): null