HashMap源码随笔
源码第一块:
概述:
Map 接口的基于哈希表的实现。此实现提供所有可选的映射操作,并允许 null 值和 null 键。(HashMap 类大致等同于 Hashtable,只不过它是不同步的,并且允许 null。此类不保证地图的顺序;特别是,它不保证订单会随着时间的推移保持不变。此实现为基本操作(get 和 put)提供恒定时间性能,假设哈希函数将元素正确地分散在存储桶中。对集合视图进行迭代所需的时间与 HashMap 实例的“容量”(存储桶数量)及其大小(键值映射数量)成正比。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载系数太低),这一点非常重要。HashMap 实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的存储桶数量,初始容量只是创建哈希表时的容量。负载因子是衡量哈希表在容量自动增加之前允许的满量。当哈希表中的条目数超过负载因子和当前容量的乘积时,将重新哈希表(即重新构建内部数据结构),以便哈希表的桶数大约是其两倍。作为一般规则,默认负载系数 (0.75) 在时间和空间成本之间提供了良好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在 HashMap 类的大多数操作中,包括 get 和 put)。在设置映射的初始容量时,应考虑映射中的预期条目数及其负载系数,以最大程度地减少重新散列操作的次数。如果初始容量大于最大条目数除以负载因子,则不会发生重新散列操作。如果要在 HashMap 实例中存储许多映射,则创建具有足够大容量的映射将允许更有效地存储映射,而不是让它根据需要执行自动重新散列以增加表。请注意,使用具有相同 hashCode() 的多个键肯定会降低任何哈希表的性能。为了改善影响,当键具有可比性时,此类可以使用键之间的比较顺序来帮助打破联系。请注意,此实现不是同步的。如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,则必须在外部同步该映射。(结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。这通常是通过在自然封装地图的某个对象上进行同步来实现的。如果不存在此类对象,则应使用 Collections.synchronizedMap 方法“包装”映射。这最好在创建时完成,以防止意外的不同步访问地图: Map m = Collections.synchronizedMap(new HashMap(...));此类的所有“集合视图方法”返回的迭代器都是快速失败的:如果在创建迭代器后的任何时间对映射进行结构修改,则迭代器将抛出 ConcurrentModificationException。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是冒着在未来不确定的时间出现任意的、非确定性行为的风险。请注意,无法保证迭代器的快速失效行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬性保证。快速失败迭代器会尽力而为地抛出 ConcurrentModificationException。因此,编写一个依赖于此异常的程序的正确性是错误的:迭代器的快速失败行为应该只用于检测错误。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}’
此映射通常充当 bined(桶)哈希表,但是当 bin 变得太大时,它们会转换为 TreeNodes 的 bin,每个 bin 的结构与 java.util.TreeMap 中的结构类似。大多数方法尝试使用普通的 bin,但在适用时中继到 TreeNode 方法(只需检查节点的实例)。TreeNodes 的 bin 可以像任何其他 bin 一样被遍历和使用,但在过度填充时还支持更快的查找。但是,由于正常使用的绝大多数箱不会过度填充,因此在表方法的过程中可能会延迟检查树箱是否存在。树 bin(即元素都是 TreeNodes 的 bin)主要由 hashCode 排序,但在平局的情况下,如果两个元素属于相同的“类 C 实现 Comparable
`/** * The default initial capacity - MUST be a power of two. * 默认容量大小为16,其必须为2的整数次幂 */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16` `/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. *最大容量,如果使用参数的任一构造函数隐式指定了较高的值,则使用该容量。必须是 2 的幂<= 1<<30。 * 规定了HashMap的最大容量大小为2的29次方,如果手动输入了其他值,则采用改值,但是该值必须为2的幂`` */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. * 负载因子,没什么好说的,网络上的文章已经写的很明白了 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. * 树化的阈值为8,当HashMap的容量大于64并且在一个bin上发生冲突,链的长度为8的时候,进行树化 * 该值必须大于 2,并且应至少为 8 */ static final int TREEIFY_THRESHOLD = 8; /** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. * 解除树化的阈值为6,与8之间空着一个7,防止单个元素增减的时候频繁进行树化与解除 * 该值必须小于树化阈值,并且最多为6 * */ static final int UNTREEIFY_THRESHOLD = 6; /** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. * 树化前置容量条件,当HashMap中的容量小于这个值的时候,达到了树化阈值,那么将会进行一次检定,如果容量 * 大于这个值,则进行树化,否则就进行扩容来减少Hash冲突 * 该值至少应该是4倍的树化阈值 */ static final int MIN_TREEIFY_CAPACITY = 64;`
接下来是一个内部类Node,是HashMap进行元素存储的基本单位之一,这里就不过多赘述。
`static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
接下来是一些对外的方法:
/** * 计算 key.hashCode() 并将 (XOR) 较高的哈希位传播到更低的位。由于该表使用 2 的幂掩码,因此仅以高于当前掩码的位数变化的哈希集将始终发生冲突。 * (在已知的例子中,有一组浮点键,它们在小表中保存连续的整数。因此,我们应用了一个转换 * ,将较高位的影响向下分散。在速度、实用性和比特扩展质量之间需要权衡。由于许多常 * 见的哈希集已经合理分布(因此不会从扩展中受益),并且由于我们使用树来处理 bin 中的大量冲突,因此我们只是以最便宜的方式对一些移位进行异或, * 以减少系统损失,并合并最高位的影响,否则由于表边界,这些位永远不会用于索引计算。 * 这里计算hash是对key的hashCode进行高16位与低16位的异或,从而使得hash值同时具有高16位与低16位的特征,从而减少Hash冲突 */ static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } /** * Returns x's Class if it is of the form "class C implements * Comparable<C>", else null. * 返回x的Class对象,当且仅当 X 是 X implement Comparable<X>,否则返回nul * 详细可以查看这篇博客 https://blog.csdn.net/qpzkobe/article/details/79533237 */ static Class<?> comparableClassFor(Object x) { // 首先 x 必须是 Comparable的实现类 if (x instanceof Comparable) { // 初始化 Class对象,type对象,参数化类型对象 Class<?> c; Type[] ts, as; Type t; ParameterizedType p; if ((c = x.getClass()) == String.class) // bypass checks return c; //返回实现接口信息的type数组 包含泛型信息 if ((ts = c.getGenericInterfaces()) != null) { for (int i = 0; i < ts.length; ++i) { //遍历数组,筛选出其中的参数化类型 if (((t = ts[i]) instanceof ParameterizedType) && // 获取其中去掉参数化类型的对象等于Comparable ((p = (ParameterizedType)t).getRawType() == Comparable.class) && // 以参数化类型返回其中的参数化列表,与getRawType刚好相反 (as = p.getActualTypeArguments()) != null && as.length == 1 && as[0] == c) // type arg is c return c; } } } return null; } /** * Returns k.compareTo(x) if x matches kc (k's screened comparable * class), else 0. * 如果 x 与 kc 匹配(k 筛选的可比类),则返回 k.compareTo(x),否则为 0。 */ @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable static int compareComparables(Class<?> kc, Object k, Object x) { return (x == null || x.getClass() != kc ? 0 : ((Comparable)k).compareTo(x)); } /** * Returns a power of two size for the given target capacity. * 返回大于 入参cap最小的2的整数次幂 * */ static final int tableSizeFor(int cap) { // 首先就是让cap减去1,否则如果cap本身就是2的整数次幂将得到一个错误的值 int n = cap - 1; //原理: int具有32bit, // 假设现在最高位的1是这样的:00100...将其与自身的无符号右移1位后的数字进行或运算,得到00110... n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; // 将其进行无符号右边移动1+2+4+8+16,会发生什么? 会发现其总共右边移动了31位,而且是无符号右移动 // 也就意味着在符号位不参与运算的情况下,该算法使得‘最高位的1右边的0全变成了1’ // 最后返回值再进行+1,就完成了这一次最近size的运算 // 当然返回值是有最大容量校验的 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端