概述
Map
Map是Java中的一个接口,它继承自Collection接口,定义了键值对的存储和检索方法。Map中的键和值可以是任意类型的对象。常见的Map实现类有HashMap、TreeMap和LinkedHashMap等。
Map的全谱系图
HashMap
HashMap是基于哈希表实现的,它提供了快速的插入和查找操作。它的存储方式是无序的,不保证键值对的顺序。
今天主要介绍HashMap
key和value
HashMap中的key和value是键值对的组成部分。key是用于标识和查找值的对象,它必须是唯一的,且不可重复。value是与key关联的对象,它可以重复。在HashMap中,通过key来计算出存储位置并存储value,当需要获取value时,通过key来查找对应的value。HashMap中的key和value可以是任意对象,但通常会使用String、Integer等常用的类作为key和value。
HashMap的四种遍历方式
keySet
//定义一个往map里添加元素的方法 public static Map inputMap(){ Map<String, Integer> userMap = new HashMap<String, Integer> (); userMap.put("zhang1",1); userMap.put("zhang2",2); userMap.put("zhang3",3); userMap.put("zhang4",4); userMap.put("zhang5",5); return userMap;
/*利用keyset进行遍历*/ public static void showMap1(Map<String,Integer> userMap){ for (String key:userMap.keySet()) { System.out.println(key+"***"+userMap.get(key)); } }
values
/*利用values进行遍历*/ public static void showMap2(Map<String,Integer> userMap){ for (Integer v:userMap.values()) { System.out.println(v); } }
entrySet
/*利用entrySet进行遍历*/ public static void showMap3(Map<String,Integer> userMap){ for (Map.Entry<String,Integer> entry:userMap.entrySet()) { System.out.println(entry.getKey()+"==="+entry.getValue()); } }
Iterator
/*利用Iterator进行遍历*/ public static void showMap4(Map<String,Integer> userMap){ Iterator<Map.Entry<String,Integer>> it=userMap.entrySet().iterator(); while (it.hasNext()){ Map.Entry<String,Integer> entry=it.next(); System.out.println(entry.getKey()+"***"+entry.getValue()); } }
性能分析
稍微修改一下inputMap代码
public static Map inputMap(){ Map<String, Integer> userMap = new HashMap<String, Integer> (); //定义一个数组,后面用来组合key用 String str[]=new String[]{"a","b","c","d","e"}; String key; Integer value; for (int i = 0; i < 100000; i++) { //定义一个随机数 int m=(int) Math.random()*5; key=String.valueOf(str[m])+i*100; value=i; userMap.put(key,value); } return userMap; }
注意:给四种遍历方式分别加上开始时间和结束时间,同时把输出语句去掉,在条件一致的前提下测试性能
/*利用values进行遍历*/ public static void showMap2(Map<String,Integer> userMap){ Long start=System.currentTimeMillis(); for (Integer v:userMap.values()) { } Long end=System.currentTimeMillis(); System.out.println("values="+(end-start)); }
输出结果如图所示:
Iterator和values速度最快,keyset速度最慢,当然每次运行时间会有些变化,但是keyset还是最慢,其他三个效率差不多。
应用场景
1.EntrySet遍历方式适用于需要在遍历过程中对键值对进行操作或者获取键值对的键和值的场景。
2.KeySet遍历方式适用于只需要获取键的场景。
3.Values遍历方式适用于只需要获取值的场景。
4.当需要在遍历过程中对HashMap进行修改操作时,可以使用迭代器遍历方式。迭代器提供了安全的遍历方式,可以在遍历过程中对HashMap进行增删操作,
而不会抛出ConcurrentModificationException异常。
当需要按照特定的顺序遍历HashMap时,可以使用迭代器遍历方式。迭代器可以通过调用HashMap的sort()方法进行排序,然后按照排序后的顺序遍历HashMap。
注意:如果使用JDK 8及以上的版本,并且希望代码简洁,还可以使用Lambda表达式遍历方式。此处不作为叙述内容。
二维表
遍历方式 | 获取键值对的键和值 | 获取键 | 获取值 | 是否支持修改操作 | 是否支持按照特定顺序遍历 |
EntrySet方式 | 是 | 否 | 否 | 是 | 是 |
KeySet方式 | 否 | 是 | 否 | 是 | 否 |
Values方式 | 否 | 否 | 是 | 是 | 否 |
迭代器方式 | 是 | 否 | 否 | 是 | 是 |
底层原理
我们通过测试发现
- Hashmap是无序的(遍历输出顺序和put进去的顺序无关)
- 但是也不是随机输出
那么put之后HashMap是如何决定键值映射所处的位置呢?
key是数值型
public static void main(String[] args) { Map<Integer,String> map=new HashMap(); map.put(120,"a");//120%16=8 map.put(37,"a");//37%16=8 map.put(61,"a");//61%16=8 map.put(40,"a");//40%16=8 map.put(92,"a");//92%16=8 map.put(78,"a");//78%16=8 System.out.println(map); }
如代码注释所示,HashMap底层是通过位运算的方式将key转化为哈希码对16求余数(此处直接求余数)决定存储位置,其中120和40对16取余数(map在初始化的时候默认长度是16)结果都是8,产生了碰撞,也就是坐标相同,此时会产生一个链表的形式。8这个坐标指向120,120键值映射的Entry里的next指向了40。
附一张HashMap的Entry结构
输出结果和上图分析一致
key是字符类型
如果key是字符类型,定位的过程与其他类型的key是相同的。HashMap内部使用哈希算法来确定key在数组中的索引位置。具体的定位过程如下:
首先,HashMap会调用key的hashCode()方法来获取key的哈希码。hashCode()方法是Object类的方法,所有的对象都可以调用该方法来获取它们的哈希码。
然后,HashMap会使用哈希码进行计算,通过哈希算法将key映射到数组中的一个索引位置。HashMap使用的哈希算法是将哈希码与数组的长度进行按位与操作,得到的结果就是key在数组中的索引位置。
如果发生哈希冲突(即不同的key计算出的索引位置相同),HashMap会使用链表或红黑树来解决冲突。具体来说,如果链表长度小于8,HashMap会将新的键值对添加到链表的末尾;如果链表长度大于等于8,HashMap会将链表转换为红黑树,以提高查找效率。
这里提一下红黑树(Red-Black Tree),它是一种自平衡的二叉搜索树,它在数据结构中被广泛应用。红黑树的特点是具有较好的平衡性和高效的查找、插入和删除操作。在Java中,红黑树主要用于解决HashMap中的哈希冲突。
TreeMap也使用了红黑树来实现有序映射。TreeMap是基于红黑树实现的,它根据键的自然顺序或自定义比较器的顺序对键进行排序。
总的来说红黑树是一种用于解决哈希冲突和实现有序映射的数据结构。在HashMap中,红黑树用于解决哈希冲突;而在TreeMap中,红黑树用于实现有序映射。
总结:
HashMap的遍历方式主要有keySet、Values、ntrySet和Iterator。其中EntrySet适用于需要在遍历过程中对键值对进行操作或者获取键值对的键和值的场景。KeySet适用于只需要获取键的场景。 Values适用于只需要获取值的场景。 当需要在遍历过程中对HashMap进行修改操作时,可以使用迭代器方式。底层原理是通过遍历数组的每个元素,然后遍历链表或红黑树的每个节点来实现遍历。