属性相关的问题
- HashMap作为一种数据结构,元素在put的过程中需要进行hash运算,目的是计算出该元素存放在hashMap中的具体位置。hash运算的过程其实就是对目标元素的Key进行hashcode,再对Map的容量进行取模,而JDK 的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map的容量一定得是2的幂。
- 作为默认容量,太大和太小都不合适,所以16就作为一个比较合适的经验值被采用了。
- 负载因子表示一个数组可以达到的最大的满的程度。这个值不宜太大,也不宜太小。如果太大, 就会有很高的哈希冲突的概率,会大大降低查询速度。如果太小, 那么频繁扩容,就会大大浪费空间。根据数学公式推算。这个值在log(2)的时候比较合理。
- HashMap的容量(capacity)有一个固定的要求,那就是一定是2的幂。所以,如果loadFactor是0.75的话,那么和capacity的乘积结果就可以是一个整数。
- 为什么建议设置HashMap的初始容量? 设置多少合适?
- 如果我们没有设置初始容量大小,随着元素的不断增加,HashMap会发生多次扩容,而HashMap中的扩容机制决定了每次扩容都需要重建hash表,是非常影响性能的。
- 当HashMap内部维护的哈希表的容量达到75%时(默认情况下),会触发rehash,而rehash的过程是比较耗费时间的。所以初始化容量建议设置成expectedSize/0.75 + 1。
// 正解, guava工具类
Map<String, String> map = Maps.newHashMapWithExpectedSize(7);
// 反例, JDK会默认帮我们计算一个比7大的2的幂, 但是并没有考虑到loadFactor这个因素。
Map<String, String> map = new HashMap(7);
- 链表与红黑树转换的临界值是多少?
当链表长度大于8且map容量大于 64 时, 为了查询效率会转换为红黑树结构; 当红黑树长度小于6时,会将红黑树结构转换成链表结构, 可以减少计算量, 提高查询效率. - 为什么数组长度要保证为2的幂次方呢?
- 只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,即实现了key的定位;
- 如果 length 不是 2 的次幂,比如 length 为 15,则 length - 1 为 14,对应的二进制为 1110,在与h&操作,最后一位都为 0 ,所以末尾为1的位置永远都不能存放元素了,数组可以使用的位置比数组长度小了很多,造成空间的浪费, 这意味着进一步增加了碰撞的几率,减慢了查询的效率。
操作相关的问题
- put方法的原理
- hash方法的原理
// 通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 在满足容量是2的幂次方下, 位运算(&)效率要比取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。
// 使用位运算代替取模运算,除了性能之外,还有一个好处就是可以很好的解决负数的问题。
(n - 1) & hash
- HashMap是使用了哪些方法来有效解决哈希冲突的?
- 使用链地址法来链接拥有相同hash值的数据;
- 使用2次扰动函数来降低哈希冲突的概率,使得数据分布更平均;