一、TreeMap集合深度解析
1.1 TreeMap的核心特点
数据结构本质
TreeMap是基于红黑树实现的有序键值对集合,它保证了元素按照键的自然顺序或自定义顺序进行排序。
核心特性详解
- 有序性保证
- 所有元素按照键的顺序排列
- 支持自然排序和定制排序两种方式
- 提供了一系列基于顺序的操作方法
- 性能特征
- 查询、插入、删除操作的时间复杂度:O(log n)
- 相对于HashMap,插入和删除稍慢,但有序性提供额外功能
- 自动维护红黑树的平衡
- 键的要求
- 键对象必须实现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替代
理解这些集合的特性和适用场景,能够帮助开发者在实际项目中做出更加合适的技术选型,编写出既高效又健壮的代码。
868

被折叠的 条评论
为什么被折叠?



