Skip to content

Commit fc50c9a

Browse files
authored
Update 集合框架面试知识点.md
1 parent c2e04d4 commit fc50c9a

File tree

1 file changed

+40
-8
lines changed

1 file changed

+40
-8
lines changed

docs/集合框架面试知识点.md

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@
5757
- List转换成为数组:调用ArrayList的toArray方法。
5858
- 数组转换成为List:调用Arrays的asList方法。
5959

60+
## Array 和 ArrayList 有何区别?
61+
62+
- Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
63+
- Array是指定大小的,而ArrayList大小是固定的。
64+
- Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。
65+
6066
## HashMap 和 Hashtable 的区别
6167

6268
1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过`synchronized` 修饰。(如果保证线程安全的话使用 ConcurrentHashMap );
@@ -81,7 +87,7 @@
8187

8288
## HashMap 的长度为什么是2的幂次方
8389

84-
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
90+
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
8591

8692
**hash%length==hash&(length-1)的前提是 length 是2的 n 次方;**
8793

@@ -93,9 +99,11 @@
9399
* **插入和扩容的判断:**1.7:先扩容后插入,1.8:先插入后扩容
94100
* 为什么?1.8增加了判断是否为红黑树节点,先扩容的话不知道到底扩链表节点还是红黑树。
95101

96-
## ConcurrentHashMap线程安全的实现方式
102+
## ConcurrentHashMap线程安全的实现方式/数据结构⭐
97103

98-
CAS+ synchronized,详见java容器总结篇。
104+
在JDK1.7版本中,ConcurrentHashMap维护了一个Segment数组,Segment这个类继承了重入锁ReentrantLock,在该类里面维护了一个 HashEntry<K,V>[] table数组,在写操作put,remove,扩容的时候,会对Segment加锁,所以仅仅影响这个Segment,不同的Segment还是可以并发的,所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率。在JDK1.8版本中,ConcurrentHashMap摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
105+
106+
详见java容器总结篇。
99107

100108
## HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
101109

@@ -111,7 +119,7 @@ CAS+ synchronized,详见java容器总结篇。
111119

112120
这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
113121

114-
## 为什么HashMap中String、Integer这样的包装类适合作为Key?
122+
## 为什么HashMap中String、Integer这样的包装类适合作为Key?
115123

116124
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
117125

@@ -122,11 +130,35 @@ String、Integer等包装类的特性能够保证Hash值的不可更改性和计
122130

123131
重写`hashCode()``equals()`方法 。 **重写`hashCode()`是因为需要计算存储数据的存储位置****重写`equals()`方法** **目的是为了保证key在哈希表中的唯一性**
124132

125-
## Array 和 ArrayList 有何区别?
133+
## HashMap 的扩容机制
126134

127-
- Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
128-
- Array是指定大小的,而ArrayList大小是固定的。
129-
- Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。
135+
扩容时机: **`map`中包含的`Entry`的数量大于等于`threshold = loadFactor \* capacity`的时候,且新建的`Entry`刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为2倍。**
136+
137+
`size`大于等于`threshold`的时候,并不一定会触发扩容机制,但是会很可能就触发扩容机制,只要有一个新建的节点出现哈希冲突,则立刻`resize`
138+
139+
- size记录的是map中包含的Entry的数量
140+
- 而threshold记录的是需要resize的阈值 且 `threshold = loadFactor * capacity`
141+
- capacity 其实就是桶的长度
142+
143+
1.7创建一个容量的新数组,重新计算每个元素在数组中的位置并且进行迁移。
144+
145+
1.8中在扩容HashMap的时候,不需要像1.7中去重新计算元素的hash,只需要看看原来的hash值新增的哪个二进制数是1还是0就好了,如果是0的话表示索引没有变,是1的话表示索引变成“oldCap+原索引”,这样即省去了重新计算hash值的时间,并且扩容后链表元素位置不会倒置。
146+
147+
步骤:
148+
149+
* 数组,阈值都扩大一倍
150+
* 如果旧数组不为空,开始遍历旧数组
151+
* 遍历到的数组上只有一个元素,就直接迁移
152+
* 如果是红黑树就使用 split 方法
153+
* 如果是链表就把链表拆成两个,放到新数组中并且保留顺序
154+
155+
## HashMap为什么不安全?⭐
156+
157+
HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
158+
159+
## 高并发下HashMap的环是如何产生的
160+
161+
若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
130162

131163
## BlockingQueue是什么?
132164

0 commit comments

Comments
 (0)