首页 > 技术文章 > 哈希表底层深入

chaeyeon 2017-06-23 09:43 原文


图片

图片



1.哈希表的底层结构?
Java中的HashSet和HsahMap用的就是哈希表

数据结构决定了数据的检索,维护的效率。数组的检索效率高,增删元素的效率低。而链表的增删元素的效率高,检索元素的效率低。而哈希表结合了他们的优点。检索与增删效率都高。哈希表的底层结构就是一个数组,数组的长度即哈希表的长度,数组中的每个空间(也叫桶)存放的是一条链表,链表中的每个节点用来存放元素。即一个数组的每个数组元素是一条链表,链表的每个节点存放元素,可以将数组的每个元素看做桶,桶里面可以有多个元素。桶里元素直接的数据结构是链表。


2.哈希表如何检索元素?

哈希表首先根据要存放的元素的key得到hash值,将这个hash值经过"特殊计算的"结果,作为数组的下标(定位桶),进而确定将要遍历的链表。就不用遍历所有的链表,所以检索效率高。


3.哈希表如何增删元素?

首先一个哈希表的默认初始长度为16,即数组有16个空间(并非指能放16个元素)。默认加载因子为0.75。当向哈希表中添加元素的时候先判断是否要扩容,如果当前元素的总个数大于16*0.75那么要进行扩容,新的容量为原来的2倍。

在向哈希表中添加元素时首先通过要添加的元素的key的hash值,经过特殊算法计算的值作为下标,确定这个元素要放到数组中的那个桶里,然后遍历这个桶里的链表中的每个元素,与key进行equals判断如果equals返回false说明这个元素在哈希表中不存在则插入。如果返回true则不插入。

4.如何计算下标值?

通过要插入的元素的key调用hashcode方法得到哈希值,并与哈希表的长度减一进行与运算得到数组下标。
公式:hash(key)&table.length-1
由于哈希表表长一定是2的指数,即table.length-1的结果的二进制一定全部是1(比如16-1)与hash(key)进行与运算,即hash(key)最高位全为0,即求hash(key)与表长的余数,得到的就是下面的公式。
hash(key)%(table.length)。

这就可以解释当表长为16,元素个数不超过16*0.75时,插入元素就会根据要插入元素的key的的hashcode值与表长取余得到下标,这样在不超过表长的前提下,元素就会散乱的分布在这16不同的桶里。而决定链表的长度主要由加载因子决定。即使hashcode值不同但与表长进行取余,得到的下标会相同,这样就会散乱的分布在16个桶里

hash()

static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }

hash值找到对应索引

static int indexFor(int h, int length) { return h & (length-1); }

HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,这也是HashMap对Hashtable的一个改进。

length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率。

说明:length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间。



5.哈希表中的单链表的稀疏程度决定因素?

单链表的稀疏程度决定检索效率高低,而加载因子可以决定元素的个数,进而决定单链表的稀疏程度,哈希初始初始容量越大,加载因子越小,而实际存放的元素越少,单链表越稀疏越接近于数组。检索效率越高,但是越接近于数组,增钱效率就降低了,所以加载因子的大小一定要根据实际需求。

6.hashcode怎么写?
由哈希表在插入元素的时候会调用对象的hashcode方法和equals方法,因此重写十分必要。
原则:
两个对象的equals返回true,那么他们的hashcode一定要相等。

两个对象的hashcode相同,他们的equals不一定返回true。
 

推荐阅读