一、Map与Set概述
1.1 基本概念
Map和Set是Java集合框架中专门用于高效搜索的数据结构:
- Map:存储键值对(key-value)的映射关系
- Set:存储唯一元素的集合(纯key模型)
1.2 核心实现类
| 接口 | Tree实现 | Hash实现 |
|---|---|---|
| Map | TreeMap | HashMap |
| Set | TreeSet | HashSet |
二、Map接口详解
2.1 Map的基本操作
创建Map对象
java
import java.util.*;
public class MapDemo {
public static void main(String[] args) {
// 创建HashMap (无序)
Map<String, Integer> hashMap = new HashMap<>();
// 创建TreeMap (按键排序)
Map<String, Integer> treeMap = new TreeMap<>();
// 添加元素
hashMap.put("Alice", 25);
hashMap.put("Bob", 30);
hashMap.put("Charlie", 28);
treeMap.put("Alice", 25);
treeMap.put("Bob", 30);
treeMap.put("Charlie", 28);
System.out.println("HashMap: " + hashMap);
System.out.println("TreeMap: " + treeMap);
}
}
核心方法演示
java
public class MapOperations {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 1. put() - 添加或更新键值对
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 28);
map.put("Alice", 26); // 更新Alice的值
System.out.println("初始Map: " + map);
// 2. get() - 获取值
Integer age = map.get("Bob");
System.out.println("Bob的年龄: " + age);
// 3. getOrDefault() - 安全获取
Integer unknownAge = map.getOrDefault("David", 0);
System.out.println("David的年龄(默认): " + unknownAge);
// 4. remove() - 删除键值对
map.remove("Charlie");
System.out.println("删除Charlie后: " + map);
// 5. containsKey() - 检查键是否存在
boolean hasAlice = map.containsKey("Alice");
System.out.println("是否包含Alice: " + hasAlice);
// 6. containsValue() - 检查值是否存在
boolean hasAge30 = map.containsValue(30);
System.out.println("是否包含年龄30: " + hasAge30);
// 7. size() - 获取大小
System.out.println("Map大小: " + map.size());
// 8. isEmpty() - 判断是否为空
System.out.println("Map是否为空: " + map.isEmpty());
// 9. clear() - 清空Map
map.clear();
System.out.println("清空后Map: " + map);
}
}
2.2 Map的遍历方式
方法1:keySet()遍历
java
public class MapIteration {
public static void keySetIteration(Map<String, Integer> map) {
System.out.println("=== keySet()遍历 ===");
Set<String> keys = map.keySet();
for (String key : keys) {
Integer value = map.get(key);
System.out.println("Key: " + key + ", Value: " + value);
}
}
}
方法2:values()遍历
java
public static void valuesIteration(Map<String, Integer> map) {
System.out.println("=== values()遍历 ===");
Collection<Integer> values = map.values();
for (Integer value : values) {
System.out.println("Value: " + value);
}
}
方法3:entrySet()遍历(推荐)
java
public static void entrySetIteration(Map<String, Integer> map) {
System.out.println("=== entrySet()遍历 ===");
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// 方式1: 增强for循环
for (Map.Entry<String, Integer> entry : entries) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
// 方式2: 迭代器
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
// 方式3: forEach + Lambda表达式 (Java 8+)
map.forEach((key, value) ->
System.out.println("Key: " + key + ", Value: " + value));
}
方法4:Java 8 Stream API
java
public static void streamIteration(Map<String, Integer> map) {
System.out.println("=== Stream API遍历 ===");
map.entrySet().stream()
.forEach(entry ->
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()));
// 带过滤的遍历
map.entrySet().stream()
.filter(entry -> entry.getValue() > 25)
.forEach(entry ->
System.out.println("年龄大于25: " + entry.getKey() + " - " + entry.getValue()));
}
2.3 Map.Entry详解
java
public class MapEntryDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 28);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
// 获取键
String key = entry.getKey();
// 获取值
Integer value = entry.getValue();
// 修改值
entry.setValue(value + 1);
System.out.println("修改后 - Key: " + key + ", Value: " + entry.getValue());
}
System.out.println("最终Map: " + map);
}
}
三、Set接口详解
3.1 Set的基本操作
java
import java.util.*;
public class SetOperations {
public static void main(String[] args) {
// 创建HashSet (无序)
Set<Integer> hashSet = new HashSet<>();
// 创建TreeSet (排序)
Set<Integer> treeSet = new TreeSet<>();
// 1. add() - 添加元素
hashSet.add(10);
hashSet.add(20);
hashSet.add(30);
hashSet.add(10); // 重复元素,不会添加
treeSet.add(30);
treeSet.add(10);
treeSet.add(20);
System.out.println("HashSet: " + hashSet);
System.out.println("TreeSet: " + treeSet);
// 2. contains() - 检查元素是否存在
boolean has20 = hashSet.contains(20);
System.out.println("HashSet是否包含20: " + has20);
// 3. remove() - 删除元素
hashSet.remove(20);
System.out.println("删除20后HashSet: " + hashSet);
// 4. size() - 获取大小
System.out.println("HashSet大小: " + hashSet.size());
// 5. isEmpty() - 判断是否为空
System.out.println("HashSet是否为空: " + hashSet.isEmpty());
}
}
3.2 Set的遍历方式
java
public class SetIteration {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Apple");
set.add("Banana");
set.add("Orange");
set.add("Grape");
// 方法1: 增强for循环
System.out.println("=== 增强for循环 ===");
for (String fruit : set) {
System.out.println(fruit);
}
// 方法2: 迭代器
System.out.println("=== 迭代器 ===");
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 方法3: forEach + Lambda
System.out.println("=== forEach ===");
set.forEach(fruit -> System.out.println(fruit));
// 方法4: Stream API
System.out.println("=== Stream API ===");
set.stream().forEach(System.out::println);
}
}
3.3 Set的特殊操作
java
public class SetAdvancedOperations {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));
// 并集
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("并集: " + union);
// 交集
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("交集: " + intersection);
// 差集 (set1 - set2)
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("差集(set1-set2): " + difference);
// 对称差集 (只在其中一个集合中)
Set<Integer> symmetricDifference = new HashSet<>(union);
symmetricDifference.removeAll(intersection);
System.out.println("对称差集: " + symmetricDifference);
}
}
四、TreeMap vs HashMap 对比
4.1 特性对比表
| 特性 | TreeMap | HashMap |
|---|---|---|
| 底层结构 | 红黑树 | 哈希表+链表/红黑树 |
| 时间复杂度 | O(log n) | O(1)平均 |
| 排序 | 按键自然排序 | 无序 |
| 线程安全 | 不安全 | 不安全 |
| Null键 | 不允许 | 允许一个null键 |
| 内存使用 | 较少 | 较多 |
| 适用场景 | 需要排序 | 高性能查询 |
4.2 实际性能测试
java
public class MapPerformanceTest {
private static final int ELEMENT_COUNT = 100000;
public static void main(String[] args) {
// HashMap性能测试
long startTime = System.currentTimeMillis();
Map<Integer, String> hashMap = new HashMap<>();
for (int i = 0; i < ELEMENT_COUNT; i++) {
hashMap.put(i, "Value" + i);
}
long hashMapTime = System.currentTimeMillis() - startTime;
// TreeMap性能测试
startTime = System.currentTimeMillis();
Map<Integer, String> treeMap = new TreeMap<>();
for (int i = 0; i < ELEMENT_COUNT; i++) {
treeMap.put(i, "Value" + i);
}
long treeMapTime = System.currentTimeMillis() - startTime;
System.out.println("HashMap插入" + ELEMENT_COUNT + "个元素耗时: " + hashMapTime + "ms");
System.out.println("TreeMap插入" + ELEMENT_COUNT + "个元素耗时: " + treeMapTime + "ms");
}
}
五、实际应用练习题
5.1 统计元素出现次数
java
public class ElementCounter {
/**
* 统计数组中每个元素的出现次数
*/
public static Map<Integer, Integer> countElements(int[] arr) {
Map<Integer, Integer> countMap = new HashMap<>();
for (int num : arr) {
// 方法1: 使用getOrDefault
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
// 方法2: 使用containsKey检查
// if (countMap.containsKey(num)) {
// countMap.put(num, countMap.get(num) + 1);
// } else {
// countMap.put(num, 1);
// }
}
return countMap;
}
/**
* 统计字符串中每个字符的出现次数
*/
public static Map<Character, Integer> countCharacters(String str) {
Map<Character, Integer> charMap = new HashMap<>();
for (char c : str.toCharArray()) {
charMap.put(c, charMap.getOrDefault(c, 0) + 1);
}
return charMap;
}
public static void main(String[] args) {
int[] numbers = {23, 352, 46, 47, 84, 1, 44, 6, 1, 23};
Map<Integer, Integer> countMap = countElements(numbers);
System.out.println("元素出现次数统计:");
countMap.forEach((key, value) ->
System.out.println("元素 " + key + " 出现 " + value + " 次"));
// 按出现次数排序
System.out.println("\n按出现次数排序:");
countMap.entrySet().stream()
.sorted(Map.Entry.<Integer, Integer>comparingByValue().reversed())
.forEach(entry ->
System.out.println("元素 " + entry.getKey() + " 出现 " + entry.getValue() + " 次"));
}
}
5.2 数组去重
java
public class ArrayDeduplication {
/**
* 使用HashSet去重
*/
public static int[] deduplicateWithSet(int[] arr) {
Set<Integer> set = new HashSet<>();
for (int num : arr) {
set.add(num);
}
// 转换回数组
int[] result = new int[set.size()];
int index = 0;
for (int num : set) {
result[index++] = num;
}
return result;
}
/**
* 使用LinkedHashSet保持原始顺序
*/
public static int[] deduplicateWithOrder(int[] arr) {
Set<Integer> set = new LinkedHashSet<>();
for (int num : arr) {
set.add(num);
}
int[] result = new int[set.size()];
int index = 0;
for (int num : set) {
result[index++] = num;
}
return result;
}
/**
* 使用TreeSet排序去重
*/
public static int[] deduplicateWithSort(int[] arr) {
Set<Integer> set = new TreeSet<>();
for (int num : arr) {
set.add(num);
}
int[] result = new int[set.size()];
int index = 0;
for (int num : set) {
result[index++] = num;
}
return result;
}
public static void main(String[] args) {
int[] arr = {23, 352, 46, 47, 84, 1, 44, 6, 1, 23};
System.out.println("原始数组: " + Arrays.toString(arr));
System.out.println("HashSet去重: " + Arrays.toString(deduplicateWithSet(arr)));
System.out.println("LinkedHashSet去重(保序): " + Arrays.toString(deduplicateWithOrder(arr)));
System.out.println("TreeSet去重(排序): " + Arrays.toString(deduplicateWithSort(arr)));
}
}
5.3 查找第一个重复元素
java
public class FirstDuplicateFinder {
/**
* 找到第一个重复的元素
*/
public static Integer findFirstDuplicate(int[] arr) {
Set<Integer> seen = new HashSet<>();
for (int num : arr) {
if (seen.contains(num)) {
return num; // 找到第一个重复元素
}
seen.add(num);
}
return null; // 没有重复元素
}
/**
* 找到所有重复的元素
*/
public static Set<Integer> findAllDuplicates(int[] arr) {
Set<Integer> duplicates = new HashSet<>();
Set<Integer> seen = new HashSet<>();
for (int num : arr) {
if (!seen.add(num)) { // add返回false表示元素已存在
duplicates.add(num);
}
}
return duplicates;
}
/**
* 找到每个元素的第一次出现位置
*/
public static Map<Integer, Integer> findFirstOccurrence(int[] arr) {
Map<Integer, Integer> firstOccurrence = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
firstOccurrence.putIfAbsent(arr[i], i); // 只在键不存在时放入
}
return firstOccurrence;
}
public static void main(String[] args) {
int[] arr = {23, 352, 46, 47, 84, 1, 44, 6, 1, 23};
Integer firstDuplicate = findFirstDuplicate(arr);
if (firstDuplicate != null) {
System.out.println("第一个重复元素: " + firstDuplicate);
} else {
System.out.println("没有重复元素");
}
Set<Integer> allDuplicates = findAllDuplicates(arr);
System.out.println("所有重复元素: " + allDuplicates);
Map<Integer, Integer> firstOccurrence = findFirstOccurrence(arr);
System.out.println("元素第一次出现位置: " + firstOccurrence);
}
}
六、高级应用场景
6.1 缓存实现
java
public class SimpleCache<K, V> {
private final Map<K, V> cache;
private final int maxSize;
public SimpleCache(int maxSize) {
this.cache = new LinkedHashMap<K, V>(maxSize, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
};
this.maxSize = maxSize;
}
public V get(K key) {
return cache.get(key);
}
public void put(K key, V value) {
cache.put(key, value);
}
public boolean contains(K key) {
return cache.containsKey(key);
}
public void remove(K key) {
cache.remove(key);
}
public int size() {
return cache.size();
}
public void clear() {
cache.clear();
}
}
6.2 单词频率统计
java
public class WordFrequencyCounter {
public static Map<String, Integer> countWordFrequency(String text) {
Map<String, Integer> frequencyMap = new HashMap<>();
// 分割单词(简单的空格分割,实际应用需要更复杂的分词)
String[] words = text.toLowerCase().split("\s+");
for (String word : words) {
// 移除标点符号
word = word.replaceAll("[^a-zA-Z]", "");
if (!word.isEmpty()) {
frequencyMap.put(word, frequencyMap.getOrDefault(word, 0) + 1);
}
}
return frequencyMap;
}
public static void printTopWords(Map<String, Integer> frequencyMap, int topN) {
frequencyMap.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.limit(topN)
.forEach(entry ->
System.out.println(entry.getKey() + ": " + entry.getValue()));
}
public static void main(String[] args) {
String text = "Hello world hello Java world programming Java programming";
Map<String, Integer> frequencyMap = countWordFrequency(text);
System.out.println("单词频率统计:");
frequencyMap.forEach((word, count) ->
System.out.println(word + ": " + count));
System.out.println("\n出现频率最高的3个单词:");
printTopWords(frequencyMap, 3);
}
}
七、最佳实践总结
7.1 选择指南
- 需要排序:TreeMap / TreeSet
- 高性能查询:HashMap / HashSet
- 保持插入顺序:LinkedHashMap / LinkedHashSet
- 线程安全:ConcurrentHashMap / CopyOnWriteArraySet
- 内存敏感:考虑使用原始类型特化版本(如Trove库)
7.2 性能优化技巧
- 为HashMap设置合适的初始容量和负载因子
- 合理实现hashCode()和equals()方法
- 使用entrySet()进行Map遍历
- 考虑使用不可变对象作为键
7.3 常见陷阱
- 在遍历时修改集合(使用迭代器的remove方法)
- 使用可变对象作为HashMap键
- 忽略null值处理
- 不正确的hashCode/equals实现
通过深入理解Map和Set的特性及使用方法,可以显著提升Java程序的性能和代码质量。