HashMap源码解析
HashMap的底层数据结构在1.8版本之前数组和链表,1.8之后加入红黑树,
当插入键值对时,对key进行哈希运算,将哈希值映射到数组下标中的链表尾端,当链表长度达到链表最大值,则将链表转为红黑树,节省插入和删除时间
继承
HashMap继承于AbstractMap类,并实现了Map、Cloneable、Serializable,说明他能够实现克隆和序列化
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
}
属性
- 初始容量,必须是2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 最大容量,必须是2的幂
static final int MAXIMUM_CAPACITY = 1 << 30;
- 负载因子,默认为0.75,即容量达到75%就扩充容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
- 树阈值,超出该阈值链表变红黑树,链表长度超过8就转为红黑树
static final int TREEIFY_THRESHOLD = 8;
- 非树阈值,低于该阈值红黑树变链表
static final int UNTREEIFY_THRESHOLD = 6;
- 最小树容量
static final int MIN_TREEIFY_CAPACITY = 64;
- 存放的数组
transient Node<K,V>[] table;
- 存放kv的数量
transient int size;
构造函数
点击查看代码
//不带参数,采用默认负载因子
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
// all other fields defaulted
}
//设置初始容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//传入K,V,用到putMapEntries方法
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
//传入初始容量和负载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
静态方法
put(k,v)
putVal()
hash()//扰动函数
get
containsKey()
containsValue()
keySet()
values()
entrySet()
getOrDefault()
取模过程
在put新的键值对时,需要讲做过hash的key映射到对应数组下标中,映射过程没有采用直接取模的方法,而是采用二进制与的方法,即hash%n = (n-1)&hash
,但此等式只在n为2的幂次方才成立,因此hashmap的长度n一定为2的幂次方。
哈希过程
由于取模过程采用hash%n = (n-1)&hash
,hashmap长度n为16,此时进行二进制与运算时,hash值的高位不参与运算,这增加了hash冲突的概率,为了利用高位hash值,在计算hash的过程中,首先调用key值本身的hashcode方法,然后将高16位与低16位进行二进制异或的二次哈希,从而获取最终的哈希值,此时再进行取模就能够同时利用hash的高位和低位值,减少hash冲突的概率。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
扩容过程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理