57
57
- List转换成为数组:调用ArrayList的toArray方法。
58
58
- 数组转换成为List:调用Arrays的asList方法。
59
59
60
+ ## Array 和 ArrayList 有何区别?
61
+
62
+ - Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
63
+ - Array是指定大小的,而ArrayList大小是固定的。
64
+ - Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。
65
+
60
66
## HashMap 和 Hashtable 的区别
61
67
62
68
1 . ** 线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过` synchronized ` 修饰。(如果保证线程安全的话使用 ConcurrentHashMap );
81
87
82
88
## HashMap 的长度为什么是2的幂次方
83
89
84
- HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
90
+ HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
85
91
86
92
** hash%length==hash&(length-1)的前提是 length 是2的 n 次方;**
87
93
93
99
* ** 插入和扩容的判断:** 1.7:先扩容后插入,1.8:先插入后扩容
94
100
* 为什么?1.8增加了判断是否为红黑树节点,先扩容的话不知道到底扩链表节点还是红黑树。
95
101
96
- ## ConcurrentHashMap线程安全的实现方式
102
+ ## ConcurrentHashMap线程安全的实现方式/数据结构⭐
97
103
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容器总结篇。
99
107
100
108
## HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
101
109
@@ -111,7 +119,7 @@ CAS+ synchronized,详见java容器总结篇。
111
119
112
120
这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
113
121
114
- ## 为什么HashMap中String、Integer这样的包装类适合作为Key?
122
+ ## 为什么HashMap中String、Integer这样的包装类适合作为Key?⭐
115
123
116
124
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
117
125
@@ -122,11 +130,35 @@ String、Integer等包装类的特性能够保证Hash值的不可更改性和计
122
130
123
131
重写` hashCode() ` 和` equals() ` 方法 。 ** 重写` hashCode() ` 是因为需要计算存储数据的存储位置** , ** 重写` equals() ` 方法** ** 目的是为了保证key在哈希表中的唯一性** ;
124
132
125
- ## Array 和 ArrayList 有何区别?
133
+ ## HashMap 的扩容机制
126
134
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引用对方,因此产生链表,导致死循环。
130
162
131
163
## BlockingQueue是什么?
132
164
0 commit comments