面试题:哈希表采用何种算法计算索引?还有哪些算法可以计算出索引? 答:hashCode计算hash值,结合数组长度进行计算,具体算法有 1.无符号右移(>>>) 2.按位异或(^) 3.按位与(&) 备选(效率较低) 1.平方取中 2.取余 3.伪随机数
如果根据hashCode计算得到的hash根据算法算出来的索引处存在数据,则比较两者的hash,如果不一样,则在此索引空间新建一个Node将键值对存储,并将Node放到该索引空间中,与之前存在的数据构成一个链表(拉链法)如果索引一样,hash也一样,则发生哈希碰撞,那么会调用类的成员方法equals比较两个键是否一样,如果一样,则使用后者的值覆盖;如果不一样,则继续依次比较该索引空间上的链表上的键,如果都不相等,如第3步,拉链处理 完整过程如下图:面试题:HashMap的容量为什么是2的n次幂? 在进行hash运算时使用位与(&)方式计算索引,公式hashCode&(length-1),2的n次幂减1,进行位与运算后可以保留低位,最大程度上计算出的索引的均匀性,也就可以最大程度上保证数组中各索引空间的均匀分布,避免造成空间浪费,也可以避免过多数据插入时发生hash碰撞,减少链表、红黑树长度等。总之,可以最大化的提高效率。 上面讨论的是位与(&)运算计算索引的方式,如果是取余:hashCode%length,则不需要考虑数组长度为2的n次幂,但是这种算法开销比较大,效率不高。
面试题:如果用户创建HashMap的时候输入的容量不是2的n次幂,会发生什么现象? 会扩容到比输入容量稍大的2的n次幂的容量,具体做法是容量减1,然后进行右移1、2、4、8、16,每一次右移会执行或运算,得出的结果再进行下一次的右移、或运算,直到hashmap的最大容量2^30。这样可以保证即使用户输入不是2的2次幂,容量也被保证为2的n次幂。
面试题:如果用户创建HashMap的时候输入的容量不是2的n次幂,会发生什么现象? 会扩容到比输入容量稍大的2的n次幂的容量,具体做法是容量减1,然后进行右移1、2、4、8、16,每一次右移会执行或运算,得出的结果再进行下一次的右移、或运算,直到hashmap的最大容量2^30。这样可以保证即使用户输入不是2的2次幂,容量也被保证为2的n次幂。
面试题:为什么链表长度大于等于8才转为红黑树,小于6又转化为链表的结构? 根据官方解释,根据统计学泊松分布的公式,假设在hashCode的离散性比较理想的情况,链表长度等于8的概率非常小,但是jdk无法保证用户计算hashCode的算法是能够保证理想的离散性的,因此基于空间和时间的考虑,选择了8这个数字作为threshold。 另外一种通俗的理解是如果长度为8,红黑树的平均查找长度为log(8)=3,链表的平均查找长度为n/2=4,对数开始比多项式效率高。如果链表长度为6,红黑树的平均查找长度为log(8)=2.6,链表的平均查找长度为n/2=3,虽然速度也很快,但是红黑树需要转化为树和生成树的时间并不会太短,因此综合考虑会选择8作为threshold,其实这也是泊松分布的具体表现。
面试题:为什么负载因子为0.75? 负载因子决定HashMap中node数量达到整体的一定情况进行扩容,大量测试得出的结论0.75是一个折中、高效的选择。
面试题:HashTable和HashMap的区别? HashTable不允许键为null,原因是HashTable在进行push操作的时候会直接调用key的hashCode方法,如果为null会报空指针异常,HashMap中对key为null的情况会把0的结果返回。
面试题:为什么进行hashCode调用完后还需要右移16位,取异或的结果? 避免高位变化很大,低位变化很小的情况,在这种情况下很容易导致最后计算出的索引是大概率相同的,大大增加hash碰撞的概率。
说明:根据阿里手册,不建议使用,因为迭代两次。keySet获取Iterator一次,还有通过get又迭代一次,降低性能
jdk8以后使用Map集合的默认方法 import java.io.IOException; import java.util.HashMap; import java.util.Map; public class Test { public static void main(String[] args) throws IOException { Map<Integer, Integer> map = new HashMap<Integer, Integer>(); map.put(1, 10); map.put(2, 20); map.forEach((k, v) -> System.out.println("key: " + k + " value:" + v)); } }