如果不懂HashMap和LinkedHashMap的原理, 请看我以前写的2篇博客 LruCache为什么要用LinkedHashMap? 和 HashMap源码分析(JDK1.8)- 你该知道的都在这里了
很多人说HashMap是无序的, LinkedHashMap是有序的, Why?
直接上代码说明问题, 向HashMap对象里插入4个元素并使用iterator输出各个元素。 很多人说输出时序是无序的。但事实并非如此, HashMap是按照所在桶的下标从低到高输出的!!! 位置等于key的哈希值与0xf。 PS:示例只考虑了初始情况,即HashMap有16个桶。
一、使用iterator遍历
//摘自HashMap.java
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("param1", "aaa"); //第14个桶
map.put("param2", "bbb"); //第15个桶
map.put("param3", "ccc"); //第12个桶
map.put("param4", "ddd"); //第13个桶
System.out.println("键值存放的位置分别是:");
System.out.println(hash("param1")&0xf); //14
System.out.println(hash("param2")&0xf); //15
System.out.println(hash("param3")&0xf); //12
System.out.println(hash("param4")&0xf); //13
Class clz = HashMap.class;
try {
Field field = clz.getDeclaredField("table");
field.setAccessible(true);
Object obj = field.get(map);
System.out.println("-----------------");
Field field1 = clz.getDeclaredField("size"); //4
field1.setAccessible(true);
int size = (Integer)field1.get(map);
System.out.println(size);
Field field2 = clz.getDeclaredField("threshold"); //16*0.75=12
field2.setAccessible(true);
int threshold = (Integer) field2.get(map);
System.out.println(threshold);
} catch (Exception ex) {
ex.printStackTrace();
}
//按照下标顺序输出
for (Iterator iterator = map.values().iterator(); iterator.hasNext(); ) {
String value = (String)iterator.next();
System.out.println(value);
}
}
输出:
14
15
12
13
-----------------
4
12
ccc
ddd
aaa
bbb
问题来了, 为什么iterator是根据下标从低到高输出呢??? 答案在HashMap.java的内部类HashIterator里。 执行iterator.next()时取table数组的值,每次执行next函数下标自增1。 所以HashMap的iterator是根据元素所在桶下标位置输出的!!!
同样代码对比一下LinkedHashMap,默认按照插入顺序排序(也可以按照访问顺序排序)。
Map<String, String> map = new LinkedHashMap<>();
map.put("param1", "aaa"); //第14个桶
map.put("param2", "bbb"); //第15个桶
map.put("param3", "ccc"); //第12个桶
map.put("param4", "ddd"); //第13个桶
//按照下标顺序输出
for (Iterator iterator = map.values().iterator(); iterator.hasNext(); ) {
String value = (String)iterator.next();
System.out.println(value);
}
输出:
aaa
bbb
ccc
ddd
LinkedHashMap是如何做到遍历时按照插入顺序输出呢? 原因在于LinkedHashMap的内部类LinkedHashIterator,执行iterator.next访问链表下一个元素, 所以可以按照插入顺序输出(即链表先后顺序)。
二、使用foreach遍历。
Map<String, String> map = new LinkedHashMap<>();
map.put("param1", "aaa"); //第14个桶
map.put("param2", "bbb"); //第15个桶
map.put("param3", "ccc"); //第12个桶
map.put("param4", "ddd"); //第13个桶
//Java8新增的遍历方式
map.forEach((key, value) -> {
System.out.println("key:" + key + ", value:" + value);
});
输出:
key:param1, value:aaa
key:param2, value:bbb
key:param3, value:ccc
key:param4, value:ddd
LinkedHashMap仍然按照插入顺序输出, 原因在源码里的forEach函数, 循环读取after对象。
将示例代码中的LinkedHashMap改为HashMap,遍历时序仍然按照桶位置输出。
Map<String, String> map = new HashMap<>();
map.put("param1", "aaa"); //第14个桶
map.put("param2", "bbb"); //第15个桶
map.put("param3", "ccc"); //第12个桶
map.put("param4", "ddd"); //第13个桶
//Java8新增的遍历方式
map.forEach((key, value) -> {
System.out.println("key:" + key + ", value:" + value);
});
输出:
key:param3, value:ccc
key:param4, value:ddd
key:param1, value:aaa
key:param2, value:bbb
综上所述:
HashMap的无序其实也有迹可循, 即按照桶下标先后排序;如果有哈希碰撞的情况,则同一个桶位置按照链表先后顺序输出。
LinkedHashMap的有序是因为维护了双向链表。