Java TreeMap与HashTable深度解析:有序映射与线程安全映射

一、TreeMap集合深度解析

1.1 TreeMap的核心特点

数据结构本质
TreeMap是基于红黑树实现的有序键值对集合,它保证了元素按照键的自然顺序或自定义顺序进行排序。
核心特性详解
  1. 有序性保证
  • 所有元素按照键的顺序排列
  • 支持自然排序和定制排序两种方式
  • 提供了一系列基于顺序的操作方法
  1. 性能特征
  • 查询、插入、删除操作的时间复杂度:O(log n)
  • 相对于HashMap,插入和删除稍慢,但有序性提供额外功能
  • 自动维护红黑树的平衡
  1. 键的要求
  • 键对象必须实现Comparable接口,或在构造时提供Comparator
  • 键不能为null(会抛出NullPointerException)
  • 值可以为null

1.2 TreeMap基本使用

① 自然排序示例
import java.util.Map;
import java.util.TreeMap;

public class TreeMapNaturalOrder {
    public static void main(String[] args) {
        System.out.println("=== TreeMap自然排序演示 ===");
        
        // String类型的自然排序(按字母表顺序)
        Map<String, Integer> stringMap = new TreeMap<>();
        stringMap.put("orange", 1);
        stringMap.put("apple", 2);
        stringMap.put("pear", 3);
        System.out.println("String键排序: " + stringMap);
        // 输出: {apple=2, orange=1, pear=3}
        
        // Integer类型的自然排序(按数字升序)
        Map<Integer, String> integerMap = new TreeMap<>();
        integerMap.put(3, "val3");
        integerMap.put(2, "val2");
        integerMap.put(1, "val1");
        integerMap.put(5, "val5");
        integerMap.put(4, "val4");
        System.out.println("Integer键排序: " + integerMap);
        // 输出: {1=val1, 2=val2, 3=val3, 4=val4, 5=val5}
        
        // 验证有序性
        System.out.println("第一个键: " + ((TreeMap<Integer, String>) integerMap).firstKey());
        System.out.println("最后一个键: " + ((TreeMap<Integer, String>) integerMap).lastKey());
    }
}
② 自定义排序 - Comparable接口
import java.util.*;

// 自定义类实现Comparable接口
class Student implements Comparable<Student> {
    private String name;
    private int age;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int compareTo(Student other) {
        // 先按年龄排序,年龄相同按姓名排序
        int ageCompare = Integer.compare(this.age, other.age);
        return ageCompare != 0 ? ageCompare : this.name.compareTo(other.name);
    }
    
    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
    
    // 必须重写equals和hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

public class TreeMapComparable {
    public static void main(String[] args) {
        System.out.println("=== TreeMap Comparable接口演示 ===");
        
        Map<Student, String> studentMap = new TreeMap<>();
        studentMap.put(new Student("Tom", 20), "计算机科学");
        studentMap.put(new Student("Alice", 19), "数学");
        studentMap.put(new Student("Bob", 20), "物理");
        studentMap.put(new Student("Charlie", 18), "化学");
        
        System.out.println("按年龄和姓名排序:");
        studentMap.forEach((student, major) -> 
            System.out.println(student + " -> " + major));
    }
}
③ 自定义排序 - Comparator接口
import java.util.*;

class Person {
    public String name;
    public int salary;
    
    Person(String name, int salary) {
        this.name = name;
        this.salary = salary;
    }
    
    @Override
    public String toString() {
        return "{Person: " + name + ", salary: " + salary + "}";
    }
}

public class TreeMapComparator {
    public static void main(String[] args) {
        System.out.println("=== TreeMap Comparator自定义排序 ===");
        
        // 使用匿名内部类定义Comparator
        Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                // 按姓名排序
                return p1.name.compareTo(p2.name);
            }
        });
        
        map.put(new Person("Tom", 5000), 1);
        map.put(new Person("Bob", 6000), 2);
        map.put(new Person("Lily", 5500), 3);
        map.put(new Person("Alice", 7000), 4);
        
        System.out.println("按姓名排序:");
        for (Person key : map.keySet()) {
            System.out.println(key + " -> " + map.get(key));
        }
        
        // 使用Lambda表达式定义按工资排序
        Map<Person, Integer> salaryMap = new TreeMap<>(
            (p1, p2) -> Integer.compare(p1.salary, p2.salary)
        );
        
        salaryMap.put(new Person("Tom", 5000), 1);
        salaryMap.put(new Person("Bob", 6000), 2);
        salaryMap.put(new Person("Lily", 5500), 3);
        
        System.out.println("\n按工资排序:");
        salaryMap.forEach((person, value) -> 
            System.out.println(person + " -> " + value));
    }
}

