第54章

Java Map接口

深入学习HashMap、LinkedHashMap和TreeMap的特性与应用

学习目标

Map接口概述

Map接口是Java集合框架中的重要组成部分,它表示键值对(key-value)的映射关系。Map中的每个元素都包含一个唯一的键和对应的值,通过键可以快速查找到对应的值。

Map接口特点:
  • 键(Key)必须唯一,值(Value)可以重复
  • 一个键最多只能映射到一个值
  • 提供基于键的快速查找功能
  • 不继承Collection接口,是独立的接口

Map接口的核心方法

Map接口常用方法
// 基本操作
V put(K key, V value)           // 添加键值对
V get(Object key)               // 根据键获取值
V remove(Object key)            // 删除键值对
boolean containsKey(Object key) // 检查是否包含指定键
boolean containsValue(Object value) // 检查是否包含指定值
int size()                      // 获取元素个数
boolean isEmpty()               // 检查是否为空
void clear()                    // 清空所有元素

// 批量操作
void putAll(Map m) // 添加另一个Map的所有元素

// 视图操作
Set keySet()                 // 获取所有键的Set视图
Collection values()          // 获取所有值的Collection视图
Set> entrySet() // 获取所有键值对的Set视图

Map实现类对比

🗂️
HashMap
  • 基于哈希表实现
  • 查找时间复杂度O(1)
  • 不保证元素顺序
  • 允许null键和null值
  • 非线程安全
  • 适合快速查找场景
🔗
LinkedHashMap
  • 继承自HashMap
  • 维护插入或访问顺序
  • 使用双向链表
  • 查找性能略低于HashMap
  • 允许null键和null值
  • 适合需要保持顺序的场景
🌳
TreeMap
  • 基于红黑树实现
  • 自动排序键
  • 查找时间复杂度O(log n)
  • 不允许null键
  • 提供导航方法
  • 适合需要排序的场景

HashMap详解

HashMap是最常用的Map实现,基于哈希表数据结构,提供了快速的插入、删除和查找操作。

HashMap基本使用

HashMap基本操作示例
import java.util.*;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建HashMap
        Map<String, Integer> map = new HashMap<>();
        
        // 添加元素
        map.put("apple", 5);
        map.put("banana", 3);
        map.put("orange", 8);
        map.put("grape", 12);
        
        // 获取元素
        System.out.println("苹果数量: " + map.get("apple"));
        
        // 检查键是否存在
        if (map.containsKey("banana")) {
            System.out.println("有香蕉库存");
        }
        
        // 遍历Map
        System.out.println("\n所有水果库存:");
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        
        // 使用forEach遍历(Java 8+)
        System.out.println("\n使用forEach遍历:");
        map.forEach((fruit, count) -> 
            System.out.println(fruit + " => " + count)
        );
        
        // 更新值
        map.put("apple", map.get("apple") + 2); // 增加苹果数量
        
        // 使用merge方法更新
        map.merge("banana", 5, Integer::sum); // 如果键存在则相加,否则设置新值
        
        System.out.println("\n更新后的苹果数量: " + map.get("apple"));
        System.out.println("更新后的香蕉数量: " + map.get("banana"));
    }
}
💻 查看完整代码 - 在线IDE体验

LinkedHashMap详解

LinkedHashMap继承自HashMap,额外维护了一个双向链表来保持元素的插入顺序或访问顺序。

LinkedHashMap顺序保持示例
import java.util.*;

public class LinkedHashMapExample {
    public static void main(String[] args) {
        // 创建保持插入顺序的LinkedHashMap
        Map<String, String> insertionOrder = new LinkedHashMap<>();
        insertionOrder.put("first", "第一个");
        insertionOrder.put("second", "第二个");
        insertionOrder.put("third", "第三个");
        
        System.out.println("插入顺序遍历:");
        insertionOrder.forEach((k, v) -> System.out.println(k + " = " + v));
        
        // 创建按访问顺序排列的LinkedHashMap
        Map<String, String> accessOrder = new LinkedHashMap<>(16, 0.75f, true);
        accessOrder.put("A", "值A");
        accessOrder.put("B", "值B");
        accessOrder.put("C", "值C");
        
        // 访问元素A,它会移到最后
        accessOrder.get("A");
        
        System.out.println("\n访问顺序遍历(访问A后):");
        accessOrder.forEach((k, v) -> System.out.println(k + " = " + v));
    }
}

TreeMap详解

TreeMap基于红黑树实现,能够自动对键进行排序,并提供了丰富的导航方法。

TreeMap排序和导航示例
import java.util.*;

