MyHashMap

此章节是仿照已有的集合类自己实现一个,在保证原有类的精髓之下,尽可能的简化,只抽取主干方法进行实现

1 概述

对于​​HashMap​​的精髓我简单概述一下:

  • 数组长度设置为2的次幂,这个有两个好处:
  • 一是​​hash%length​​计算具体位置时可以用​​hash&(length-1)​​代替
  • 二是数组进行扩容时,一个链表上面的数据要么在原位置,要么在原位置+​​oldlength​
  • 使用​​tableSizeFor​​这个函数即得到一个二的次幂长度,具体做法是使用位运算
  • 使用扰动函数:​​hash = hash ^ hash>>16​​来进行hash值计算,使得碰撞更加均匀
  • 使用一个阈值,让使用者自定义空间换事件效率
  • 当链表长度大于8时,链表转换成红黑树(这个我没有实现)

1 源代码

ppackage com.aer.util;

import java.util.HashMap;

/**
* @author:"aer"
* @date:2021/8/25 21:26
* @description: 自己实现一个HashMap,只抽取主干方法进行实现
*/
public class MyHashMap<K,V> {

/**
* 用于存储元素的节点
* @param <K>
* @param <V>
*/
static class Node<K,V> {
final int hash;
final K key;
V value;
Node next;


public Node(int hash, K key, V value, Node next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}

}

/**
* 默认数组长度
*/
static public int DEFAUL_LENGTH = 16;

/**
* 默认负载因子
*/
static public float DEFAULT_LOADFACTOR = 0.75f;


/**
* table的长度
*/
private int length;

/**
* table中节点的数量
*/
private int size;

/**
* 负载因子
*/
private float loadFactor;

/**
* 存储节点的数组
*/
private Node[] tables;

public MyHashMap(){
this.length = DEFAUL_LENGTH;
this.loadFactor = DEFAULT_LOADFACTOR;
}

public MyHashMap(int length){
this(length,DEFAULT_LOADFACTOR);
}

public MyHashMap(int length, float loadFactor) {
this.length = tableSizeFor(length);
this.loadFactor = loadFactor;
}


/**
*
* @param n 输入参数
* @return 返回大于n的最小二次幂整数
*/
static public int tableSizeFor(int n) {
n = n - 1;
n = n | n>>>1;
n = n | n>>>2;
n = n | n>>>4;
n = n | n>>>8;
n = n | n>>>16;
return n + 1;

}

/**
* hash函数
* @param key
* @return
*/
public int hash(Object key){
if(key == null) return 0;
int code = key.hashCode();
// 扰动函数 前16位与后16位异或
return code ^ code>>>16;
}

/**
* 放置元素
* @param key
* @param value
*/
public void put(K key,V value){
//1. 如果table为空就初始化
if (tables == null){
tables = new Node[this.length];
}

//2. 获取key的hash值并获取对应的位置
int hash = hash(key);
int index = hash&(this.length-1);
Node p = tables[index];


//3.1 如果为空就直接放入
if(p == null){ // 如果对应为空就直接插入
Node<K, V> node = new Node<>(hash, key, value, null);
tables[index] = node;
}
else if(p.hash == hash && (p.key == key || (p.key != null && p.key.equals(key)))){
//更新旧值
p.value = value;
}
//3.2 出现冲突就拉链法
else{

Node q = p.next;
while (q != null){
if(q.hash == hash && (q.key == key ||(p.key != null && p.key.equals(key)))){
q.value = value;
break;
}
p = q;
q = q.next;
}
// 在链表尾部插入
Node<K, V> node = new Node<>(hash, key, value, null);
p.next = node;
}

//3.3 数目超过一定限度就进行扩容
size++;
if(size > length*loadFactor){
resize();
}
}

/**
* 扩容
*/
public void resize(){
//1.将新表扩大一倍
Node[] tab = this.tables;
int n = tab.length;
this.length = this.length * 2;
Node[] newTab = new Node[this.length];

//2.将数据进行搬迁
for (int i = 0; i < tab.length; i++) {
Node node = tab[i];
if(node == null) continue;
if(node.next == null) newTab[node.hash & (this.length - 1)] = node;

Node p = node;
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
while (p != null){
if((p.hash & n) == 0){ //原位置
if(loTail != null){
loTail.next = p;
}else{
loHead = p;
}
loTail = p;
}else{ //原位置 + 二次幂
if(hiTail != null){
hiTail.next = p;
}else{
hiHead = p;
}
hiTail = p;
}
p = p.next;
}
newTab[i] = loHead;
newTab[i+n] = hiHead;
}

tables = newTab;
}

/**
* 获取容量里面的元素
* @param key
* @return
*/
public V get(Object key){
//1.获取key对应的位置
int hash = hash(key);
int index = hash&(this.length-1);
Node p,q;
// 2. 获取对应位置的value
if(tables != null && (p = tables[index]) != null){
//2.1 链表头部
if(hash == p.hash && (p.key == key || (p.key != null && p.key.equals(key)))){ //判断key是否相等
return (V)p.value;
}
//2.2 去链表中找
if(p.next != null){
for(q = p.next; q != null; q = q.next){
if(hash == q.hash && (q.key == key || (q.key != null && q.key.equals(key)))){
return (V)q.value;
}
}
}

}
//找不到数据就返回null
return null;
}
}