1.3 TreeMap特有方法

import java.util.TreeMap;

public class TreeMapSpecialMethods {
    public static void main(String[] args) {
        System.out.println("=== TreeMap特有方法演示 ===");
        
        TreeMap<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(1, "One");
        treeMap.put(3, "Three");
        treeMap.put(5, "Five");
        treeMap.put(7, "Seven");
        treeMap.put(9, "Nine");
        
        System.out.println("原始Map: " + treeMap);
        
        // 第一个和最后一个元素
        System.out.println("第一个键: " + treeMap.firstKey());
        System.out.println("最后一个键: " + treeMap.lastKey());
        
        // 范围查询
        System.out.println("小于等于4的最大键: " + treeMap.floorKey(4)); // 3
        System.out.println("大于等于4的最小键: " + treeMap.ceilingKey(4)); // 5
        
        // 子Map
        System.out.println("3到7的子Map: " + treeMap.subMap(3, 7));
        System.out.println("小于5的Map: " + treeMap.headMap(5));
        System.out.println("大于等于5的Map: " + treeMap.tailMap(5));
        
        // 逆序
        System.out.println("逆序Map: " + treeMap.descendingMap());
    }
}

二、HashTable集合深度解析

2.1 HashTable的核心特点

数据结构特性
  • 基于哈希表实现键值对存储
  • 线程安全:所有方法都是同步的
  • 键值限制:键和值都不能为null
  • 性能特征:由于同步开销,性能低于HashMap
HashTable 的特点
├── 数据结构
│   ├── 基于哈希表实现
│   └── 键值对存储
├── 线程安全
│   ├── 所有方法都是同步的
│   └── 适合多线程环境
├── 键和值的限制
│   ├── 键和值都不能为 null
│   └── 键必须实现 hashCode() 和 equals()
├── 性能
│   ├── 查找、插入、删除的时间复杂度为 O(1)
│   └── 由于同步机制,性能低于 HashMap
├── 初始容量和负载因子
│   ├── 默认初始容量:11
│   └── 默认负载因子:0.75
├── 扩容机制
│   ├── 当元素数量超过容量 × 负载因子时扩容
│   └── 扩容规则:新容量 = 旧容量 × 2 + 1
├── 遍历方式
│   ├── 使用 Enumeration 遍历
│   └── 使用 Iterator 遍历
├── 与 HashMap 的区别
│   ├── HashTable 线程安全,HashMap 非线程安全
│   ├── HashTable 不允许 null 键和值,HashMap 允许
│   └── HashTable 性能较低,HashMap 性能较高
└── 使用场景
    ├── 多线程环境
    └── 需要线程安全的键值对存储
与HashMap的关键区别

特性

HashTable

HashMap

线程安全

是(同步方法)

null键值

不允许

允许

性能

较低

较高

继承关系

Dictionary

AbstractMap

迭代器

Enumeration

Iterator

2.2 HashTable基本操作

创建和基本操作
import java.util.Enumeration;
import java.util.Hashtable;

