文章目录
面试场景
面试官:
1:你对HashMap了解吗?
我回答:
- key-value形式存放数据,支持,null-null,null-value,key-null,key-value 四种形式的。
- 线程不安全。
- 数据结构 数组+链表+红黑树来存放数据。
- 实现Map接口
- 效率较HashTable来说比较快
面试官:
2:刚说线程不安全,那如果要线程安全该如何处理?
我回答:
- 使用Collections.synchronizedMap.(Map) 来进行线程安全处理
- 使用ConcurrentHashMap类来代替,该类使用分段锁,比HashTable效率要快。
面试官:
点点头,还不错,看来是有准备的。
3:刚说数据结构是 数组+链表+红黑树,可以说下HashMap 是如何用该结构存数据的吗?
我回答:
- HashMap 内部会判断是否是第一次put,如果是第一次那么会调用resize()方法进行初始化,申请数组长度为 n = 16, threshold(阈值) = 12;之后通过tab[i = (n - 1) & hash]) ==null 来判断该下标是否已经存在值,第一次put,肯定没有值,那么 通过 i=(n-1)&hash 计算出第一次put值得下标,并把值放入Node []中,可以看到下标的取值,取决于key的hash值。
- 当put第二个值时,假设第二次的hash值和第一次的hash值相同,那么就会出现散列冲突情况。然后把第二个值添加到第一个值得next行程链表,当链表的长度>=TREEIFY_THRESHOLD(8) - 1 时转换为红黑树。
面试官:
哟呵,可以啊
4:刚才说的是通过hash值该进行下标定位,那么hash值时如何计算的,计算为什么使用^(异或) 运算符 ?
我回答:
/**
* 计算hash值
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* get(key) 中调用该方法
*/
final Node<K, V> getNode(int hash, Object key) {
......
if ((tab = table) != null && (n = tab.length) > 0 &&
//注意这里的取值
(first = tab[(n - 1) & hash]) != null) {
......
return null;
}
主要是因为
(1):在 get(key) 方法中 是通过 tab[(n - 1) & hash]) 中来定位下标的;
(2):在这如果不使用 ^ (h >>>16) 运算符, 假设 n = 16 (初始长度),A对象hash值为: 10000000,B对象的hash值为11000000;那么 就是 15(00001111) 和 hash值来取数,15分别于A,B 进行&运算 得到的结果都为 0 ;这样就会导致key值重复;如果要是加上 ^(h >>> 16) A 右移16位位 8个0,然后与 A的hashCode进行 ^ 进行运算 得到 10000000 还是原来的值,B同样也还是得到原来的值为 11000000 ;这样就不重复了;
(3):总结,使用该运算符,就是为了避免hash值不重复。因为在定位下标的时候要用;
面试官:
在下标定位时
5:为什么使用(n-1) & hash 取代了 hash % 16 ,来进行下标的定位?
/**
* put(key,value) 方法中调用该方法
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
......
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
......
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
有会有人问为什么使用&运算符代替%运算符;其实问的是同一个问题。
答案:& 预算比 % 运算 效率高 ; 因为 & 是二位运算符,直接和0,1打交道的。
面试官:
刚才在说put的过程中,有几个数值:
6:n(默认数组长度)=16,threshold(扩容阈值) = 12,treeify_threshold(转换数阈值)=8,这几个值为什么是这几个数,而不是别的数呢?
我回答:
- n=16,因为 16=15-1 =(2的次幂数),然后15 = 1111
- threshold = 12 = 16*0.75
- 之后补充