相信大家在工作学习的过程中,都有遇到过这样的情况:那就是使用hashmap类型的集合时,取出元素的顺序和放入元素的顺序不一致,后面一上网搜索,发现将HashMap换成LinkedHashMap就解决了元素存取的无序性问题。不知道大家有没有考虑到为什么hashmap的存取顺序会不一致?以及是否还有别的方法来实现顺序读取?
下面就针对上诉的两个问题进行探讨一下。
首先,HashMap的存取为什么不一致?这主要是因为Hashmap的put和get方法。
(以jdk1.8版本为例,hashmap的底层结构为table数组+entry链表/红黑树)
put()方法的大致流程如下(省略了扩容的部分,有兴趣了解的可以自行搜索):
1、对要put的元素key值的hashCode做hash运算,得到的index值就是数组下标
2、判断该位置是否存在元素
2.1、如果没有则将元素放到此位置上
2.2、如果已经存在元素,说明出现了hash冲突,这时候需要遍历该位置的链表元素,使用equals()方法将当前元素的key和链表元素的key一一进行比较,看看是否有相等的key
2.2.1、如果有相等的key,则将新的value替换掉旧的value
2.2.2、如果没有找到相等的key,则在链表的最前面加入当前节点
3、如果链表过长,链表就会转为红黑树
再来说get()方法:
1、对key值的hashCode做hash运算,得到数组下标index
2、根据index位置去找元素,如果该位置上只有一个元素,则直接返回
3、如果index位置的元素是以链表或者红黑树形式存在的,则需要使用equal()方法遍历比较,找到相等key的元素并返回。
通过对put方法和get方法的实现过程进行分析后,不难发现造成元素读取不一致的原因。
那就是,hashmap取元素,都是先拿到元素key值的hashCode,再做hash运算,得到实际存放的下标,再根据下标去取出的,而这个和元素的放入顺序是无关的。
下面结合代码来证明。
初始化两个元素相同的数组,只是一个升序,一个降序。依次放入hashmap集合,再依次取出,比较这两个数组的读取结果是否和存放相关。
结果如下:
可以看出,只要是同一组数据,不管以什么顺序放入,读取的顺序都是固定的。
那如果要以指定的顺序拿到map集合的元素,该怎么做?
方法一:使用LinkedHashMap代替HashMap,LinkedHashMap读取元素的顺序与存放元素的顺序保持一致,代码实测结果如下图:
方法二:将HahMap转为List集合,也就是构建一个元素为键值对(Map.Entry)的List集合,再搭配Collection.sort()方法对集合元素自定义排序(需要重写compare()方法)
结果如下,可以看到也实现了顺序读取hashMap元素的功能
完整代码如下
import java.util.*;
public class HashMapOrder {
//初始化一个数组
public int[] initArray(int num)
{
int[] arr = new int[num];
for(int i=0;i<num;i++)
{
arr[i]=i+1;
}
return arr;
}
//按顺序将数组的值放入Map类型的集合中
public Map<String,Integer> setOrderMap(int[] arr,Map map,int order) {
String mapType = map.getClass().getTypeName();
System.out.println("map类型为"+mapType+",写入Map集合的顺序如下:");
if (order == 1) {
for (int i = 0; i < arr.length; i++) {
map.put("第" + (i + 1) + "位:", arr[i]);
System.out.println("第" + (i + 1) + "位:" + arr[i]);
}
}
else{
for (int i = arr.length-1; i>=0; i--) {
map.put("第" + (i + 1) + "位:", arr[i]);
System.out.println("第" + (i + 1) + "位:" + arr[i]);
}
}
return map;
}
//从HashMap类型的集合里读取元素
public void getOrderMap(Map<String,Integer> map)
{
System.out.println("从map集合取出的元素如下:");
for(Map.Entry<String,Integer> entry:map.entrySet())
{
System.out.println(entry.getKey() + entry.getValue());
}
}
//对hashmap的元素进行排序
public void sortHashMap(Map<String,Integer> map)
{
List<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(map.entrySet());
Collections.sort(list,new Comparator<Map.Entry<String,Integer>>(){
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
System.out.println("对hashmap里的元素手动排序后取出的顺序:" );
for(Map.Entry<String,Integer> entry:list)
{
System.out.println(entry.getKey() + entry.getValue());
}
}
public static void main(String[] args) {
HashMapOrder hashMapOrder = new HashMapOrder();
int[] arr = hashMapOrder.initArray(10);
Map mapType1 = new HashMap();
Map mapType2 = new LinkedHashMap();
//定义排序的规则,正数为升序,否则为降序
int order = 1;
//升序排列,hashmap类型
Map<String,Integer> map = hashMapOrder.setOrderMap(arr,mapType1,order);
hashMapOrder.getOrderMap(map);
System.out.println("--------------分割线---------------");
order = -1;
//降序排列,hashmap类型
map = hashMapOrder.setOrderMap(arr,mapType1,order);
hashMapOrder.getOrderMap(map);
//对hashmap手动排序
System.out.println("--------------分割线---------------");
map = hashMapOrder.setOrderMap(arr,mapType1,order);
hashMapOrder.sortHashMap(map);
// hashMapOrder.getOrderMap(map);
System.out.println("--------------分割线---------------");
//升序排列,LinkedHashMap类型
map = hashMapOrder.setOrderMap(arr,mapType2,order);
hashMapOrder.getOrderMap(map);
}
}
写在最后,如果这些文章对您有所帮助,不妨点个赞支持一下,谢谢!
如果有写的不对的地方,也欢迎大家批评指正。