public class HashTableBasic {
    public static void main(String[] args) {
        System.out.println("=== HashTable基本操作 ===");
        
        // 创建HashTable的不同方式
        Hashtable<String, Integer> hashtable1 = new Hashtable<>(); // 默认容量11,负载因子0.75
        Hashtable<String, Integer> hashtable2 = new Hashtable<>(16); // 指定初始容量
        Hashtable<String, Integer> hashtable3 = new Hashtable<>(16, 0.8f); // 指定容量和负载因子
        
        // 添加元素
        Hashtable<String, Integer> hashtable = new Hashtable<>();
        hashtable.put("Apple", 10);
        hashtable.put("Banana", 20);
        hashtable.put("Cherry", 30);
        
        System.out.println("添加元素后: " + hashtable);
        
        // 尝试添加null值 - 会抛出NullPointerException
        try {
            hashtable.put("NullKey", null); // 抛出异常
        } catch (NullPointerException e) {
            System.out.println("不能添加null值: " + e.getMessage());
        }
        
        try {
            hashtable.put(null, 100); // 抛出异常
        } catch (NullPointerException e) {
            System.out.println("不能添加null键: " + e.getMessage());
        }
    }
}
访问和删除操作
public class HashTableAccess {
    public static void main(String[] args) {
        Hashtable<String, Integer> hashtable = new Hashtable<>();
        hashtable.put("Apple", 10);
        hashtable.put("Banana", 20);
        hashtable.put("Cherry", 30);
        hashtable.put("Orange", 40);
        
        System.out.println("初始HashTable: " + hashtable);
        
        // 访问元素
        int bananaQuantity = hashtable.get("Banana");
        System.out.println("Banana数量: " + bananaQuantity);
        
        // 访问不存在的键
        Integer unknown = hashtable.get("Unknown");
        System.out.println("不存在的键返回值: " + unknown);
        
        // 删除元素
        Integer removed = hashtable.remove("Cherry");
        System.out.println("删除的元素值: " + removed);
        System.out.println("删除后: " + hashtable);
        
        // 检查存在性
        boolean hasApple = hashtable.containsKey("Apple");
        boolean hasValue20 = hashtable.containsValue(20);
        System.out.println("包含Apple键: " + hasApple);
        System.out.println("包含值20: " + hasValue20);
        
        // 获取大小
        System.out.println("HashTable大小: " + hashtable.size());
    }
}

2.3 HashTable遍历方式

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

public class HashTableIteration {
    public static void main(String[] args) {
        System.out.println("=== HashTable遍历方式 ===");
        
        Hashtable<String, Integer> hashtable = new Hashtable<>();
        hashtable.put("Apple", 10);
        hashtable.put("Banana", 20);
        hashtable.put("Cherry", 30);
        hashtable.put("Orange", 40);
        
        // 1. 使用Enumeration遍历键(传统方式)
        System.out.println("1. Enumeration遍历键:");
        Enumeration<String> keys = hashtable.keys();
        while (keys.hasMoreElements()) {
            String key = keys.nextElement();
            System.out.println("Key: " + key + ", Value: " + hashtable.get(key));
        }
        
        // 2. 使用Enumeration遍历值
        System.out.println("\n2. Enumeration遍历值:");
        Enumeration<Integer> values = hashtable.elements();
        while (values.hasMoreElements()) {
            System.out.println("Value: " + values.nextElement());
        }
        
        // 3. 使用EntrySet遍历(推荐)
        System.out.println("\n3. EntrySet遍历:");
        for (Map.Entry<String, Integer> entry : hashtable.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
        
        // 4. 使用KeySet遍历
        System.out.println("\n4. KeySet遍历:");
        for (String key : hashtable.keySet()) {
            System.out.println("Key: " + key + ", Value: " + hashtable.get(key));
        }
        
        // 5. 使用forEach(Java 8+)
        System.out.println("\n5. forEach遍历:");
        hashtable.forEach((key, value) -> 
            System.out.println("Key: " + key + ", Value: " + value));
    }
}

2.4 HashTable线程安全演示

import java.util.Hashtable;

public class HashTableThreadSafety {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== HashTable线程安全演示 ===");
        
        Hashtable<String, Integer> sharedHashtable = new Hashtable<>();
        
        // 创建写入任务
        Runnable writeTask = () -> {
            String threadName = Thread.currentThread().getName();
            for (int i = 0; i < 100; i++) {
                String key = threadName + "-" + i;
                sharedHashtable.put(key, i);
                System.out.println(threadName + " 写入: " + key);
            }
        };
        
