HashMap底层数据结构详解
JDK1.7及之前:数组+链表
JDK1.8:数组+链表+红黑树
HashMap添加元素时,会根据哈希值和数组长度来计算该元素put的位置。put元素会判断当前位置是否已经有元素,这样在多线程环境下,会发生数据覆盖,导致数据丢失。通常为了使元素分布均匀会使用取模运算,计算出index后,就会将改元素添加进去,但是会出现两个key的哈希值相同,这时候会产生哈希冲突,当哈希冲突时会产生链表的数据结构,冲突的元素会在该索引出以链表的形式保存。
但是当链表的长度过长时,其固有弊端就显示出来了,即查询效率低。
此时就引入了第三种数据结构,红黑树,红黑树是一刻接近于平衡的二叉树,远远比链表的查询料率高。但是如果链表的长度不到一定阈值,直接使用红黑树也不行,因为红黑树的自身维护代价也比较高,每插入一个元素都可能打破红黑树的平衡,这时候就需要对红黑树再平衡(左旋,右旋,重新着色)。
HashMap中数组的初始长度是16,默认的加载因子是0.75
数组一单达到容量的阈值就需要对数组进行扩容。扩容就意味着要进行数组的移动,数组一旦移动,每移动一次就要重新计算索引,这个过程牵扯大量元素的迁移,会大大影响效率。如果直接使用与运算,这个效率远远高于取模运算。
为什么链表长度大于等于8时转成了红黑树
遵循概率论里的泊松分布。链表中元素个数为8的概率已经非常小,红黑树平均查找长度是log(n),当长度为8时,平均查找长度为3,如果继续使用链表,平均查找长度为8、2=4,这时候才有转为树的必要。
HashMap的遍历方式
迭代器遍历(iterator)
For Each方式遍历
Lambda表达式遍历
Streams Api遍历
迭代器entrySet
@Test
public void test001(){
Map<String, Integer> map = new HashMap<>();
map.put("1",1);
map.put("2",2);
Iterator<Map.Entry<String,Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String,Integer> entry = iterator.next();
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
}
当遍历时涉及到删除操作,建议使用iterator的remove方法。使用forEach会报错。
迭代器keySet
@Test
public void test002(){
Map<String, Integer> map = new HashMap<>();
map.put("11",1);
map.put("22",2);
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.println("key:"+key+",value:"+map.get(key));
}
}
ForEach EntrySet
@Test
public void test003(){
Map<String, Integer> map = new HashMap<>();
map.put("111",1);
map.put("222",2);
for (Map.Entry<String,Integer> entry:map.entrySet()
) {
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
}
通过Map.entrySet遍历key和value,代码简洁高效,推荐使用
ForEach keySet(如果只需要获取所有的key,推荐使用,比entrySet遍历要快,代码简洁)
@Test
public void test004(){
Map<String,Integer> map = new HashMap<>();
map.put("1111",1);
map.put("2222",2);
for (String str:
map.keySet()) {
System.out.println("key:"+str+",value:"+map.get(str));
}
}
根据键取值是耗时操作,不推荐使用
如果只需要获取所有的value,推荐使用,比entrySet快,简洁
@Test
public void test004(){
Map<String,Integer> map = new HashMap<>();
map.put("1111",1);
map.put("2222",2);
for (String str:
map.values()) {
System.out.println("value:"+str);
}
}
Lambda表达式
@Test
public void test005(){
Map<String,Integer> map = new HashMap<>();
map.put("11111",1);
map.put("22222",2);
map.forEach((key,value)->{
System.out.println("key:"+key+",value:"+value);
});
}
Streams API单线程
@Test
public void test006(){
Map<String,Integer> map = new HashMap<>();
map.put("111111",1);
map.put("222222",2);
map.entrySet().stream().forEach((entry)->{
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
});
}
Streams API多线程
@Test
public void test007(){
Map<String,Integer> map = new HashMap<>();
map.put("1111111",1);
map.put("2222222",2);
map.entrySet().parallelStream().forEach((entry)->{
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
});
}