模仿JDK1.7版本,手写一个迷你版的HashMap,实现HashMap的部分功能
难点分析:
一、Hash碰撞问题
当两次计算的hash取模值相等的情况下,会出现两种情况。
1、两次key完全相等,此时需要做的是修改节点中的value值。
2、两次key不等,此时就在向该链表头节点添加新的Node对象
二、数组扩容问题
当Hash碰撞出现次数过多时,会造成链表过长,从而影响运行效率,此时需要进行数组扩容。
解决方法是,在每次添加数据时进行一次扩容判断,当实际容量超过阈值时,就进行扩容。
扩容基本步骤为:
1.新建扩容两倍后的table数组
2.重新计算hash索引
3.将table进行替换
package com.hashmap;
/**
* 模仿JDK1.7自定义HashMap
*
* @author 李福涛
*
*/
public class MyHashMap<K, V> {
// Map的实际容量
private int size;
// Map的初始容量
private int capacity = 1 << 2; // aka 16
// 默认负载因子的大小
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 存放map的节点链表
private Node<K, V>[] table;
// 构造函数
public MyHashMap() {
}
// 向map插入数据
public void put(K key, V value) {
// 1.判断table是否为空
if (table == null) {
table = new Node[capacity];
}
// 2.判断是否需要扩容
// 实际存储大小>负载因子*初始容量时 -> 0.75*16=12时,需要扩容
if (size > (DEFAULT_LOAD_FACTOR * capacity)) {
// 需要对table数组进行扩容
resize();
}
// 3.插入数据
// 获得key的hash取模后的值,该值就是链表数组对应的下标
int hash = key.hashCode() % table.length;
// 获得数组中该下标的链表
Node<K, V> node = table[hash];
// 如果该链表为空,则需要新建一个节点
if (node == null) {
// 没有发生hash碰撞
table[hash] = new Node<K, V>(key, value, null);
size++;
} else {
// 发生hash冲突
// 否则遍历该链表,查看是否有相同的key
while (node != null) {
// 判断该节点的key值是否等于新插入的key
if (node.key.equals(key) || node.key == key) {
// 如果发生了hash碰撞,直接修改该值
node.value = value;
return;
} else {
// 否则获取下一节点
node = node.next;
}
}
// 如果遍历完仍然没有相同的key,则在该链表头位置添加节点
table[hash] = new Node<K, V>(key, value, table[hash].next);
size++;
}
}
// 通过key获取值
public V get(K key) {
// 获得key的hash取模后的值,该值就是链表数组对应的下标
int hash = key.hashCode() % table.length;
// 获得该下标的链表
Node<K, V> node = table[hash];
while (node != null) {
// 判断该节点的key值是否等于要查找的key
if (node.key.equals(key)) {
return node.value;
} else {
// 否则获取下一节点
node = node.next;
}
}
return null;
}
// 对table数组进行扩容
private void resize() {
// 1.新建扩容两倍后的table数组
capacity = capacity << 1;
Node<K, V>[] newTable = new Node[capacity];
// 2.重新计算hash索引
// 遍历需要扩容的table
for (int i = 0; i < table.length; i++) {
// 获取table上的节点的信息
Node<K, V> oldNode = table[i];
// 将oldNode上的值添加至newTable中
putToTable(oldNode, newTable);
}
// 3.将table进行替换
table = newTable;
}
// 将oldNode的值放入newTable中
private void putToTable(Node<K, V> oldNode, Node<K, V>[] newTable) {
while (oldNode != null) {
// 计算新的hash值
int newHash = oldNode.key.hashCode() % newTable.length;
// 获得该hash值节点
Node<K, V> node = newTable[newHash];
// 判断节点是否为空
if (node == null) {
// 没有发生hash碰撞
newTable[newHash] = new Node<K, V>(oldNode.key, oldNode.value, null);
} else {
// 又发生hash碰撞
// 此时发生hash碰撞,其原因不会时key值相同而产生,故直接添加节点
newTable[newHash] = new Node<K, V>(oldNode.key, oldNode.value, newTable[newHash].next);
}
oldNode = oldNode.next;
}
}
// 打印所有元素
public void print() {
for (int i = 0; i < table.length; i++) {
Node<K, V> node = table[i];
while (node != null) {
System.out.println("key:" + node.key + "||value:" + node.value + " ");
node = node.next;
}
}
}
class Node<K, V> {
// 存放key值
K key;
// 存放Value值
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 static void main(String[] args) {
MyHashMap<String, String> myHashMap = new MyHashMap<String, String>();
myHashMap.put("a", "aa");
myHashMap.put("b", "bb");
myHashMap.put("c", "bb");
myHashMap.put("d", "bb");
myHashMap.put("e", "bb");
myHashMap.put("f", "bb");
myHashMap.put("g", "bb");
myHashMap.put("a", "bb");
myHashMap.print();
System.out.println("a:" + myHashMap.get("a"));
System.out.println("size:" + myHashMap.size);
}
}