        // 创建读取任务
        Runnable readTask = () -> {
            String threadName = Thread.currentThread().getName();
            for (int i = 0; i < 50; i++) {
                int size = sharedHashtable.size();
                System.out.println(threadName + " 读取大小: " + size);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        
        // 启动多个线程
        Thread writer1 = new Thread(writeTask, "Writer-1");
        Thread writer2 = new Thread(writeTask, "Writer-2");
        Thread reader1 = new Thread(readTask, "Reader-1");
        Thread reader2 = new Thread(readTask, "Reader-2");
        
        writer1.start();
        writer2.start();
        reader1.start();
        reader2.start();
        
        // 等待所有线程完成
        writer1.join();
        writer2.join();
        reader1.join();
        reader2.join();
        
        System.out.println("最终HashTable大小: " + sharedHashtable.size());
        System.out.println("数据一致性检查完成");
    }
}

2.5 HashTable容量管理

public class HashTableCapacity {
    public static void main(String[] args) {
        System.out.println("=== HashTable容量管理 ===");
        
        // 创建指定容量的HashTable
        Hashtable<String, Integer> hashtable = new Hashtable<>(5, 0.5f);
        
        System.out.println("初始容量相关信息:");
        System.out.println("大小: " + hashtable.size());
        // 注意:HashTable没有公开的capacity()方法
        
        // 添加元素直到触发扩容
        for (int i = 0; i < 10; i++) {
            hashtable.put("Key" + i, i);
            System.out.println("添加Key" + i + "后,大小: " + hashtable.size());
        }
        
        System.out.println("最终HashTable: " + hashtable);
        
        // 清空操作
        hashtable.clear();
        System.out.println("清空后大小: " + hashtable.size());
        System.out.println("是否为空: " + hashtable.isEmpty());
    }
}

三、集合选择指南

3.1 三大Map实现类对比

特性

HashMap

TreeMap

HashTable

数据结构

数组+链表/红黑树

红黑树

哈希表

排序

无序

按键排序

无序

线程安全

null键值

允许

键不能为null

都不允许

性能

O(1)平均

O(log n)

O(1)平均

使用场景

大多数场景

需要排序

多线程环境

3.2 选择策略

选择HashMap:
  • 大多数单线程场景
  • 不需要排序
  • 允许null键值
  • 追求最佳性能
选择TreeMap:
  • 需要按键排序
  • 需要范围查询等有序操作
  • 键对象有自然顺序或可比较
选择HashTable:
  • 简单的多线程场景
  • 遗留系统维护
  • 不需要null键值

3.3 现代替代方案

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ModernAlternatives {
    public static void main(String[] args) {
        System.out.println("=== HashTable的现代替代方案 ===");
        
        // 1. 需要线程安全时使用ConcurrentHashMap
        Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
        concurrentMap.put("A", 1);
        concurrentMap.put("B", 2);
        System.out.println("ConcurrentHashMap: " + concurrentMap);
        
        // 2. 使用Collections.synchronizedMap包装HashMap
        Map<String, Integer> synchronizedMap = 
            Collections.synchronizedMap(new HashMap<>());
        synchronizedMap.put("X", 10);
        synchronizedMap.put("Y", 20);
        System.out.println("synchronizedMap: " + synchronizedMap);
        
        // 3. 需要排序时使用TreeMap
        Map<String, Integer> treeMap = new TreeMap<>();
        treeMap.put("Z", 3);
        treeMap.put("A", 1);
        treeMap.put("M", 2);
        System.out.println("TreeMap(自动排序): " + treeMap);
    }
}

总结

TreeMap和HashTable各自在特定的使用场景下发挥着重要作用:
  • TreeMap 提供了有序的键值对存储,适合需要排序和范围查询的场景
  • HashTable 提供了线程安全的键值对存储,但在现代Java开发中通常被ConcurrentHashMap替代
理解这些集合的特性和适用场景,能够帮助开发者在实际项目中做出更加合适的技术选型,编写出既高效又健壮的代码。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值