Skip to content

Java Map集合完全指南

一、核心知识点

1. Map集合概述

1.1 基本概念

  • 定义:双列集合的顶级接口
  • 特点:一个元素由两部分构成(key和value,即键值对)
  • 实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

1.2 集合体系结构

Map集合体系结构

2. HashMap详解

2.1 基本特点

  • 无序:存取顺序不一致
  • 无索引:不能通过索引访问元素
  • key唯一:key不能重复,value可以重复
  • 线程不安全:多线程环境下需要同步处理
  • 可存储null:允许存储null键和null值
  • 数据结构:哈希表(数组+链表+红黑树)

2.2 常用方法

方法说明
V put(K key, V value)添加元素,返回被替换的value值
V remove(Object key)根据key删除键值对,返回被删除的value
V get(Object key)根据key获取value
boolean containsKey(Object key)判断集合中是否包含指定的key
Collection<V> values()获取集合中所有的value,转存到Collection集合中
Set<K> keySet()将Map中的key获取出来,转存到Set集合中
Set<Map.Entry<K,V>> entrySet()获取Map集合中的键值对,转存到Set集合中

2.3 代码示例

java
@Test
public void test01() {
    HashMap<String, String> map = new HashMap<>();
    map.put("1","张三");
    map.put("2","李四");
    map.put("2", "王五");
    map.put("3", "赵六");
    map.put("4", "田七");
    map.put("5", "朱八");
    System.out.println(map);
    
    String value = map.remove("1");
    System.out.println("被删除的value: " + value);
    System.out.println(map);
    
    String value1 = map.get("2");
    System.out.println("key为2的value: " + value1);
    
    boolean b = map.containsKey("2");
    System.out.println("是否包含key为2: " + b);
    
    Collection<String> values = map.values();
    System.out.println("所有的value: " + values);
}

3. LinkedHashMap

3.1 基本特点

  • 有序:存取顺序一致
  • 无索引:不能通过索引访问元素
  • key唯一:key不能重复,value可以重复
  • 线程不安全:多线程环境下需要同步处理
  • 数据结构:哈希表 + 双向链表

3.2 使用示例

java
@Test
public void test02() {
    LinkedHashMap<String, String> map = new LinkedHashMap<>();
    map.put("1","张三");
    map.put("2","李四");
    map.put("2", "王五");
    map.put("5", "朱八");
    map.put("4", "田七");
    map.put("3", "赵六");
    System.out.println(map);
}

4. HashMap的两种遍历方式

4.1 方式一:通过keySet遍历

java
@Test
public void test03() {
    HashMap<String, String> map = new HashMap<>();
    map.put("大郎","金莲");
    map.put("岩朔","王婆");
    map.put("硕鑫","雨姐");
    
    Set<String> set = map.keySet();
    for (String key : set) {
        String value = map.get(key);
        System.out.println(key + "=" + value);
    }
}

4.2 方式二:通过entrySet遍历(推荐)

entrySet原理图
java
@Test
public void test04() {
    HashMap<String, String> map = new HashMap<>();
    map.put("大郎","金莲");
    map.put("岩朔","王婆");
    map.put("硕鑫","雨姐");
    
    Set<Map.Entry<String, String>> set = map.entrySet();
    for (Map.Entry<String, String> entry : set) {
        String key = entry.getKey();
        String value = entry.getValue();
        System.out.println(key + "=" + value);
    }
}

5. Map存储自定义对象保证key唯一

5.1 原理说明

  • Set集合存储元素都是存到了Map集合的key的位置
  • key需要重写hashCode和equals方法
  • 去重复过程:
    1. 先比较key的哈希值
    2. 再比较key的内容
    3. 如果哈希值不一样,存
    4. 如果哈希值一样,内容不一样,存
    5. 如果哈希值一样,内容也一样,去重复(value覆盖)

5.2 代码示例

java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private Integer age;
}

