基础14-Java集合框架:掌握List、Set和Map的使用
在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 HashSetLinkedList LinkedHashSetVector TreeSetStack
Map||HashMapLinkedHashMapTreeMapHashtable
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) {// 创建一个ArrayListList<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 TaskSystem.out.println("Removed last: " + lastTask); // Final TaskSystem.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 APISystem.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方法@Overridepublic 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方法@Overridepublic int hashCode() {return Objects.hash(name, age);}@Overridepublic 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;}@Overrideprotected 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,触发移除最老的BSystem.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=3System.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. 遍历keySetSystem.out.println("KeySet:");for (String key : map.keySet()) {System.out.println(key + " -> " + map.get(key));}// 3. 遍历valuesSystem.out.println("Values:");for (Integer value : map.values()) {System.out.println(value);}// 4. 使用IteratorSystem.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 forEachSystem.out.println("forEach:");map.forEach((key, value) -> System.out.println(key + " -> " + value));// 6. Java 8 Stream APISystem.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代码。通过不断实践和深入学习,你将能够灵活运用这些工具,应对复杂的编程挑战。