在Java编程中,数据的组织与管理是构建高效、可维护应用程序的核心。Java集合框架(Java Collections Framework)为此提供了强大而灵活的工具集。它是一组接口和类的集合,用于存储、操作和处理对象组。理解并熟练掌握List
、Set
和Map
这三大核心接口及其常用实现类,是每个Java开发者必备的技能。本文将深入探讨这些集合的特性、用法、性能差异以及最佳实践,通过丰富的代码示例帮助你全面掌握Java集合框架。
一、Java集合框架概览
Java集合框架位于java.util
包中,其设计基于一组核心接口,这些接口定义了集合的行为。主要的接口包括:
Collection
:所有单值集合(存储单个元素的集合)的根接口,List
和Set
都继承自它。List
:有序集合,允许重复元素,可以通过索引访问。Set
:不允许重复元素的集合,通常无序(但有例外)。Queue
:用于模拟队列数据结构,通常遵循先进先出(FIFO)原则。Deque
:双端队列,支持在两端插入和删除元素。Map
:存储键值对(key-value pairs)的集合,键不允许重复。
集合框架的设计遵循“接口优先”的原则,允许我们编写与具体实现无关的代码,从而提高代码的灵活性和可重用性。
1.1 集合框架的层次结构
Collection
/ \
/ \
/ \
List Set
| |
| |
ArrayList HashSet
LinkedList LinkedHashSet
Vector TreeSet
Stack
Map
|
|
HashMap
LinkedHashMap
TreeMap
Hashtable
1.2 使用集合框架的优势
- 高性能:经过优化的实现提供了高效的增删改查操作。
- 可重用性:提供了一套标准的数据结构和算法。
- 类型安全(结合泛型):在编译期检查类型,避免运行时错误。
- 易于使用:统一的接口和丰富的API。
- 可扩展性:可以轻松地创建自定义集合实现。
二、List接口:有序的序列
List
接口代表一个有序的集合,也称为序列。它允许存储重复元素,并且可以通过元素的整数索引(位置)来访问元素。
2.1 List的核心特性
- 有序性:元素的插入顺序被保留。
- 可重复:同一个元素可以出现多次。
- 索引访问:支持通过
get(int index)
和set(int index, E element)
等方法进行随机访问。 - 常用方法:
add(E e)
/add(int index, E element)
:添加元素。remove(int index)
/remove(Object o)
:移除元素。get(int index)
:获取指定位置的元素。set(int index, E element)
:替换指定位置的元素。indexOf(Object o)
/lastIndexOf(Object o)
:查找元素的索引。size()
:返回元素数量。isEmpty()
:检查是否为空。contains(Object o)
:检查是否包含某个元素。
2.2 ArrayList:动态数组
ArrayList
是List
接口最常用的实现,基于动态数组。它提供了快速的随机访问,但在列表中间插入或删除元素时性能较差,因为需要移动后续元素。
2.2.1 基本使用
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
// 创建一个ArrayList
List<String> fruits = new ArrayList<>();
// 添加元素
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Apple"); // 允许重复
System.out.println("Fruits: " + fruits); // [Apple, Banana, Orange, Apple]
// 通过索引访问
String firstFruit = fruits.get(0);
System.out.println("First fruit: " + firstFruit); // Apple
// 修改元素
fruits.set(1, "Grape");
System.out.println("After set: " + fruits); // [Apple, Grape, Orange, Apple]
// 插入元素
fruits.add(1, "Mango");
System.out.println("After insert: " + fruits); // [Apple, Mango, Grape, Orange, Apple]
// 删除元素
fruits.remove("Apple"); // 删除第一个匹配的元素
System.out.println("After remove: " + fruits); // [Mango, Grape, Orange, Apple]
fruits.remove(0); // 删除索引为0的元素
System.out.println("After remove by index: " + fruits); // [Grape, Orange, Apple]
// 查找元素
int indexOfOrange = fruits.indexOf("Orange");
System.out.println("Index of Orange: " + indexOfOrange); // 1
// 遍历
System.out.println("Fruits:");
for (String fruit : fruits) {
System.out.println("- " + fruit);
}
}
}
2.2.2 性能特点
- 随机访问:
O(1)
- 非常快。 - 在末尾添加/删除:
O(1)
- 平均情况下很快(扩容时为O(n)
)。 - 在中间或开头插入/删除:
O(n)
- 需要移动元素。 - 查找:
O(n)
- 需要遍历。
初始容量与扩容:ArrayList
有一个初始容量(默认为10),当元素数量超过容量时,会自动扩容(通常增加50%),并复制所有元素到新数组。如果知道大致元素数量,建议在创建时指定初始容量以避免频繁扩容。
// 指定初始容量
List<Integer> numbers = new ArrayList<>(100);
2.3 LinkedList:双向链表
LinkedList
基于双向链表实现。它在列表的任意位置插入和删除元素都非常高效,但随机访问性能较差。
2.3.1 基本使用
import java.util.LinkedList;
import java.util.List;
public class LinkedListExample {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
tasks.add("Task 1");
tasks.add("Task 2");
tasks.add("Task 3");
System.out.println("Tasks: " + tasks);
// LinkedList还实现了Deque接口,支持双端操作
((LinkedList<String>) tasks).addFirst("Priority Task");
((LinkedList<String>) tasks).addLast("Final Task");
System.out.println("After addFirst/addLast: " + tasks);
String firstTask = ((LinkedList<String>) tasks).removeFirst();
String lastTask = ((LinkedList<String>) tasks).removeLast();
System.out.println("Removed first: " + firstTask); // Priority Task
System.out.println("Removed last: " + lastTask); // Final Task
System.out.println("After removeFirst/removeLast: " + tasks);
}
}
2.3.2 性能特点
- 在任意位置插入/删除:
O(1)
- 只需修改指针。 - 随机访问:
O(n)
- 需要从头或尾遍历。 - 查找:
O(n)
。
何时使用LinkedList:
- 需要频繁在列表中间插入或删除元素。
- 需要实现栈(
push
/pop
)或队列(offer
/poll
)数据结构。
2.4 Vector和Stack
Vector
是ArrayList
的线程安全版本,其方法大多使用synchronized
修饰。由于性能开销,现代开发中通常使用ArrayList
配合Collections.synchronizedList()
或CopyOnWriteArrayList
来实现线程安全。
Stack
继承自Vector
,实现了后进先出(LIFO)的栈。但由于其继承自Vector
,且有更好的替代品(如ArrayDeque
),不推荐使用。
// 不推荐
Stack<String> stack = new Stack<>();
stack.push("A");
stack.push("B");
String top = stack.pop();
// 推荐使用ArrayDeque作为栈
import java.util.ArrayDeque;
ArrayDeque<String> dequeStack = new ArrayDeque<>();
dequeStack.push("A");
dequeStack.push("B");
String topDeque = dequeStack.pop();
2.5 List的遍历方式
import java.util.*;
public class ListTraversal {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C", "D");
// 1. 增强for循环(推荐)
System.out.println("Enhanced for loop:");
for (String item : list) {
System.out.println(item);
}
// 2. 迭代器(Iterator)
System.out.println("Iterator:");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
// iterator.remove(); // 如果需要在遍历中删除元素
}
// 3. 列表迭代器(ListIterator)- 支持双向遍历和修改
System.out.println("ListIterator (backward):");
ListIterator<String> listIterator = list.listIterator(list.size());
while (listIterator.hasPrevious()) {
String item = listIterator.previous();
System.out.println(item);
// listIterator.set("Modified"); // 修改当前元素
// listIterator.add("New"); // 在当前位置插入
}
// 4. 传统for循环(索引)
System.out.println("Traditional for loop:");
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
System.out.println(item);
}
// 5. Java 8 Stream API
System.out.println("Stream API:");
list.stream().forEach(System.out::println);
}
}
注意:在使用迭代器遍历集合时,如果需要删除元素,必须使用iterator.remove()
方法,直接调用list.remove()
会导致ConcurrentModificationException
。
三、Set接口:无重复元素的集合
Set
接口表示一个不包含重复元素的集合。它继承自Collection
,并添加了对重复元素的限制。Set
通常不保证元素的顺序,但有例外。
3.1 Set的核心特性
- 无重复:不能包含两个相等的元素(根据
equals()
方法判断)。 - 无序性:大多数实现不保证元素的顺序(但
LinkedHashSet
和TreeSet
例外)。 - 常用方法:与
Collection
接口基本相同,如add()
,remove()
,contains()
,size()
等。add()
方法在元素已存在时返回false
,否则返回true
。
3.2 HashSet:基于哈希表
HashSet
是Set
接口最常用的实现,基于HashMap
。它提供最快的查找、添加和删除操作,但不保证元素的顺序。
3.2.1 基本使用
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
Set<String> colors = new HashSet<>();
// 添加元素
colors.add("Red");
colors.add("Green");
colors.add("Blue");
colors.add("Red"); // 重复元素,添加失败
System.out.println("Colors: " + colors); // 顺序不确定,如 [Red, Blue, Green]
System.out.println("Size: " + colors.size()); // 3
// 检查元素
boolean hasRed = colors.contains("Red");
System.out.println("Contains Red: " + hasRed); // true
// 删除元素
colors.remove("Green");
System.out.println("After remove: " + colors); // [Red, Blue]
// 遍历
System.out.println("Colors:");
for (String color : colors) {
System.out.println("- " + color);
}
}
}
3.2.2 哈希码与equals方法
HashSet
依赖于对象的hashCode()
和equals()
方法来确保唯一性。对于自定义对象,必须正确重写这两个方法。
import java.util.Objects;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
// 重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + '}';
}
}
public class HashSetWithCustomObject {
public static void main(String[] args) {
Set<Person> people = new HashSet<>();
people.add(new Person("Alice", 25));
people.add(new Person("Bob", 30));
people.add(new Person("Alice", 25)); // 与第一个Alice相等,不会被添加
System.out.println("People: " + people);
// 输出:[Person{name='Alice', age=25}, Person{name='Bob', 30}]
}
}
性能特点:
- 添加、删除、查找:平均
O(1)
,最坏O(n)
(哈希冲突严重时)。
3.3 LinkedHashSet:有序的HashSet
LinkedHashSet
继承自HashSet
,但它通过维护一个双向链表来记录元素的插入顺序。因此,它既保证了元素的唯一性,又保持了插入顺序。
3.3.1 基本使用
import java.util.LinkedHashSet;
import java.util.Set;
public class LinkedHashSetExample {
public static void main(String[] args) {
Set<String> insertionOrdered = new LinkedHashSet<>();
insertionOrdered.add("First");
insertionOrdered.add("Second");
insertionOrdered.add("Third");
insertionOrdered.add("First"); // 重复,不添加
System.out.println("Insertion ordered: " + insertionOrdered);
// 输出:[First, Second, Third] - 保持插入顺序
}
}
性能特点:
- 与
HashSet
基本相同,但略慢(需要维护链表)。 - 空间开销:比
HashSet
大。
3.4 TreeSet:基于红黑树
TreeSet
实现了SortedSet
接口,使用红黑树(一种自平衡二叉搜索树)存储元素。它保证元素按照升序排列(或自定义比较器指定的顺序),并且不允许null
值。
3.4.1 基本使用
import java.util.TreeSet;
import java.util.Set;
public class TreeSetExample {
public static void main(String[] args) {
Set<Integer> sortedNumbers = new TreeSet<>();
sortedNumbers.add(5);
sortedNumbers.add(1);
sortedNumbers.add(3);
sortedNumbers.add(9);
sortedNumbers.add(2);
System.out.println("Sorted numbers: " + sortedNumbers);
// 输出:[1, 2, 3, 5, 9] - 自动排序
// 获取第一个和最后一个元素
Integer first = ((TreeSet<Integer>) sortedNumbers).first();
Integer last = ((TreeSet<Integer>) sortedNumbers).last();
System.out.println("First: " + first + ", Last: " + last); // 1, 9
// 获取子集
Set<Integer> subset = ((TreeSet<Integer>) sortedNumbers).subSet(2, 7);
System.out.println("Subset [2, 7): " + subset); // [2, 3, 5]
// 获取头部和尾部集合
Set<Integer> headSet = ((TreeSet<Integer>) sortedNumbers).headSet(4);
Set<Integer> tailSet = ((TreeSet<Integer>) sortedNumbers).tailSet(4);
System.out.println("Head set (<4): " + headSet); // [1, 2, 3]
System.out.println("Tail set (>=4): " + tailSet); // [5, 9]
}
}
3.4.2 自定义排序
import java.util.Comparator;
import java.util.TreeSet;
public class CustomSortedSet {
public static void main(String[] args) {
// 按字符串长度排序
TreeSet<String> lengthSorted = new TreeSet<>((s1, s2) ->
Integer.compare(s1.length(), s2.length()));
lengthSorted.add("Apple");
lengthSorted.add("Fig");
lengthSorted.add("Banana");
lengthSorted.add("Kiwi");
System.out.println("Length sorted: " + lengthSorted);
// 输出:[Fig, Kiwi, Apple, Banana] (按长度:3,4,5,6)
// 或者实现Comparator接口
TreeSet<String> reverseAlphabetical = new TreeSet<>(Comparator.reverseOrder());
reverseAlphabetical.add("Apple");
reverseAlphabetical.add("Banana");
reverseAlphabetical.add("Cherry");
System.out.println("Reverse order: " + reverseAlphabetical);
// 输出:[Cherry, Banana, Apple]
}
}
性能特点:
- 添加、删除、查找:
O(log n)
。 - 排序:自动维护有序性。
3.5 Set的遍历与操作
import java.util.*;
public class SetOperations {
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));
// 并集 (Union)
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("Union: " + union); // [1, 2, 3, 4, 5, 6, 7, 8]
// 交集 (Intersection)
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("Intersection: " + intersection); // [4, 5]
// 差集 (Difference)
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("Difference (set1 - set2): " + difference); // [1, 2, 3]
// 子集判断
Set<Integer> subset = new HashSet<>(Arrays.asList(1, 2));
boolean isSubset = set1.containsAll(subset);
System.out.println("Is subset: " + isSubset); // true
}
}
四、Map接口:键值对的映射
Map
接口存储键值对(key-value pairs),其中键是唯一的。它不继承自Collection
接口,但提供了集合视图(keySet()
, values()
, entrySet()
)来访问其内容。
4.1 Map的核心特性
- 键的唯一性:每个键最多映射到一个值。
- 值的可重复性:不同的键可以映射到相同的值。
- 常用方法:
put(K key, V value)
:添加或更新键值对。get(Object key)
:根据键获取值,键不存在时返回null
。remove(Object key)
:移除指定键的映射。containsKey(Object key)
/containsValue(Object value)
:检查是否包含键或值。size()
/isEmpty()
:获取大小和检查是否为空。keySet()
:返回所有键的Set
视图。values()
:返回所有值的Collection
视图。entrySet()
:返回所有键值对的Set<Map.Entry<K,V>>
视图。
4.2 HashMap:基于哈希表的Map
HashMap
是Map
接口最常用的实现,基于哈希表。它提供最快的查找、添加和删除操作,但不保证映射的顺序。
4.2.1 基本使用
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
Map<String, Integer> ages = new HashMap<>();
// 添加键值对
ages.put("Alice", 25);
ages.put("Bob", 30);
ages.put("Charlie", 35);
ages.put("Alice", 26); // 更新Alice的年龄
System.out.println("Ages: " + ages); // {Alice=26, Bob=30, Charlie=35}
// 获取值
Integer aliceAge = ages.get("Alice");
System.out.println("Alice's age: " + aliceAge); // 26
// 检查键
boolean hasDavid = ages.containsKey("David");
System.out.println("Has David: " + hasDavid); // false
// 检查值
boolean hasAge30 = ages.containsValue(30);
System.out.println("Has age 30: " + hasAge30); // true
// 删除键值对
ages.remove("Bob");
System.out.println("After remove Bob: " + ages); // {Alice=26, Charlie=35}
// 遍历
System.out.println("Entries:");
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 仅遍历键
System.out.println("Keys:");
for (String name : ages.keySet()) {
System.out.println(name);
}
// 仅遍历值
System.out.println("Values:");
for (Integer age : ages.values()) {
System.out.println(age);
}
}
}
4.2.2 处理null值
HashMap
允许null
键和null
值。
ages.put(null, 100); // 允许null键
ages.put("David", null); // 允许null值
Integer nullKeyAge = ages.get(null); // 返回100
Integer davidAge = ages.get("David"); // 返回null
Integer unknownAge = ages.get("Unknown"); // 也返回null
注意:get()
方法在键不存在或键映射到null
值时都返回null
。如果需要区分这两种情况,可以使用containsKey()
。
4.2.3 性能特点
- 查找、添加、删除:平均
O(1)
,最坏O(n)
。 - 初始容量与负载因子:默认初始容量16,负载因子0.75。当元素数量超过
容量 * 负载因子
时,会进行扩容(rehashing)。
4.3 LinkedHashMap:有序的HashMap
LinkedHashMap
继承自HashMap
,通过维护一个双向链表来记录元素的插入顺序或访问顺序。
4.3.1 按插入顺序排序
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapInsertionOrder {
public static void main(String[] args) {
Map<String, Integer> insertionOrdered = new LinkedHashMap<>();
insertionOrdered.put("First", 1);
insertionOrdered.put("Second", 2);
insertionOrdered.put("Third", 3);
System.out.println("Insertion ordered: " + insertionOrdered);
// 输出:{First=1, Second=2, Third=3}
}
}
4.3.2 按访问顺序排序(LRU缓存)
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCacheExample {
// 实现一个简单的LRU缓存
static class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int capacity;
public LRUCache(int capacity) {
// accessOrder=true 表示按访问顺序排序
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 当元素数量超过容量时,移除最老的条目
return size() > capacity;
}
}
public static void main(String[] args) {
LRUCache<String, Integer> cache = new LRUCache<>(3);
cache.put("A", 1);
cache.put("B", 2);
cache.put("C", 3);
System.out.println("Cache: " + cache); // {A=1, B=2, C=3}
cache.get("A"); // 访问A,将其移到末尾
System.out.println("After get A: " + cache); // {B=2, C=3, A=1}
cache.put("D", 4); // 添加D,触发移除最老的B
System.out.println("After put D: " + cache); // {C=3, A=1, D=4}
}
}
4.4 TreeMap:基于红黑树的有序Map
TreeMap
实现了SortedMap
接口,使用红黑树存储键值对。它保证键按照升序排列(或自定义比较器指定的顺序)。
4.4.1 基本使用
import java.util.TreeMap;
import java.util.Map;
public class TreeMapExample {
public static void main(String[] args) {
Map<String, Integer> sortedMap = new TreeMap<>();
sortedMap.put("Banana", 5);
sortedMap.put("Apple", 3);
sortedMap.put("Cherry", 8);
sortedMap.put("Date", 2);
System.out.println("Sorted map: " + sortedMap);
// 输出:{Apple=3, Banana=5, Cherry=8, Date=2} - 按键的字母顺序排序
// 获取第一个和最后一个条目
Map.Entry<String, Integer> firstEntry = sortedMap.firstEntry();
Map.Entry<String, Integer> lastEntry = sortedMap.lastEntry();
System.out.println("First: " + firstEntry); // Apple=3
System.out.println("Last: " + lastEntry); // Date=2
// 获取子映射
Map<String, Integer> subMap = sortedMap.subMap("Apple", "Date");
System.out.println("Submap [Apple, Date): " + subMap); // {Apple=3, Banana=5, Cherry=8}
// 获取头部和尾部映射
Map<String, Integer> headMap = sortedMap.headMap("Cherry");
Map<String, Integer> tailMap = sortedMap.tailMap("Cherry");
System.out.println("Head map (<Cherry): " + headMap); // {Apple=3, Banana=5}
System.out.println("Tail map (>=Cherry): " + tailMap); // {Cherry=8, Date=2}
}
}
4.4.2 自定义排序
import java.util.Comparator;
import java.util.TreeMap;
public class CustomSortedMap {
public static void main(String[] args) {
// 按值排序(需要自定义比较器)
TreeMap<String, Integer> valueSorted = new TreeMap<>((k1, k2) -> {
int valueCompare = Integer.compare(valueSorted.get(k1), valueSorted.get(k2));
return valueCompare != 0 ? valueCompare : k1.compareTo(k2); // 如果值相等,按键排序
});
// 注意:上面的比较器在put时无法获取值,因此需要另一种方式
// 更好的方式是使用值的排序,但TreeMap按键排序
// 如果需要按值排序,可以将条目放入List并排序
Map<String, Integer> unsorted = new HashMap<>();
unsorted.put("A", 3);
unsorted.put("B", 1);
unsorted.put("C", 4);
// 按值降序排序
unsorted.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue()));
// 输出:C=4, A=3, B=1
}
}
性能特点:
- 查找、添加、删除:
O(log n)
。 - 不允许null键(但允许
null
值,除非比较器支持null
)。
4.5 Hashtable与Properties
Hashtable
是HashMap
的线程安全版本,其方法使用synchronized
修饰。与HashMap
不同,Hashtable
不允许null
键和null
值。由于性能问题,现代开发中通常使用ConcurrentHashMap
替代。
Properties
继承自Hashtable
,专门用于处理配置文件(.properties
文件)。
import java.util.Properties;
import java.io.*;
public class PropertiesExample {
public static void main(String[] args) throws IOException {
Properties props = new Properties();
// 设置属性
props.setProperty("database.url", "jdbc:mysql://localhost:3306/mydb");
props.setProperty("database.username", "admin");
props.setProperty("database.password", "secret");
// 保存到文件
try (FileOutputStream out = new FileOutputStream("config.properties")) {
props.store(out, "Database Configuration");
}
// 从文件加载
Properties loadedProps = new Properties();
try (FileInputStream in = new FileInputStream("config.properties")) {
loadedProps.load(in);
}
System.out.println("Loaded properties: " + loadedProps);
}
}
4.6 Map的遍历方式
import java.util.*;
public class MapTraversal {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// 1. 遍历entrySet(推荐,同时获取键和值)
System.out.println("EntrySet:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 2. 遍历keySet
System.out.println("KeySet:");
for (String key : map.keySet()) {
System.out.println(key + " -> " + map.get(key));
}
// 3. 遍历values
System.out.println("Values:");
for (Integer value : map.values()) {
System.out.println(value);
}
// 4. 使用Iterator
System.out.println("Iterator (entrySet):");
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println(entry.getKey() + " -> " + entry.getValue());
// iterator.remove(); // 如果需要在遍历中删除
}
// 5. Java 8 forEach
System.out.println("forEach:");
map.forEach((key, value) -> System.out.println(key + " -> " + value));
// 6. Java 8 Stream API
System.out.println("Stream API:");
map.entrySet().stream()
.forEach(entry -> System.out.println(entry.getKey() + " -> " + entry.getValue()));
}
}
五、集合的线程安全
Java集合框架中的大多数实现(如ArrayList
, HashMap
, HashSet
)都不是线程安全的。在多线程环境中直接使用它们可能导致数据不一致或ConcurrentModificationException
。
5.1 同步包装器
Collections
类提供了将非线程安全集合转换为线程安全集合的静态方法。
import java.util.*;
public class SynchronizedCollections {
public static void main(String[] args) {
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 使用同步集合
syncList.add("Item");
syncSet.add("Element");
syncMap.put("Key", 1);
// 注意:遍历时需要手动同步
synchronized (syncList) {
for (String item : syncList) {
System.out.println(item);
}
}
}
}
5.2 并发集合
java.util.concurrent
包提供了专门为高并发场景设计的集合类。
5.2.1 ConcurrentHashMap
ConcurrentHashMap
是HashMap
的线程安全版本,通过分段锁(JDK 7)或CAS+synchronized(JDK 8+)实现高并发性能。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(10);
// 模拟并发操作
for (int i = 0; i < 1000; i++) {
final int taskId = i;
executor.submit(() -> {
String key = "Task" + taskId % 10;
map.merge(key, 1, Integer::sum); // 原子性地增加计数
});
}
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Task counts: " + map);
}
}
5.2.2 CopyOnWriteArrayList
CopyOnWriteArrayList
在修改操作(add, set, remove)时创建底层数组的副本,适用于读多写少的场景。
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 遍历时可以安全地修改(因为遍历的是快照)
for (String item : list) {
System.out.println(item);
if ("B".equals(item)) {
list.add("D"); // 不会影响当前遍历
}
}
System.out.println("Final list: " + list); // [A, B, C, D]
}
}
六、集合的最佳实践与总结
6.1 选择合适的集合
需求 | 推荐集合 |
---|---|
需要有序且允许重复 | ArrayList |
频繁在中间插入/删除 | LinkedList |
不允许重复元素 | HashSet |
保持插入顺序且无重复 | LinkedHashSet |
需要排序的元素 | TreeSet |
键值对映射,高性能 | HashMap |
保持插入顺序的键值对 | LinkedHashMap |
需要排序的键值对 | TreeMap |
线程安全的Map | ConcurrentHashMap |
读多写少的List | CopyOnWriteArrayList |
6.2 最佳实践
- 使用接口而非具体实现:
List<String> list = new ArrayList<>();
- 指定泛型类型:避免原始类型。
- 初始化时指定容量:减少扩容开销。
- 合理使用
Collections
工具类:如unmodifiable
,synchronized
,emptyList
等。 - 优先使用Java 8+的Stream API:进行复杂的集合操作。
- 注意线程安全:在多线程环境中选择合适的并发集合。
6.3 总结
Java集合框架是Java编程的基石之一。List
、Set
和Map
三大接口及其丰富的实现类,为各种数据存储和操作需求提供了完美的解决方案。理解它们的特性、性能差异和适用场景,能够帮助你编写出高效、健壮的Java代码。通过不断实践和深入学习,你将能够灵活运用这些工具,应对复杂的编程挑战。