1.4 get()方法的工作原理?
根据key的hashcodejinxinghash,得到buckets的值,如果存在碰撞,则利用equals方法在链表中产找
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表中查找对应的节点。
1.5 HashMap中hash函数怎么是是实现的?还有哪些 hash 的实现方式?
1. 对key的hashCode做hash操作(高16bit不变,低16bit和高16bit做了一个异或);
2. h & (length-1); //通过位操作得到下标index。
还有数字分析法、平方取中法、分段叠加法、 除留余数法、 伪随机数法。
1.2 HashMap的原理,内部数据结构?
HashMap是基于hashing的原理,底层使用哈希表(数组 + 链表,后改为数组+红黑树,原因是因为链表太长,查询时间复杂度太高)实现。里边最重要的两个方法put、get,使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。
存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
1.HashMap原理,内部数据结构?
底层使用哈希表(数组加链表)来存储,链表过长会将链表转成红黑树,以实现在O(logn)时间复杂度内查找
2.讲一下HashMap中的put方法过程?
对key求哈希值然后计算下标
如果没有哈希碰撞则直接放入槽中
如果碰撞了以链表的形式链接到后面
如果链表长度超过阈值(默认阈值是8),就把链表转成红黑树
如果节点已存在就替换旧值
如果槽满了(容量*加载因子),就需要resize
4.HashMap如何解决冲突,讲一下扩容过程。如果一个值在原数组中,扩容后移动到了新数组,位置肯定改变了,如何定位到这个值在新数组中的位置?
将节点加到链表后,链表达到最大,变为红黑树
容量扩充为原来的两倍,然后对每个节点重新计算哈希值
这个值只可能在两个地方:一种是在原下标位置,另一种是在下标为<原下标+原容量>的位置
6.针对HashMap中某个Entry链太长,查找的时间复杂度可能达到O(n),如何优化?
将链表转为红黑树,JDK1.8已经实现,变为olog(n)
HashMap与HashTable区别
5.容量的初始值和增加方式都不一样:HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”;
6.添加key-value时的hash值算法不同:HashMap添加元素时,是使用自定义的哈希算法。Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。
1.9 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
扩容。这个过程也叫作rehashing,因为它重建内部数据结构,并调用hash方法找到新的bucket位置。
大致分两步:
1.扩容:容量扩充为原来的两倍(2 * table.length);
2.移动:对每个节点重新计算哈希值,重新计算每个元素在数组中的位置,将原来的元素移动到新的哈希表中。
补充:
loadFactor:加载因子。默认值DEFAULT_LOAD_FACTOR = 0.75f;
capacity:容量;
threshold:阈值=capacity*loadFactor。当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍(capacity*2);
size:HashMap的大小,它是HashMap保存的键值对的数量。
1.7 如果两个键的hashcode相同,你如何获取值对象?
重点在于理解hashCode()与equals()。
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。两个键的hashcode相同会产生碰撞,则利用key.equals()方法去链表或树(java1.8)中去查找对应的节点。