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 对象,包含 keyvalue 两部分,存储时和查询时都与 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 值。而 userDemoHashMapkey 类型为对象,没有判断到重复值,都进行了插入操作。 因为在 Map 集合中,判断 key 值重复是根据 key 的引用类型对象是否重写了 hashCode()equals() 方法进行判断的。

Map判断key重复步骤

  1. 计算哈希值: 调用key 对象的 hashCode 方法得到对象的哈希码(int类型);
  2. 定位桶的位置: 根据哈希值计算出 key 对应的存储位置;
  3. 解决哈希碰撞:
    1. 如果桶为空,则直接将该 key-value 值存储;
    2. 如果桶补为空:遍历桶中的链表/红黑树,对每个已存在的 key 调用 equals 方法比较:
      1. 若相等,则说明key相同,直接覆盖value值;
      2. 若不相等,说明是不同的key(哈希碰撞),将新的key-value添加到链表/红黑树。

无序性

Map 的存储是无序的,不回根据存储顺序也不会根据 key 的顺序进行存储,同一个 Map 有可能多次输出的顺序都是不同的。

遍历Map

key遍历

使用 keySet 方法获取集合中所有 keySet 集合存储。

//遍历  
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

特性:

  1. 无序性:不保证插入顺序和存储顺序一致;
  2. 允许null: keyvalue 都可以为 null 值,且 key 唯一;
  3. 基于哈希表实现。

底层结构:

jdk7及之前:数组+链表(解决哈希碰撞); jdk8及之后:数组+链表/红黑树 (当链表长度大于等于8且数组长度大于等于64,链表转换为红黑树,减少查询时间)

适用场景:

  • 通用键值对存储;
  • 无需顺序或排序的场景;
  • 单线程或低并发场景。

LinkedHashMap

核心特性:

  1. 继承HashMap :具备 HashMap 所有特性,单支持有序排列;
  2. 有序性:通过双向链表维护元素的插入顺序和访问顺序;
  3. 允许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 ;
  • 高性能:理想情况下并发读无锁,写操作仅缩并冲突的节点。

使用场景:

  • 高并发场景(多线程,计数器,分布式锁的本地缓存);
  • 需要线程安全且高性能的场景。