取模运算在包括JAVA在内的大多数语言中的效率都十分低下,而当除数为2的N次方时,取模运算将退化为最简单的位运算,其效率明显提升(按照Bruce Eckel给出的数据,大约可以提升5~8倍) 。看看JDK中是如何实现的:
- static int indexFor(int h, int length) {
- return h & (length-1);
- }
- static int indexFor(int h, int length) {
- return h & (length-1);
- }
当key空间长度为2的N次方时,计算hashCode为h的元素的索引可以用简单的与操作来代替笨拙的取模操作!假设某个对象的hashCode为 35(二进制为100011),而hashMap采用默认的initialCapacity(16),那么indexFor计算所得结果将会是 100011 & 1111 = 11,即十进制的3,是不是恰好是35 Mod 16。
上面的方法有一个问题,就是它的计算结果仅有对象hashCode的低位决定,而高位被统统屏蔽了;以上面为例,19(10011)、 35(100011)、67(1000011)等就具有相同的结果。针对这个问题, Joshua Bloch采用了“防御性编程”的解决方法,在使用各对象的hashCode之前对其进行二次Hash,参看JDK中的源码:
- static int hash(Object x) {
- int h = x.hashCode();
- h += ~(h << 9);
- h ^= (h >>> 14);
- h += (h << 4);
- h ^= (h >>> 10);
- return h;
- }
- static int hash(Object x) {
- int h = x.hashCode();
- h += ~(h << 9);
- h ^= (h >>> 14);
- h += (h << 4);
- h ^= (h >>> 10);
- return h;
- }
采用这种旋转Hash函数的主要目的是让原有hashCode的高位信息也能被充分利用,且兼顾计算效率以及数据统计的特性,其具体的原理已超出了本文的领域。
加快Hash效率的另一个有效途径是编写良好的自定义对象的HashCode,String的实现采用了如下的计算方法:
- for (int i = 0; i < len; i++) {
- h = 31*h + val[off++];
- }
- hash = h;
- for (int i = 0; i < len; i++) {
- h = 31*h + val[off++];
- }
- hash = h;
这种方法HashCode的计算方法可能最早出现在Brian W. Kernighan和Dennis M. Ritchie的《The C Programming Language》中,被认为是性价比最高的算法(又被称为times33算法,因为C中乘数常量为33,JAVA中改为31),实际上,包括List在 内的大多数的对象都是用这种方法计算Hash值。
相关推荐
然而,当多个键映射到同一个数组索引时,会出现散列冲突,这时`HashMap`会使用链表或者红黑树来解决,这会影响查找效率。 **List (ArrayList vs LinkedList)** `List`接口有两个常见的实现类:`ArrayList`和`...
它使用数组和链表来存储键值对,并通过Hash函数来将键转换为索引。同时,我们还可以看到HashMap的 resize 操作,用于扩展HashMap的容量以适应不断增加的数据。 结论 在本文中,我们详细介绍了如何模拟Java的...
"学习笔记:三数组实现的HashMap"这篇博客文章可能讨论了一种非标准但有趣的HashMap实现,使用了三个数组来代替标准的哈希表结构。这里我们将深入探讨这种三数组实现HashMap的原理和可能的优势。 1. **基本概念**:...
假设数组长度为16(2的4次方),如两个键的哈希值分别为8和9,进行按位与操作后结果相同(均为0),这会导致哈希碰撞。因此,8和9会存放在数组的同一位置,形成链表。 然而,如果数组长度为非2的幂,如15(1111二...
它首先通过键(k)的哈希值计算出应存储的位置,这里使用了简单的取模运算`k.hashCode() % entries.length`。如果键为null,那么它将被存放在数组的索引0处。如果该位置上已经有元素存在,即发生了哈希冲突,新元素...
`(oldCap - 1)`和`(2 * oldCap - 1)`的低n位都是111...1,它们与`e.hash`的低n位进行按位与运算得到的值决定了元素在旧数组中的索引,而这个值在满足`(e.hash & oldCap) == 0`的条件下对新数组的索引计算也适用。...
例如,对于大小为16的数组,计算数组索引时使用`index = (n - 1) & hash`。 - 当负载因子超过0.75时,`HashMap`会自动扩容,新的容量为原来的两倍。这种扩容策略有助于保持良好的性能。 - **HashTable**: - 默认...
然而,“索引”这一概念通常与数组或列表关联,而在`HashMap`中,我们通过键(Key)来访问其对应的值(Value)。因此,理解如何获取`HashMap`中的“索引”,实际上是指如何有效地遍历`HashMap`以及获取其键值对。 #...
在Java中,HashMap的实现方式有多种,本文将介绍使用数组和链表的方式简单实现HashMap的增删改功能。 HashMap的数据结构 HashMap的数据结构主要由三个部分组成:数组、链表和红黑树。数组用于存储键值对,链表用于...
同时,在resize()方法中,我们还可以看到HashMap使用了 threshold这个字段来记录扩容阈值,该阈值是旧数组容量乘以负载因子的结果。 五、结论 HashMap的resize()方法是HashMap中最核心的方法之一,该方法负责扩容...
HashMap 之 put 方法源码解读 HashMap 是 Java 中一种常用的数据结构,用于存储键值对。其中,put 方法是 HashMap 中最重要的方法之一,负责将键值对存储到HashMap 中。在本文中,我们将对 HashMap 的 put 方法的...
HashMap 底层的数据结构主要是:数组 + 链表 + 红黑树。其中当链表的长度大于等于 8 时, 链表会转化成红黑树,...比如数组下标索引为 2 的位置就是一个链表,下标索引为 9 的位置对应的 就是红黑树,具体细节请看内容
HashMap的扩容机制在JDK 1.8中有优化,其容量大小必须为2的n次方,主要是为了保证在计算索引位置时,通过位运算可以实现快速定位。具体来说,通过哈希值与数组长度减一的结果进行位运算,可以得到一个更分散的索引...
在Java 8之后,如果链表长度超过一定阈值(通常是8),HashMap会将链表转换为红黑树以进一步提高查找效率。 在“全手写HashMap精简版Demo”中,我们可以看到以下关键部分: 1. **初始化**:创建一个固定大小的数组...
Java集合,HashMap底层实现和原理(1.7数组+链表与1.8+的数组+链表+红黑树) 在Java集合中,HashMap是一个常用的数据结构,它基于Map接口实现,元素以键值对的方式存储,并且允许使用null键和null值。由于key不允许...
* 在 JDK 1.8 中,HashMap 使用数组+链表+红黑树来存储键值对,链表过长时,会将其转换为红黑树,以提高查询效率。 5. HashMap 的线程安全问题 HashMap 在多线程环境下使用时,需要注意线程安全问题,否则可能会...
计算公式是`(数组长度-1)&hash`,这里的`&`操作符是按位与,相当于取模运算,但效率更高。这是因为HashMap的数组长度总是2的幂,所以`(length-1)`的结果是一个二进制数,所有位都是1,与`hash`进行按位与操作可以...
2. **哈希函数**:HashMap使用键对象的`hashCode()`方法生成哈希码,然后通过取模运算将哈希码映射到数组的索引位置。哈希函数的质量直接影响了冲突的可能性和性能。 3. **负载因子**:HashMap有一个名为`...
HashMap是Java中广泛使用的数据结构之一,它主要用于存储键值对(key-value pairs),具备快速存取的能力。本文将详细探讨HashMap的底层原理,包括其数据结构、存取实现以及与Hashtable的区别。 1. HashMap的数据...
2. 可空性:键和值都可以为null,但一个HashMap只能有一个键为null的条目。 3. 默认容量:16,负载因子0.75,当容量达到负载因子乘以当前容量时,会发生扩容。 四、HashMap面试题解析 1. HashMap的初始容量和扩容...