目录
题外话
正题
哈希表
哈希碰撞
HashMap底层实现
小结
题外话
又水了两天,怪我,在宿舍确实没什么状态,是时候调整调整了
正题
今天直接讲解HashMap底层实现
哈希表
哈希表又称散列表
是数组和单向链表的结合体
如下图
而哈希表存放元素机制是靠哈希函数解析关键字,使其转变为哈希值
然后再由这个哈希值和整个数组长度求模运算,放入求模算出对应的数组下标中
哈希碰撞
输入两个不同的值,经过哈希函数解析之后,哈希值模完数组长度是一样的
如下图:
当出现哈希碰撞时,我们就需要利用单向链表,将其放入同一下标的链表当中,这样也就解决了哈希碰撞
而且单向链表的插入和修改操作时间复杂度很小,只需要修改结点next域即可
大家可以想象一下,如果对数组进行增删时,我们普遍需要让数组一些元素往后移或者往前移,时间复杂度更高
HashMap底层实现
下面我们分两组代码让大家明白HashMap底层如何实现,我们这里先将key和value默认为int类型
public class HashMapBottom{
class Node
{
/*
创建key,value和next
*/
int key;
int value;
Node next;
//提供构造方法
public Node(int key, int value) {
this.key = key;
this.value = value;
}
/**
*构造一个结点对象
* @param key 键
* @param value 值
* @param next 指向下一个结点
*/
}
//负载因子为0.75
private final float loadFactor=0.75f;
/*
哈希表
*/
private Node[] table;
/*
记录哈希表元素数量
*/
private int size;
/*
初始化容量
*/
public HashMapBottom()
{
this.table=new Node[16];
}
public int size()
{
return size;
}
public void put(int key,int value)
{
//获取key模数组长度
int index=key%table.length;
//将index放入数组中,cur获取当前数组元素
Node cur=table[index];
//如果cur获得元素不为空
while (cur!=null)
{
//判断cur取出元素是否和key相等,相等则说明重复了
if (cur.key==key)
{
//如果相等则更新cur的value值
cur.value=value;
return;
}
//如果cur.key等于key,说明没有重复,插入到后面位置
cur=cur.next;
}
//如果cur为空,则创建结点存放当前元素
Node node=new Node(key,value);
node.next=table[index];
table[index]=node;
size++;
if (doloadFactor()>loadFactor)
{
resize();
}
}
private void resize()
{
//将table数组进行二倍扩容
Node[] newTable=new Node[2*table.length];
for (int i=0;i<table.length;i++)
{
//将table数组中元素拿出
Node cur=table[i];
//如果取出元素不为空
while (cur!=null)
{
//先将cur下一个结点放入tmp中
Node tmp=cur.next;
//将cur.key模newTable.length算出新的值
int newIndex=cur.key%newTable.length;
//将cur放入扩容数组中
newTable[newIndex]=cur;
//将之前cur保存的下一个结点传入cur继续循环
cur=tmp;
}
}
//全部放入扩容数组中之后,将扩容数组给到之前的数组
table=newTable;
}
private float doloadFactor()
{
return size*1.0f/table.length;
}
public int get(int key)
{
//先找到key在数组的下标
int index=key%table.length;
//将数组下标元素赋值cur
Node cur=table[index];
//当cur不为空
while (cur!=null)
{
//如果cur.key==key说明找到了,返回cur的value即可
if (cur.key==key)
{
return cur.value;
}
//如果不等于则进入下一个结点继续找
cur=cur.next;
}
//如果遍历完成全部结点则返回-1
return -1;
}
}
我们如果使用的是基本数据类型包装类,或者String类的话
里面是重写过HashCode()和equals()方法的,所以我们使用的时候不需要过于担心
但是当我们自己创建类例如Student或者User之类的类
我们一定要重写HashCode()和equals()方法
比如我们在HashMap创建对象中调用put方法,添加了Student类,我们如果不重写上面两个方法
我们就没法比较,也没法哈希碰撞,如果put两个key值一样的元素,没有重写方法,就会将这两个元素全部放入HashMap中
下面让我们稍微实现一下带泛型的底层代码
public class HashMapBottom02<K,V> {
//创建key和value还有next
class Node<K,V>{
K key;
V value;
Node<K,V> next;
public Node(K key,V value)
{
this.key=key;
this.value=value;
}
}
Node<K,V>[] table;
private int size;
private static final float loadFactor=0.75f;
public HashMapBottom02()
{
table=new Node[16];
}
public void put(K key,V value) {
int hash=key.hashCode();
//获取key模数组长度
int index = hash % table.length;
//将index放入数组中,cur获取当前数组元素
Node<K,V> cur = table[index];
//如果cur获得元素不为空
while (cur != null) {
//判断cur取出元素是否和key相等,相等则说明重复了,这里比较一定要用equals,不然比较的是地址,而不是数值大小
if (cur.key.equals(key) ) {
//如果相等则更新cur的value值
cur.value = value;
return;
}
//如果cur.key等于key,说明没有重复,插入到后面位置
cur = cur.next;
}
//如果cur为空,则创建结点存放当前元素
Node<K,V> node = new Node(key, value);
node.next = table[index];
table[index] = node;
size++;
}
public V get(K key)
{
int hash=key.hashCode();
int index=hash%table.length;
Node<K,V> cur=table[index];
//当cur不为空
while (cur!=null)
{
//如果cur.key==key说明找到了,返回cur的value即可
if (cur.key.equals(key))
{
return cur.value;
}
//如果不等于则进入下一个结点继续找
cur=cur.next;
}
//如果遍历完成全部结点则返回-1
return null;
}
}
小结
明天直接更新算法题,最近堕落了,赶紧调整下状态
麻烦喜欢的家人们三连一下(点赞关注收藏!!!)