Q1 HashMap是通过什么方式实现的? - >数组Entry的链表
Q2 HashMap为什么不安全?怎样实现安全?
多线程情况下如果两个线程同时放的哈希(键)相同,则会发生碰撞,出现覆盖的情况。
避免需要这样实例:建议使用ConcurrentHashMap
Q3两个键的hashcode相同,你如何获取值对象?如何把对象?
3.1当我们调用get()方法方法,HashMap中会使用键对象的哈希码找到桶位置,找到斗位置之后,会调用keys.equals()方法去找到链表链表中正确的节点,最终找到要找的值对象。
3.2把时候先查找键的哈希值,如果哈希相同,那么则找到该对象的链表,放在链头的位置。
Q4调整HashMap的大小存在什么问题吗?
当多线程的情况下,可能产生条件竞争。
当重新调整HashMap的大小的时候,存在条件竞争,因为如果两个线程都发现HashMap中需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的桶位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap的呢?:)
Q5为什么字符串,Interger这样的包装类适合作为键?自定义对象可以作为键吗?
字符串是不可变的,也是最后的,而且已经重写了equals()方法和hashCode()方法方法。
自定义对象可以作为键,只要是插入之后是不可变的,并重写了等于和hashCode()方法方法。
Q6 HashMap复杂度 - >理想O (N / Buckets),N就是以数组中没有发生碰撞的元素
要保证O(N),需要尽量避免出现重复的的hashCode(键),因为出现重复的回在获得/放的时候需要进行二次等于比较计算出在链表的位置;以及链表的长度都会影响时间复杂度
如果某个桶中的链表记录过大的话(当前是TREEIFY_THRESHOLD = 8),就会把这个链动态变成红黑二叉树(TreeMap),使查询最差复杂度由O(N)变成了O( logn)时间。
Q7重新调整HashMap大小存在什么问题吗?
可能产生条件竞争,多线程的情况下会有多个线程同时修改地图的大小,导致出现死循环。
Q8 HashMap&TreeMap&LinkedHashMap使用场景?
一般情况下,使用最多的是HashMap.HashMap
:在Map中插入,删除和定位元素时;
TreeMap:在需要按自然顺序或自定义顺序遍历键的情况下;
LinkedHashMap的:在需要输出的顺序和输入的顺序相同的情况下。
Q9 HashMap和HashTable有什么区别?
①,HashMap是线程不安全的,HashTable是线程安全的;
②,由于线程安全,所以HashTable的效率比不上HashMap;
③,HashMap最多只允许一条记录的键为null ,允许多条记录的值为null,而HashTable不允许;
④,HashTable为11,前者扩容时,扩大两倍,后者扩大两倍+1;
⑤,HashMap需要重新计算哈希值,而HashTable直接使用对象的hashCode
1地图是一个接口。
//map是一个接口
public interface Map<K,V> {}
//定义抽象的类实现Map接口
public abstract class AbstractMap<K,V> implements Map<K,V>{}
//AbstractHashedMap继承抽象map
public class AbstractHashedMap extends AbstractMap implements IterableMap{}
2可以通过entrySet获取当前数组的所有条目。
public abstract Set<Entry<K,V>> entrySet();
3 HashMap定义
3.1初始容量:默认map容量,默认为16
3.2加载因子:为设定临界值的维度,默认为0.75。
3.3容量临界值:在put后会如果当前的大小大于旧的数组大小,需要重新计划临界值。
3.4最大容量:1.8jdk规定最大为2的30次方。
protected static final int DEFAULT_CAPACITY = 16;//初始容量
protected static final int DEFAULT_THRESHOLD = 12;//容量临界值=初始容量*默认加载因子
protected static final float DEFAULT_LOAD_FACTOR = 0.75F;//默认加载因子0.75
protected static final int MAXIMUM_CAPACITY = 1073741824;//最大容量为2的30次方
protected static final Object NULL = new Object();
protected transient float loadFactor;
protected transient int size;//已经存在的个数
protected transient AbstractHashedMap.HashEntry[] data;//数组
protected transient int threshold;
protected transient int modCount;//已从结构上修改 此列表的次数
4 HashMap的构造函数
JDK源码中有3个构造函数,最后都调用AbstractHashedMap(int initialCapacity,float loadFactor)方法。
/**
* initialCapacity : 初始化容量,默认16
* loadFactor : 初始化加载因子,默认0.75
**/
protected AbstractHashedMap(int initialCapacity, float loadFactor) {
if(initialCapacity < 1) {
throw new IllegalArgumentException("Initial capacity must be greater than 0");
} else if(loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
this.loadFactor = loadFactor;
// 判断初始化容量是否大于hashMap定义大小(2的30次方),如果大于设置初始大小为2的30次方,如果小于,设置大小为2(1<<=1)
initialCapacity = this.calculateNewCapacity(initialCapacity);
//初始化临界值:(int)((float)initialCapacity* loadFactor)
this.threshold = this.calculateThreshold(initialCapacity, loadFactor);
//初始化数组大小,通过initialCapacity
this.data = new AbstractHashedMap.HashEntry[initialCapacity];
this.init();
} else {
throw new IllegalArgumentException("Load factor must be greater than 0");
}
}
面试问题:HashMap的默认大小是多大?
HashMap的默认大小为16
/**
* 如果传递参数的map的大小大于16,那么会创建size*2的容量。
**/
protected AbstractHashedMap(Map map) {
this(Math.max(2 * map.size(), 16), 0.75F);
this.putAll(map);
}
面试问题:为什么每次创建都是为2的幂次?
判断初始化容量是否大于hashMap定义大小(2的30次方),如果大于设置初始大小为2的30次方,如果小于,设置大小为2(1 << = 1);通过左移位赋值运算符进行计算<< 1
protected int calculateNewCapacity(int proposedCapacity) {
int newCapacity = 1;
if(proposedCapacity > 1073741824) {
newCapacity = 1073741824;
} else {
while(true) {
if(newCapacity >= proposedCapacity) {
if(newCapacity > 1073741824) {
newCapacity = 1073741824;
}
break;
}
newCapacity <<= 1;//左移位赋值运算符
}
}
return newCapacity;
}
5放(Object key,Object value)方法
/**
* 逻辑步骤
* 1 判断key是否为空,支持NULL关键字
* 2 取key的hashCode值
* 3 通过key的hashCode值与当前map的长度进行&与运算,得到数组下标
(&运算:将两端转化为二进制,进行对应位比较,如果相等返回1,不想等返回0)
* 4 循环当前map是否存在key,如果存在比较==/equals是否相等,
* 4.1 如果相等更新key的值,返回旧的值
* 5 不存在则创建HashEntry对象,
* 6 将HashEntry存放到数组中指定下标位置
**/
public Object put(Object key, Object value) {
// 判断key是否为空,如果为空,返回NULL
key = this.convertKey(key);
//得到key的hash()值
int hashCode = this.hash(key);
//得到数组下标:hashCode & this.data.length- 1;
int index = this.hashIndex(hashCode, this.data.length);
// 循环当前数组,判断是否选在当前key
for(AbstractHashedMap.HashEntry entry = this.data[index]; entry != null; entry = entry.next) {
// 如果存在key进行比较,如果完全对象相等,值相等则替换key值,返回旧的值
if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {
Object oldValue = entry.getValue();
this.updateEntry(entry, value);
return oldValue;
}
}
this.addMapping(index, hashCode, key, value);
return null;
}
protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
++this.modCount;
//创建HashEntry对象,
AbstractHashedMap.HashEntry entry = this.createEntry(this.data[hashIndex], hashCode, key, value);
this.addEntry(entry, hashIndex);
//将enrty存放到指定数组下标位置
-->this.data[hashIndex] = entry;
//设置房前的map大小+1
++this.size;
//检查map容量
this.checkCapacity();
}
/**
* 检查容量
* 逻辑步骤:
* 1 判断当前map大小大于等于容量界定值(默认12) && map大小*2后小于最大容量(2的32次方)
* 2 重新进行容量的初始化
* 3 实例化新的数组,并将old数组集合循环赋值给新的数组集合
* 4 根据新的容量*加载因子计算临界值
**/
protected void checkCapacity() {
if(this.size >= this.threshold) {
int newCapacity = this.data.length * 2;
if(newCapacity <= 1073741824) {
this.ensureCapacity(newCapacity);
}
}
}
protected void ensureCapacity(int newCapacity) {
int oldCapacity = this.data.length;
if(newCapacity > oldCapacity) {
if(this.size == 0) {
this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
this.data = new AbstractHashedMap.HashEntry[newCapacity];
} else {
AbstractHashedMap.HashEntry[] oldEntries = this.data;
AbstractHashedMap.HashEntry[] newEntries = new AbstractHashedMap.HashEntry[newCapacity];
++this.modCount;
for(int i = oldCapacity - 1; i >= 0; --i) {
AbstractHashedMap.HashEntry entry = oldEntries[i];
if(entry != null) {
oldEntries[i] = null;
AbstractHashedMap.HashEntry next;
do {
next = entry.next;
int index = this.hashIndex(entry.hashCode, newCapacity);
entry.next = newEntries[index];
newEntries[index] = entry;
entry = next;
} while(next != null);
}
}
//计算新的加载因子并复制
this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
//-->(int)((float)newCapacity * loadFactor);
this.data = newEntries;
}
}
}
6 get(String key)
/*** 1 检查key是否为空
* 2 获取key的HashCode值
* 3 循环遍历得到HashEntry,返回entry的值
**/
public Object get(Object key) {
key = this.convertKey(key);
int hashCode = this.hash(key);
for(AbstractHashedMap.HashEntry entry = this.data[this.hashIndex(hashCode, this.data.length)]; entry != null; entry = entry.next) {
if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {
return entry.getValue();
}
}
return null;
}