文章目录
- JAVA集合
-
- 一、JDK7和JDK8中的HashMap有什么区别
- 二、HashMap底层的实现原理
- 三、HashMap的扩容机制
- 四、HashMap中的循环链表如何产生的
- 五、HashMap为什么用红黑树而不用B树
- 六、HashMap为什么线程不安全
- 七、HashMap如何实现线程安全
- 八、HashMap如何解决哈希冲突的
- 九、HashMap与ConcurrentHashMap有什么区别
- 十、 介绍一下ConcurrentHashMap是怎么实现的
- 十一、ConcurrentHashMap是怎么分段分组的
- 十二、Collection 和 Collections的区别
- 十三、List,Set,Map三个接口的区别
- 十四、LinkedList、ArrayList和Vector的区别?
- 十五、数组和集合的区别?
- 十六、HashMap、HashTable、LinkedHashMap、TreeMap区别
- 十七、HashSet、TreeSet和LinkHashSet区别?
- 十八、请说明Comparable和Comparator接口的作用以及它们的区别。
- 十九、Iterator和ListIterator的区别
- 二十、有哪些线程安全的List
- 二十一、介绍一下ArrayList的数据结构
- 二十二、CopyOnWriteArrayList原理
- 二十三、Collections.SynchronizedList 原理?
- 二十四、BlockingQueue 实现
- 二十五、Condition 的实现原理
- 二十六、LinkedTransferQueue 和SynchronizedQueue 实现
- 二十七、红黑树特点
- 二十八、
JAVA集合
一、JDK7和JDK8中的HashMap有什么区别
JDK7中的HashMap,是基于数组+链表来实现的,它的底层维护一个Entry数组。它会根据计算的hashCode将对应的KV键值对存储在该数组中,一旦发生hashCode冲突,那么就会将该KV键值对放到对应的已有元素的后面,此时便形成了一个链表式的存储结构。
JDK7中HashMap的实现方案有一个明显的缺点,即当Hash冲突严重时,在桶上形成的链表会变得越来越长,这样在查询时的效率就会越来越低,其时间复杂度为O(N)。
JDK8中的HashMap,是基于数组+链表+红黑树来实现的,它的底层维护一个Node数组。当链表的存储的数据个数大于等于8时候,不再采用链表存储,而采用了红黑树存储结构。这么做主要是在查询的时间复杂度上进行优化,链表为O(N),而红黑树一直是O(logN),可以大大的提高查找性能。
二、HashMap底层的实现原理
它基于hash算法,通过put方法和get方法存储和获取对象。
存储对象时,我们将KV传给put方法时,它调用K的hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Factor则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。
如果发生碰撞的时候,HashMap通过链表将产生碰撞冲突的元素组织起来。在Java8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
put解析:
1、 如果HashMap为空,则进行初始化;
2、调用哈希函数对Key求Hash值,然后再计算下标。并查找所在链表。
3、 如果链表长度超过阈值(TREEIFY_THRESHOLD == 8),就把链表转换成红黑树。
4、如果结点的键已经存在就替换旧值。否则用头插法插入新结点。
5、如果桶满了(容量+加载因子),就需要resize(双倍扩容,保证2的n次幂)进行扩容,并且为了使结点均匀分散,应该重新分配结点位置if(++size>threshold)resize();
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
// 1. HashMap 为空,则进行初始化
inflateTable(threshold);
}
// 键为 null 单独处理
if (key == null)
return putForNullKey(value);
// 2. 调用哈希函数对key求hash值,并取模得桶下标
int hash = hash(key);
int i = indexFor(hash, table.length);
// 3. 先找出是否已经存在键为 key 的键值对
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 注:散列类容器唯一性判断
if (e.hash == hash && ((k = e.key) == key || key.equals(k))){
//如果存在的话就更新这个键值对的值为 value
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 4. 插入新键值对(如果碰撞了则头插法,没有碰撞则直接插在
addEntry(hash, key, value, i);
return null;
}
/*
HashMap 允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,也就无 法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
*/
get解析:
1、调用哈希函数对Key求Hash值,然后再计算Entry数组的索引i。
2、 遍历table[i]为头结点的链表/红黑树,如果发现有节点hash,key都相同节点,则取出该节点的值。
public V get