public class TreeMapExample {
    public static void main(String[] args) {
        // 创建TreeMap,自动按键排序
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(3, "三");
        treeMap.put(1, "一");
        treeMap.put(4, "四");
        treeMap.put(2, "二");
        treeMap.put(5, "五");
        
        System.out.println("自动排序后的遍历:");
        treeMap.forEach((k, v) -> System.out.println(k + " = " + v));
        
        // 导航方法
        System.out.println("\n导航方法演示:");
        System.out.println("第一个键: " + treeMap.firstKey());
        System.out.println("最后一个键: " + treeMap.lastKey());
        System.out.println("小于3的最大键: " + treeMap.lowerKey(3));
        System.out.println("大于3的最小键: " + treeMap.higherKey(3));
        
        // 子映射
        System.out.println("\n子映射(键在2到4之间):");
        SortedMap<Integer, String> subMap = treeMap.subMap(2, 5);
        subMap.forEach((k, v) -> System.out.println(k + " = " + v));
        
        // 自定义比较器
        TreeMap<String, Integer> customOrder = new TreeMap<>(
            (s1, s2) -> s2.compareTo(s1) // 逆序排列
        );
        customOrder.put("apple", 5);
        customOrder.put("banana", 3);
        customOrder.put("cherry", 8);
        
        System.out.println("\n自定义逆序排列:");
        customOrder.forEach((k, v) -> System.out.println(k + " = " + v));
    }
}

性能对比

操作 HashMap LinkedHashMap TreeMap
插入(put) O(1) O(1) O(log n)
查找(get) O(1) O(1) O(log n)
删除(remove) O(1) O(1) O(log n)
遍历 O(n) O(n) O(n)
空间复杂度 O(n) O(n) + 链表开销 O(n) + 树结构开销
性能选择建议:
  • HashMap:需要最快的查找性能,不关心元素顺序
  • LinkedHashMap:需要保持插入顺序或实现LRU缓存
  • TreeMap:需要有序的键值对或范围查询功能

Map使用最佳实践

编程最佳实践

推荐做法

  • 使用接口类型声明变量:Map<K,V> map = new HashMap<>();
  • 重写equals()和hashCode()方法作为键
  • 使用不可变对象作为键
  • 合理设置初始容量避免频繁扩容
  • 使用computeIfAbsent()等现代API
  • 在多线程环境使用ConcurrentHashMap

避免做法

  • 使用可变对象作为键
  • 在TreeMap中使用null键
  • 忽略equals()和hashCode()的一致性
  • 在遍历时修改Map结构
  • 过度使用TreeMap导致性能问题
  • 在多线程环境使用HashMap

实际应用场景

Map应用场景示例
import java.util.*;

public class MapApplications {
    
    // 1. 缓存实现(使用LinkedHashMap)
    public static class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private final int capacity;
        
        public LRUCache(int capacity) {
            super(capacity, 0.75f, true);
            this.capacity = capacity;
        }
        
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > capacity;
        }
    }
    
    // 2. 统计词频(使用HashMap)
    public static Map<String, Integer> countWords(String text) {
        Map<String, Integer> wordCount = new HashMap<>();
        String[] words = text.toLowerCase().split("\\s+");
        
        for (String word : words) {
            wordCount.merge(word, 1, Integer::sum);
        }
        
        return wordCount;
    }
    
    // 3. 配置管理(使用TreeMap保持有序)
    public static class ConfigManager {
        private TreeMap<String, String> configs = new TreeMap<>();
        
        public void setConfig(String key, String value) {
            configs.put(key, value);
        }
        
        public String getConfig(String key) {
            return configs.get(key);
        }
        
        public void printAllConfigs() {
            configs.forEach((k, v) -> 
                System.out.println(k + " = " + v)
            );
        }
    }
    
    public static void main(String[] args) {
        // 测试LRU缓存
        LRUCache<String, String> cache = new LRUCache<>(3);
        cache.put("1", "one");
        cache.put("2", "two");
        cache.put("3", "three");
        cache.put("4", "four"); // 会移除最久未使用的"1"
        
        System.out.println("LRU缓存内容: " + cache);
        
        // 测试词频统计
        String text = "hello world hello java world";
        Map<String, Integer> wordCount = countWords(text);
        System.out.println("\n词频统计: " + wordCount);
        
        // 测试配置管理
        ConfigManager config = new ConfigManager();
        config.setConfig("database.url", "jdbc:mysql://localhost:3306/test");
        config.setConfig("app.name", "MyApp");
        config.setConfig("cache.size", "1000");
        
        System.out.println("\n配置信息:");
        config.printAllConfigs();
    }
}

章节总结