@Test
public void test05() {
    HashMap<Person, String> map = new HashMap<>();
    map.put(new Person("涛哥", 18), "廊坊");
    map.put(new Person("硕鑫", 20), "济南");
    map.put(new Person("岩朔",22), "通辽");
    map.put(new Person("彭思",16),"辽宁");
    map.put(new Person("彭思",16),"北京");
    System.out.println(map);
}

6. TreeSet

6.1 基本特点

  • 可排序:可以对元素进行排序
  • 无索引:不能通过索引访问元素
  • 元素唯一:元素不可重复
  • 线程不安全:多线程环境下需要同步处理
  • 数据结构:红黑树

6.2 构造方法

  • TreeSet():对元素进行自然排序(ASCII码值)
  • TreeSet(Comparator<? super E> comparator):按照指定规则排序

6.3 使用示例

java
@Test
public void test01() {
    TreeSet<String> set = new TreeSet<>();
    set.add("b.曲项向天歌");
    set.add("a.鹅鹅鹅");
    set.add("d.红掌拨清波");
    set.add("c.白毛浮绿水");
    System.out.println(set);
}

@Test
public void test02() {
    TreeSet<Person> set = new TreeSet<>(new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            return o1.getAge()-o2.getAge();
        }
    });
    set.add(new Person("张三", 18));
    set.add(new Person("李四", 15));
    set.add(new Person("王五", 20));
    System.out.println(set);
}

7. TreeMap

7.1 基本特点

  • 可排序:可以对key进行排序
  • 无索引:不能通过索引访问元素
  • key唯一:key不能重复,value可以重复
  • 线程不安全:多线程环境下需要同步处理
  • 数据结构:红黑树

7.2 构造方法

  • TreeMap():将key按照自然排序(ASCII码)
  • TreeMap(Comparator<? super K> comparator):将key按照指定的顺序排序

7.3 使用示例

java
@Test
public void test01() {
    TreeMap<String, String> map = new TreeMap<>();
    map.put("b", "汗滴禾下土");
    map.put("a", "锄禾日当午");
    map.put("c", "谁知盘中餐");
    map.put("d", "粒粒皆辛苦");
    System.out.println(map);
}

@Test
public void test02() {
    TreeMap<Person, String> map = new TreeMap<>(new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            return o2.getAge()-o1.getAge();
        }
    });
    map.put(new Person("张三", 18), "杭州");
    map.put(new Person("李四", 15), "上海");
    map.put(new Person("王五", 20), "北京");
    System.out.println(map);
}

8. Hashtable(了解)

8.1 基本特点

  • 无序:存取顺序不一致
  • 无索引:不能通过索引访问元素
  • key唯一:key不能重复,value可以重复
  • 线程安全:多线程环境下安全
  • 不能存null:不允许存储null键和null值
  • 数据结构:哈希表

8.2 使用示例

java
@Test
public void test01() {
    Hashtable<String, String> table = new Hashtable<>();
    table.put("1", "张三");
    table.put("2", "李四");
    table.put("3", "王五");
    System.out.println(table);
}

9. Properties集合

9.1 基本特点

  • 概述:Hashtable的子类
  • 无序:存取顺序不一致
  • 无索引:不能通过索引访问元素
  • key唯一:key不能重复,value可以重复
  • 线程安全:多线程环境下安全
  • 固定类型:key和value固定为String
  • 数据结构:哈希表

9.2 特有方法

方法说明
setProperty(String key, String value)存键值对
String getProperty(String key)根据key获取value
Set<String> stringPropertyNames()获取所有的key保存到set集合中
void load(InputStream inStream)将流中的数据加载到properties集合中

9.3 使用场景

用于解析配置文件(xxx.properties、xxx.xml、xxx.yml)

配置文件说明

  • 存放"硬数据"的文件(用户名、密码、地址等)
  • 数据以key=value形式存储
  • 一个键值对写完需要换行
  • key和value都是字符串,不要加""
  • 尽量不要写中文

9.4 代码示例

基本使用

java
@Test
public void test01() {
    Properties properties = new Properties();
    properties.setProperty("username", "tom");
    properties.setProperty("password", "123456");

    Set<String> set = properties.stringPropertyNames();
    for (String key : set) {
        String value = properties.getProperty(key);
        System.out.println(key + "=" + value);
    }
}

