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);
    }

扩容过程



posted @   你也要来一颗长颈鹿吗  阅读(61)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示