Map集合
与
List集合不同,Map集合中存储的是键值对,且是无序存储。
引入
在使用集合时,如果只需要存储一些对象,那么直接使用 List 进行存储即可。但如果要定位这些对象中是否存在某个属性为X值,在使用 List 集合的情况下只能进行遍历查询,有时候可能遍历完整个集合最后才找到对应的对象。
//使用List存储并查询指定属性对应的对象
UserDemo userDemo = new UserDemo("张三", 17);
UserDemo userDemo1 = new UserDemo("李四", 18);
List<UserDemo> userDemos = new ArrayList<>();
userDemos.add(userDemo);
userDemos.add(userDemo1);
//查询张三的年龄List方案
for (UserDemo userDemo2 : userDemos) {
if (userDemo2.getName().equals("张三")) {
System.out.println(userDemo2.getAge());
}
}Map 中常用的实现类为 HashMap ,以上逻辑使用 Map 进行存储时,可以将需要查询的属性作为键值对的key值,对象作为value值存储,查询时可以直接使用 get 方法查询。
UserDemo userDemo = new UserDemo("张三", 17);
UserDemo userDemo1 = new UserDemo("李四", 18);
//查询张三的性别Map方案
Map<String, UserDemo> stringUserDemoHashMap = new HashMap<>();
stringUserDemoHashMap.put(userDemo.getName(), userDemo);
stringUserDemoHashMap.put(userDemo.getName(), userDemo1);
UserDemo demo = stringUserDemoHashMap.get("张三");
System.out.println(demo.getAge());使用
双链存储
Map 集合每一个元素就是一个 Entry 对象,包含 key 和 value 两部分,存储时和查询时都与 List 不同。
键唯一性
key值不允许重复,若相同key值插入新数据会覆盖旧数据。
//map中key唯一性
Map<String, String> stringStringHashMap = new HashMap<>();
stringStringHashMap.put("张三", "西安");
stringStringHashMap.put("张三", "渭南");
System.out.println(stringStringHashMap.get("张三"));//渭南
Map<UserDemo,String> userDemoHashMap = new HashMap<>();
UserDemo userDemo3 = new UserDemo("张三", 17);
UserDemo userDemo4 = new UserDemo("张三", 17);
userDemoHashMap.put(userDemo3,"学生1");
userDemoHashMap.put(userDemo4,"学生2");
System.out.println(userDemoHashMap.get(userDemo3));//学生1
System.out.println(userDemoHashMap.get(userDemo4));//学生2
上述代码中,因为 stringStringHashMap 集合key为 String 类型,插入重复 key 验证重复替换了 value 值。而 userDemoHashMap 中 key 类型为对象,没有判断到重复值,都进行了插入操作。
因为在 Map 集合中,判断 key 值重复是根据 key 的引用类型对象是否重写了 hashCode() 和 equals() 方法进行判断的。
Map判断key重复步骤
- 计算哈希值: 调用
key对象的hashCode方法得到对象的哈希码(int类型); - 定位桶的位置: 根据哈希值计算出
key对应的存储位置; - 解决哈希碰撞:
- 如果桶为空,则直接将该
key-value值存储; - 如果桶补为空:遍历桶中的链表/红黑树,对每个已存在的
key调用equals方法比较:- 若相等,则说明
key相同,直接覆盖value值; - 若不相等,说明是不同的
key(哈希碰撞),将新的key-value添加到链表/红黑树。
- 若相等,则说明
- 如果桶为空,则直接将该
无序性
Map 的存储是无序的,不回根据存储顺序也不会根据 key 的顺序进行存储,同一个 Map 有可能多次输出的顺序都是不同的。
遍历Map
key遍历
使用
keySet方法获取集合中所有key以Set集合存储。
//遍历
Set<UserDemo> userDemos1 = userDemoHashMap.keySet();
for (UserDemo userDemo5 : userDemos1) {
System.out.println(userDemoHashMap.get(userDemo5));
}Entry遍历(键值对)
entrySet方法可以将集合中的每一个键值对返回。
Set<Map.Entry<UserDemo, String>> entries = userDemoHashMap.entrySet();
for (Map.Entry<UserDemo, String> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}常用Map集合
HashMap
特性:
- 无序性:不保证插入顺序和存储顺序一致;
- 允许null:
key和value都可以为null值,且key唯一; - 基于哈希表实现。
底层结构:
jdk7及之前:数组+链表(解决哈希碰撞); jdk8及之后:数组+链表/红黑树 (当链表长度大于等于8且数组长度大于等于64,链表转换为红黑树,减少查询时间)
适用场景:
- 通用键值对存储;
- 无需顺序或排序的场景;
- 单线程或低并发场景。
LinkedHashMap
核心特性:
- 继承
HashMap:具备HashMap所有特性,单支持有序排列; - 有序性:通过双向链表维护元素的插入顺序和访问顺序;
- 允许null。
关键参数:accessOrder
构造参数可指定 assessOrder 默认false;
- false:保持插入顺序(先插入先遍历);
- true:保证访问顺序(最近访问的元素移动到链表尾部)
Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("张三", "西安");
linkedHashMap.put("李四", "渭南");
linkedHashMap.put("王五", "临潼");
for (String s : linkedHashMap.keySet()) {
System.out.println("key:"+s+"---- value:"+linkedHashMap.get(s));
}
/**
*key:张三---- value:西安
*key:李四---- value:渭南
*key:王五---- value:临潼
**/
System.out.println("-----------------------------------");
Map<String, String> linkedHashMap2 = new LinkedHashMap<>(16, 0.75f, true);
linkedHashMap2.put("张三", "西安");
linkedHashMap2.put("李四", "渭南");
linkedHashMap2.put("王五", "临潼");
// 访问元素以改变顺序
linkedHashMap2.get("张三");
linkedHashMap2.get("王五");
for (Map.Entry<String, String> entry : linkedHashMap2.entrySet()) {
System.out.println("key:" + entry.getKey() + "---- value:" + entry.getValue());
}
/**
*key:李四---- value:渭南
*key:张三---- value:西安
*key:王五---- value:临潼
**/ 适用场景:
- 需要保持插入顺序的场景;
- 需要访问顺序;
- 单线程或低并发场景。
TreeMap
核心特性:
- 有序性:按键的自然顺序或自定义比较器排序;
- 键约束:键不能为
null(排序需要比较,null无法比较); - 时间复杂度:基于红黑树实现,插入、查询、删除的时间复杂度为O(log n)。 排序方式:
- 自然排序:键实现
Comparable接口; - 自定义排序:构造函数传入
Comparator;
//自然排序
Map<Integer, String> treeMap = new TreeMap<>();
treeMap.put(1, "张三");
treeMap.put(3, "王五");
treeMap.put(2, "李四");
treeMap.entrySet().forEach(System.out::println);
System.out.println("--------------------------");
//自定义排序
Map<UserDemo, String> treeMap1 = new TreeMap<>((o1, o2) -> {
if (o1 == null && o2 == null) return 0;
if (o1 == null) return -1;
if (o2 == null) return 1;
return Integer.compare(o2.age, o1.age);
});
treeMap1.put(new UserDemo("李四", 19), "渭南");
treeMap1.put(new UserDemo("张三", 17), "西安");
treeMap1.put(new UserDemo("王五", 18), "临潼");
for (Map.Entry<UserDemo, String> userDemoStringEntry : treeMap1.entrySet()) {
System.out.println(userDemoStringEntry.getKey().age + ":" + userDemoStringEntry.getValue());
}使用场景:
- 需按键排序;
- 需要范围查询;
- 单线程或低并发场景。
ConcurrentHashMap
核心特性:
- 线程安全:采用
CAS+Node级锁; - 不允许null:键和值都不能为
null; - 高性能:理想情况下并发读无锁,写操作仅缩并冲突的节点。
使用场景:
- 高并发场景(多线程,计数器,分布式锁的本地缓存);
- 需要线程安全且高性能的场景。