HashMap常见面试题
- 当key为null时,进行put操作,数据会被放在那个桶位?为什么?
- 数组0的位置
- 为什么HashMap内部的散列表数组长度一定时2的次方数?
- 如果不是2的次方数会出现大量的重复 而且有些位置存放不到数据(散列性差)
- HashMap内部的散列表结构,什么时候初始化?以及初始化大小分别有几种情况?
- 调用put方法的时候初始化, 初始化大小可以根据创建map的时候设置,如果没有设置默认16
- HashMap为什么需要扩容,扩容resize()又是如何实现的?
- 为了map的查询性能,数组长度太小不管优化成什么数据结构重复率也会很高,导致查询变慢。这是牺牲空间提升时间的解决方案
- 扩容值根据加载因子 数组长度到了一定的位置就会进行扩容,每次数组长度<<1,还会根据新的数组长度计算重复位置链表中的哈希值 把哈希值不同的元素放到新的位置。
- JDK8中,HashMap为什么引入红黑树
HashMap()
- 构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。
- new的时候还没有初始化在第一次put的时候才会初始化
//HashMap的无参构造方法源码
public HashMap() {
//默认加载因子0.75
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
HashMap(int initialCapacity, float loadFactor)
- initialCapacity 设置的数组长度
- loadFactor 设置负载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)//数组长度不能小于0 小于0抛出异常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)//如果数组长度大于最大长度就把数组长度设置为最大长度
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//如果加载因子小于等于0或者不是数字抛出异常
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
HashMap(Map<? extends K,? extends V> m)
- 把传入的map变为Hashmap
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
put方法
```java public V put(K key, V value) { //通过hash方法计算出这个数据存放在Node[]中的index位置 return putVal(hash(key), key, value, false, true); } static final int hash(Object key) { int h; //key如果是null那么就把该数据存放到Node[]中的第一个位置 //如果key不为null那么根据二进制算出该数据存放到Node[]中的index //该index不会越界 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
- 把传入的map变为Hashmap
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node
hashMap数据存放图<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12363754/1624367782072-93f5a483-d2a9-4c30-83fd-d5a7755f35c2.png#clientId=u3729e950-aac0-4&from=paste&id=u60d0b943&margin=%5Bobject%20Object%5D&name=image.png&originHeight=211&originWidth=682&originalType=url&ratio=1&size=21214&status=done&style=none&taskId=u222b84dc-87d0-4692-b36e-7fb6345dafd)
<a name="E2pNG"></a>
## get方法
```java
//get源码根据hash值找到index找到该值如果有重复就遍历列表找到对应的key返回value
//是红黑树就遍历红黑树找到value
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
remove方法
//删除也是一样根据hash找到位置如果是链表就把上一个node的next属性指向被删除元素的next
//如果是红黑树就删除该元素然后红黑树会进行平衡
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
HashMap扩容
- 当Node[]的内容达到initialCapacityloadFactor就会进行扩容比如默认值(160.75=12)当Node[]元素中有12个值了就会进行扩容是当前数组的2倍长度
- 而且还会根据数组长度和元素key的哈希值重新计算重复元素在新的Node[]中的index
//扩容源码
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}