读取配置文件

配置文件内容(pro.properties):

properties
username=root
password=1234

代码实现:

java
@Test
public void test02() throws Exception {
    Properties properties = new Properties();
    InputStream in = Demo01Properties.class.getClassLoader().getResourceAsStream("pro.properties");
    properties.load(in);
    
    String username = properties.getProperty("username");
    String password = properties.getProperty("password");
    System.out.println(username + ":" + password);
}

10. 集合嵌套

10.1 List嵌套List

java
@Test
public void listInList() {
    ArrayList<String> list1 = new ArrayList<>();
    list1.add("张三");
    list1.add("李四");

    ArrayList<String> list2 = new ArrayList<>();
    list2.add("王五");
    list2.add("赵六");

    ArrayList<ArrayList<String>> list = new ArrayList<>();
    list.add(list1);
    list.add(list2);

    for (ArrayList<String> arrayList : list) {
        for (String s : arrayList) {
            System.out.println(s);
        }
    }
}

10.2 List嵌套Map

java
@Test
public void listInMap() {
    HashMap<Integer, String> map1 = new HashMap<>();
    map1.put(1, "张三");
    map1.put(2, "李四");

    HashMap<Integer, String> map2 = new HashMap<>();
    map2.put(3, "王五");
    map2.put(4, "赵六");

    ArrayList<HashMap<Integer, String>> list = new ArrayList<>();
    list.add(map1);
    list.add(map2);

    for (HashMap<Integer, String> map : list) {
        Set<Map.Entry<Integer, String>> set = map.entrySet();
        for (Map.Entry<Integer, String> entry : set) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

二、重点难点

1. HashMap与Hashtable的区别 ⭐⭐⭐

对比项HashMapHashtable
线程安全线程不安全线程安全
null处理可以存储null键null值不能存储null键null值
效率效率高效率低
使用场景单线程环境多线程环境

相同点

  • 元素无序
  • 无索引
  • key唯一
  • 数据结构都是哈希表

2. 各集合特点对比

List系列

  • ArrayList:元素有序、有索引、元素可重复、线程不安全、数据结构为数组
  • LinkedList:元素有序、无索引、元素可重复、线程不安全、数据结构为双向链表
  • Vector:元素有序、有索引、元素可重复、线程安全、数据结构为数组

Set系列

  • HashSet:元素无序、无索引、元素唯一、线程不安全、数据结构为哈希表
  • LinkedHashSet:元素有序、无索引、元素唯一、线程不安全、数据结构为哈希表+双向链表
  • TreeSet:对元素排序、无索引、元素唯一、线程不安全、数据结构为红黑树

Map系列

  • HashMap:无序、无索引、key唯一、线程不安全、可存null、数据结构为哈希表
  • LinkedHashMap:有序、无索引、key唯一、线程不安全、数据结构为哈希表+双向链表
  • TreeMap:可对key排序、无索引、key唯一、线程不安全、数据结构为红黑树
  • Hashtable:无序、无索引、key唯一、线程安全、不能存null、数据结构为哈希表

3. 哈希表存储元素细节 ⭐⭐⭐

存储流程

  1. 先计算key的哈希值
  2. 再比较key的内容
  3. 如果哈希值不一样,直接存储
  4. 如果哈希值一样,内容不一样,存储
  5. 如果哈希值一样,内容也一样,去重复(value覆盖)

重要提示

  • 存储自定义对象作为key时,必须重写hashCode和equals方法
  • Set集合保证元素唯一和Map集合保证key唯一的方式相同

三、常见面试题

1. HashMap的底层原理是什么?⭐⭐⭐

答案: HashMap的底层数据结构是哈希表,JDK 1.8之前是数组+链表,JDK 1.8之后是数组+链表+红黑树。

工作原理

  1. 当调用put方法时,首先计算key的hash值
  2. 根据hash值确定数组中的位置
  3. 如果该位置没有元素,直接存储
  4. 如果该位置有元素(哈希冲突),遍历链表
  5. JDK 1.8优化:当链表长度超过8时,转换为红黑树,提高查询效率

2. HashMap和Hashtable的区别?⭐⭐⭐

答案

对比项HashMapHashtable
线程安全线程不安全线程安全(使用synchronized)
null处理允许null键和null值不允许null键和null值
效率高(无同步开销)低(有同步开销)
使用场景单线程环境多线程环境
推荐度推荐不推荐(使用ConcurrentHashMap替代)

3. 如何保证HashMap的key唯一?⭐⭐

答案

  • 自定义类作为key时,必须重写hashCode()和equals()方法
  • 先比较哈希值,再比较内容
  • 如果哈希值不同,直接存储
  • 如果哈希值相同但内容不同,存储
  • 如果哈希值和内容都相同,覆盖value

4. HashMap的遍历方式有哪些?哪种效率更高?⭐⭐

答案

方式一:通过keySet遍历

java
Set<K> keySet = map.keySet();
for (K key : keySet) {
    V value = map.get(key);
}

方式二:通过entrySet遍历(推荐)

java
Set<Map.Entry<K,V>> entrySet = map.entrySet();
for (Map.Entry<K,V> entry : entrySet) {
    K key = entry.getKey();
    V value = entry.getValue();
}

效率对比

  • entrySet效率更高,因为只需要一次查询
  • keySet需要两次查询:先获取key,再根据key获取value

5. TreeSet和TreeMap的排序方式有哪些?⭐⭐

答案

自然排序

  • 元素类实现Comparable接口
  • 重写compareTo方法
  • 使用无参构造:new TreeSet<>()

比较器排序

  • 创建Comparator实现类
  • 重写compare方法
  • 使用有参构造:new TreeSet<>(comparator)

6. Properties集合的使用场景是什么?⭐

答案

  • 读取配置文件(.properties、.xml、.yml)
  • 存储应用程序配置信息(数据库连接、系统参数等)
  • 实现软编码,避免硬编码
  • 方便修改配置,无需修改源代码

7. LinkedHashMap如何保证有序?⭐⭐

答案

  • LinkedHashMap继承自HashMap
  • 在HashMap基础上维护了一个双向链表
  • 记录元素的插入顺序或访问顺序
  • 遍历时按照链表顺序输出

8. HashMap的扩容机制是什么?⭐⭐⭐

答案

  • 默认初始容量:16
  • 默认负载因子:0.75
  • 扩容阈值:容量 × 负载因子 = 16 × 0.75 = 12
  • 当元素个数超过阈值时,扩容为原来的2倍
  • 扩容后需要重新计算元素位置(rehash)

四、实用代码片段

1. 统计字符串中每个字符出现的次数

字符统计流程图
java
@Test
public void test06() {
    String s = "adfasdfas";
    HashMap<String, Integer> map = new HashMap<>();
    char[] chars = s.toCharArray();
    for (char c : chars) {
        String key = c + "";
        if (!map.containsKey(key)) {
            map.put(key, 1);
        } else {
            Integer value = map.get(key);
            value++;
            map.put(key, value);
        }
    }
    System.out.println(map);
}

2. 使用Properties读取配置文件

java
public class ConfigReader {
    private Properties properties = new Properties();
    
    public void loadConfig(String fileName) throws IOException {
        InputStream in = getClass().getClassLoader().getResourceAsStream(fileName);
        properties.load(in);
    }
    
    public String getProperty(String key) {
        return properties.getProperty(key);
    }
}

3. HashMap的线程安全处理

java
public class ThreadSafeMap<K, V> {
    private final Map<K, V> map = new HashMap<>();
    
    public synchronized void put(K key, V value) {
        map.put(key, value);
    }
    
    public synchronized V get(K key) {
        return map.get(key);
    }
    
    public synchronized V remove(K key) {
        return map.remove(key);
    }
}

4. 使用TreeMap实现排序

java
@Test
public void sortedMap() {
    TreeMap<Integer, String> map = new TreeMap<>(Collections.reverseOrder());
    map.put(3, "C");
    map.put(1, "A");
    map.put(2, "B");
    System.out.println(map);
}

五、经典练习题

练习题1:Map集合统计字符出现次数

题目:用Map集合统计字符串中每一个字符出现的次数

要求

  1. 指定一个字符串
  2. 创建一个map集合,key为String代表字符,value为Integer代表字符个数
  3. 遍历字符串,用每一个字符去判断,Map中是否包含字符
  4. 如果不包含,将字符和1存到map中
  5. 如果包含,根据字符将对应的value获取出来,让其+1,并重新存入
  6. 输出map

参考答案:见上文"实用代码片段"第1条

练习题2:List嵌套Map

题目:1班有三名同学,学号和姓名分别为:1=张三,2=李四,3=王五;2班有三名同学,学号和姓名分别为:1=黄晓明,2=杨颖,3=刘德华。请将同学的信息以键值对的形式存储到2个Map集合中,再将2个Map集合存储到List集合中。

参考答案:见上文"集合嵌套"第10.2节

练习题3:Map嵌套Map(作业)

题目

  • JavaSE集合存储的是学号键,值学生姓名
    • 1(key) 张三(value)
    • 2 李四
  • JavaEE集合存储的是学号键,值学生姓名
    • 1 王五
    • 2 赵六

要求

  • 小map的key为学号,value为姓名
  • 大map的key为字符串(javase,javaee),value为小map

提示

java
HashMap<String, HashMap<Integer, String>> bigMap = new HashMap<>();

六、扩展知识

1. HashMap在JDK 1.8的优化

主要优化

  • 引入红黑树:当链表长度超过8时,转换为红黑树
  • 提高查询效率:从O(n)提升到O(log n)
  • 减少哈希冲突的影响

2. ConcurrentHashMap(线程安全的HashMap)

特点

  • 线程安全
  • 效率比Hashtable高
  • JDK 1.8使用CAS + synchronized
  • 推荐在多线程环境下使用

3. 集合选择原则

根据需求选择集合

  • 需要排序:TreeSet、TreeMap
  • 需要有序:LinkedHashMap、LinkedHashSet
  • 需要线程安全:Hashtable、Vector、ConcurrentHashMap
  • 需要效率:HashMap、ArrayList
  • 需要频繁增删:LinkedList

七、学习建议

1. 学习重点

  1. 掌握HashMap的底层原理和使用
  2. 理解哈希表的存储机制
  3. 掌握Map的两种遍历方式
  4. 理解TreeSet和TreeMap的排序机制
  5. 掌握Properties的使用场景

2. 面试准备

  1. 重点理解HashMap的底层原理
  2. 掌握HashMap和Hashtable的区别
  3. 理解哈希表存储元素的细节
  4. 掌握集合的选择原则
  5. 熟练编写Map的遍历代码

3. 实践建议

  1. 多写代码练习Map的使用
  2. 实现一个简单的HashMap
  3. 练习使用Properties读取配置文件
  4. 尝试解决实际业务问题
  5. 理解集合嵌套的应用场景

4. 常见错误

  1. 忘记重写hashCode和equals方法
  2. 使用可变对象作为key
  3. 在遍历时修改集合
  4. 忽略null值的处理
  5. 混淆HashMap和Hashtable的使用场景

八、知识图谱

Map集合
├── HashMap(哈希表)
│   ├── 无序、无索引、key唯一、线程不安全、可存null
│   ├── LinkedHashMap(哈希表+双向链表)
│   │   └── 有序、无索引、key唯一、线程不安全
│   └── 常用方法:put、get、remove、keySet、entrySet
├── TreeMap(红黑树)
│   ├── 可对key排序、无索引、key唯一、线程不安全
│   └── 排序方式:自然排序、比较器排序
├── Hashtable(哈希表)
│   ├── 无序、无索引、key唯一、线程安全、不能存null
│   └── Properties(属性集)
│       └── key和value固定为String,用于配置文件
└── 集合嵌套
    ├── List嵌套List
    ├── List嵌套Map
    └── Map嵌套Map