在阅读JAVA中HashMap的源码时,以前数据结构学的都又回到脑海中,JAVA中的HashMap是"链表散列"的结构。有些收获点在此记录。
1. 基本结构Entry为 key-value,HashMap中的Entry<K,V>结构大致如下:
static class Entry<K,V> implements Map.Entry<K,V>{
final K key;
V value;
Entry<K,V> next;
final int hash;
.......
}
HashMap这里的Entry<K,V>相当于《数据结构》中节点node,Entry包含key(常量),value,hash(哈希值)和指向下一个Entry结构的引用。这么一来,一个Entry就相当于一个链表了。
2. HashMap本身的成员变量如下:
//The table, resized as necessary. Length MUST Always be a power of two.
transient Entry[] table;
(核心结构,table是一个Entry类型的数组,每一个元素都是Entry节点的链表。)
//The default initial capacity - MUST be a power of two.
static final int DEFAULT_INITIAL_CAPACITY = 16;
默认哈希表的初始容量,必须为2的多少次幂。为什么?正确答案:这是为了让元素在数组中分布均匀,而且为元素在数组中位置的计算速度优化做了铺垫。
考虑哈希表要添加一个元素,现在假设前提是某个Entry的hash值已经计算出来,那么我们都知道哈希表下一步就是根据这个hash值
把Entry放在table数组的某个位置。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大。我们看一下Java的牛人是怎么写的。Java源码中indexFor(int h, int length) 方法用来计算某Entry对象应该保存在 table 数组的哪个索引处,其代码如下:
static int indexFor(int h, int length) {
return h & (length-1);
}
这个h为Entry的哈希值,length为table数组的长度。
这个方法简单而又非常巧妙,它通过 h & (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的 n 次方,这里就是HashMap在速度上优化的一个点。在 HashMap 构造器中有如下代码:
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
这段代码保证初始化时HashMap的容量总是2的n次方,即底层数组的长度总是为2的n次方。
当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
这看上去很简单,其实比较有玄机的,我们举个例子来说明:
假设数组长度分别为15和16,优化后的hash码分别为8和9,那么&运算后的结果如下:
h & (table.length-1) hash table.length-1
8 & (15-1): 0100 & 1110 = 0100
9 & (15-1): 0101 & 1110 = 0100
--------------------------------------------------------------------------------------
8 & (16-1): 0100 & 1111 = 0100
9 & (16-1): 0101 & 1111 = 0101
从上面的例子中可以看出:当它们和15-1(1110)“与”的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到数组中的同一个位置上形成链表,那么查询的时候就需要遍历这个链 表,得到8或者9,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hash值会与15-1(1110)进行“与”,那么 最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。现在明白为什么MUST be a power of two 了吧。Java在这里充分考虑了速度的优化。
static final int MAXIMUM_CAPACITY = 1 << 30;
//The load factor used when none specified in constructor.
static final float DEFAULT_LOAD_FACTOR = 0.75f;
这个0.75是HashMap的负载因子,即数据结构做题的时候算的那个 “散列表的实际元素数目(n)/ 散列表的容量(m)”。
负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。
//The number of key-value mappings contained in this map.
transient int size;
size 用于记录Entry的总数
//The next size value at which to resize (capacity * load factor).
int threshold;
threshold为Entry数目的上限,为容量和负载因子的乘积。当size超过了threshold时,将table进行扩容。
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
modCount记录对HashMap结构性修改的次数,modCount声明为volatile,保证线程之间修改的可见性。我们知道 java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出 ConcurrentModificationException,这就是所谓fail-fast策略。
未完待续。。。
参考 http://beyond99.blog.51cto.com/1469451/429789
分享到:
相关推荐
哈希表是一种高效的数据结构,它通过特定的函数——哈希函数,将任意大小的键(key)映射到一个固定大小的数组中,从而实现快速的查找、插入和删除操作。这种数据结构的设计旨在解决在大量数据中查找特定元素的问题...
哈希表,也被称为散列表,是数据结构中一种高效的数据存储和检索工具。它通过哈希函数将数据的关键字映射到一个固定大小的数组中,使得在平均情况下,查找、插入和删除操作的时间复杂度可以达到O(1)。这种高效的性能...
哈希函数应尽可能地减少冲突,同时要考虑到内存效率和计算速度。开放地址法与链地址法各有优缺点,前者适用于小规模数据且冲突较少的情况,后者在处理大量冲突时表现更好。 哈希查找的时间复杂度理想情况下可以达到...
哈希查找数据结构实验报告 本实验报告的标题是 "哈希查找数据结构实验报告.pdf", 描述是 "哈希查找数据结构实验报告.pdf", 标签是 "考试"。该实验报告讲述了哈希表的造表和查找算法的实现。 第一部分:需求分析 ...
哈希工具是一种在IT行业中广泛使用的软件工具,其主要功能是计算文件或数据的哈希值。哈希值,也称为散列值或消息摘要,...同时,对于想要定制化哈希工具或者深入研究密码学的人来说,这样的源码无疑是一个宝贵的资源。
哈希表是一种高效的数据结构,它通过特定的算法——哈希函数,将任意大小的键(key)映射到一个固定大小的数组索引上,从而实现快速的查找、插入和删除操作。在这个"哈希表源代码"压缩包中,我们可以期待找到实现...
输入:待哈希数据序列 功能要求:输出哈希方法和解决冲突的方法(文字输出),输出哈希表
哈希表,也被称为散列表,是计算机科学中一种非常重要的数据结构,它提供了一种高效的数据存储和检索方法。哈希表通过将键(Key)映射到一个索引位置来实现快速访问,这个索引位置是通过哈希函数计算得出的。哈希...
哈希表,也被称为散列表,是数据结构中一种高效的数据存储方式,它通过特定的哈希函数将关键字映射到一个固定大小的数组中,从而实现快速的查找、插入和删除操作。在计算机科学中,哈希表的性能优势在于它的平均时间...
////采用除留余数法定义哈希表,哈希表长度为10,哈希函数为H(key)=key%13。产生冲突时采用线性探测法实现下面要求的功能。 ////(1)初始化哈希表,置空哈希表 ////(2)在哈希表中查找元素 ////(3)在哈希表中...
【哈希函数与字符串问题】 哈希函数在信息技术领域中扮演着重要的角色,尤其在处理字符串问题时,它的高效性和唯一性使得它成为解决复杂问题的利器。在这个特定的问题中,我们面临的是如何利用哈希值来解决字谜问题...
**感知哈希算法详解与Matlab实现** 感知哈希(Perceptual Hashing,简称pHash)是一种图像识别技术,用于判断两张图片是否相似。它通过模拟人类视觉系统的感知特性,将图片转换成一个简短的哈希值,进而进行比较。...
哈希排序是一种基于哈希表的数据结构和算法,用于高效地对数据进行排序。哈希表,也称为散列表,是通过哈希函数将数据映射到数组索引上,从而实现快速查找、插入和删除操作的数据结构。在哈希排序中,我们利用哈希...
首先,我们要理解哈希表(Hash Table),它是基于哈希函数实现的一种高效的数据结构。哈希表通过哈希函数将键(Key)转换为哈希值,然后将键值对存储到对应哈希值的槽位上。C#中的`Dictionary, TValue>`类就是一种...
哈希表是一种高效的数据结构,它通过特定的函数——哈希函数,将任意大小的键(key)映射到一个固定大小的数组中,从而实现快速查找、插入和删除操作。在“数据结构哈希表设计实验报告”中,我们可能会涉及到以下几...
最小完美哈希函数是一种特殊的哈希函数设计,它在计算机科学中用于数据结构的高效存储和查找。在本文中,我们将深入探讨最小完美哈希函数的概念、其在实际应用中的重要性,以及如何通过源代码实现O(1)查找时间和1的...
哈希表课程设计数据结构实验报告——哈希表设计 针对某个集体中的人名设计一个哈希表,使得平均查找长度不超过R,完成相应的建立和查表程序. 1.2 人名为汉语拼音形式,最长不超过18个字符(如:庄双双 ...
在VB6(Visual Basic 6)编程环境中,获取文件的哈希值是一项常见的任务,它通常用于验证文件的完整性或比较文件是否相同。哈希值是通过特定算法(如MD5、SHA-1、SHA-256等)对文件内容进行计算得到的一段固定长度的...
哈希查找: 1、 哈希表类的哈希函数采用除留余数法哈希函数; 2、 解决哈希冲突的函数采用开放定址法中的线性探察法。 3、 建立一个由10个数据元素组成的集合; 4、 测试哈希表长度m=13和m=11两种情况下的哈希表,并...