1.基本概念
定义与特点
HashMap是一个散列表,存储键值对(key,value),每一个键对应一个唯一的值,并且允许null作为键或值。
HashMap是不是线程安全的,当多个线程同时访问和修改同一个 HashMap 时,可能会导致数据不一致或其他并发问题。
数据结构
JDK1.7:数组+链表
JDK1.8:数组+链表+红黑树 链表过长(长度>8)时,将链表转化为红黑树
本篇文章中使用数组+链表实现HashMap,当发生哈希冲突时,元素被存在链表中
//保存链表,MNode是自定义的节点类
private LinkedList<MNode>[] linkArr;
2.工作原理
哈希函数
hashmap基于哈希表实现,他通过计算key的哈希值来确定元素应该存储的位置
public int hash(Object key) {
return key.hashCode() % len;
}
哈希冲突
不同的键可能会产生相同的哈希值,这种情况称为哈希冲突。
当插入新的键值对时,如果发生哈希冲突(对应的数组位置已有元素)则会遍历链表:检查是否存在相同的键:
如果找到相同的键,新的键值对覆盖旧的键值对。
如果没有找到相同的键,则会将新的键值对插入到链表的末尾。
3.常用方法
put
- 判断是否需要扩容:如果当前size>=数组长度*负载因子(0.75),resize()扩容
- 根据当前key值计算哈希值,获得存储位置,取出对应链表
- 判断是否发生哈希冲突,遍历检查重复键。如果有重复键,更新value值,return
- 添加新节点到链表
如果key值为null,则手动将key值赋为0,以便存储元素。
public void put(Object key, Object value) {
//判断是否需要扩容
if (size >= len * LOAD_FACTOR)
resize();
//根据当前key计算存储位置
int index = hash(key);
//取出对应链表
LinkedList<MNode> list = linkArr[index];
//遍历检查重复键
for(MNode node : list) {
if(node.key.equals(key)) {
node.value = value;
return;
}
}
//添加新节点到链表
//保存null key
if (key == null) {
linkArr[index].add(new MNode(0,value));
}
else {
linkArr[index].add(new MNode(key,value));
}
size++;
}
get
通过哈希值获取位置,遍历相应链表
如果能找到这个key,返回对应的value,找不到则返回null
public Object get(Object key) {
int index = hash(key);
for (MNode node : linkArr[index]) {
//如果能在哈希表找到这个key
if (node.key.equals(key))
return node.value;
}
return null;
}
remove
remove方法实现逻辑与get方法相似
先通过哈希值获取位置,遍历相应链表
如果能找到这个key,保存对应的value值,删除当前位置的元素,返回保存的value
如果没找到,返回null
public Object remove(Object key) {
int index = hash(key);
for (MNode node : linkArr[index]) {
if (node.key.equals(key)) {
Object value = node.value;
linkArr[index].remove(node);
size--;
return value;
}
}
return null;
}
hash
hash() 方法用于确定对象在哈希表中的存储位置。
哈希表通过将对象的 hashCode 值映射到一个数组的索引上,以此来快速定位和存储对象,从而提高数据的存储和查找效率。
//哈希函数
public int hash(Object key) {
return key.hashCode() % len;
}
4.扩容机制
resize
- 数组长度变为原长度的两倍,创建新的链表数组
- 遍历初始化新数组
- 重新计算哈希值,将所有节点加入新数组
- 将新数组赋给原数组
public void resize() {
len *= 2;
LinkedList<MNode>[] newLinkArr = new LinkedList[len];
//初始化新数组
for (int i = 0; i < newLinkArr.length; i++) {
newLinkArr[i] = new LinkedList();
}
//重新哈希所有节点
for (LinkedList<MNode> list : linkArr) {
for (MNode node:list) {
int newIndex = hash(node.key);
newLinkArr[newIndex].add(node);
}
}
linkArr = newLinkArr;
}
5.代码实现
import java.util.LinkedList;
public class MhashMap {
private LinkedList<MNode>[] linkArr;//保存链表
private static int len = 16;
private static final float LOAD_FACTOR = 0.75f;
public int size;
public MhashMap(int size) {
linkArr = new LinkedList[size];
//初始化数组
for (int i = 0; i < linkArr.length; i++) {
linkArr[i] = new LinkedList<MNode>();
}
}
public MhashMap() {
this(len);
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void put(Object key, Object value) {
//判断是否需要扩容
if (size >= len * LOAD_FACTOR)
resize();
//根据当前key计算存储位置
int index = hash(key);
//取出对应链表
LinkedList<MNode> list = linkArr[index];
//遍历检查重复键
for(MNode node : list) {
if(node.key.equals(key)) {
node.value = value;
return;
}
}
//添加新节点到链表
//保存null key
if (key == null) {
linkArr[index].add(new MNode(0,value));
}
else {
linkArr[index].add(new MNode(key,value));
}
size++;
}
//哈希函数
public int hash(Object key) {
return key.hashCode() % len;
}
public LinkedList[] getLinkArr() {
return linkArr;
}
public Object get(Object key) {
int index = hash(key);
for (MNode node : linkArr[index]) {
//如果能在哈希表找到这个key
if (node.key.equals(key))
return node.value;
}
return null;
}
public void resize() {
len *= 2;
LinkedList<MNode>[] newLinkArr = new LinkedList[len];
//初始化新数组
for (int i = 0; i < newLinkArr.length; i++) {
newLinkArr[i] = new LinkedList();
}
//重新哈希所有节点
for (LinkedList<MNode> list : linkArr) {
for (MNode node:list) {
int newIndex = hash(node.key);
newLinkArr[newIndex].add(node);
}
}
linkArr = newLinkArr;
}
public Object remove(Object key) {
int index = hash(key);
for (MNode node : linkArr[index]) {
if (node.key.equals(key)) {
Object value = node.value;
linkArr[index].remove(node);
size--;
return value;
}
}
return null;
}
class MNode {
Object key;
Object value;
public MNode(Object key, Object value) {
this.key = key;
this.value = value;
}
}
总结
HashMap以数组 + 链表存储键值对,允许 null 键值且非线程安全。哈希表的设计将复杂的数据结构简化为数组索引,极大地提升了数据操作的效率。在处理哈希冲突时,链表的引入让数据存储更加灵活。