一、引言
- Java 集合框架概述
-
定义和重要性
Java 集合框架是一个提供了表示和操作集合数据结构的标准化 API 的库。它是 Java 程序设计语言中非常重要的一部分,用于存储、检索、操纵和传输数据。集合框架极大地简化了编程过程,通过提供预定义的数据结构和算法,开发者可以更加专注于业务逻辑,而不是数据结构的实现细节。 -
集合框架的组成部分
Java 集合框架主要由以下几个部分组成:-
接口:定义了集合操作的基本合同。常见的接口包括 Collection、List、Set、Map、Queue 等。
-
实现类:这些是接口的具体实现。常见的实现类包括 ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap 等。
-
算法:算法是定义在集合上的方法,例如排序、搜索和修改集合。Java 提供了 Collections 类来实现这些算法。
-
实用工具类:如 Collections 和 Arrays 类,提供了各种集合操作的静态方法,如排序、搜索、同步控制等。
-
-
二、集合框架的基本接口
-
Collection 接口
Collection接口是Java集合框架的根接口,所有集合类都直接或间接实现了这个接口。它定义了一些基本操作,如添加、删除和检查集合的大小等。基本方法:
add(E e)
: 添加元素到集合中。remove(Object o)
: 从集合中移除指定元素。size()
: 返回集合中元素的个数。isEmpty()
: 检查集合是否为空。iterator()
: 返回一个迭代器,用于遍历集合中的元素。
示例:
import java.util.ArrayList;
import java.util.Collection;
public class CollectionExample {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("Hello");
collection.add("World");
System.out.println("Collection size: " + collection.size());
System.out.println("Is collection empty? " + collection.isEmpty());
for (String item : collection) {
System.out.println(item);
}
}
}
- List 接口
List接口是Collection的子接口,表示一个有序的元素集合,允许重复元素。-
常用实现类:
- ArrayList:基于动态数组的实现。
- LinkedList:基于双向链表的实现。
- Vector和Stack:线程安全的实现。
-
基本方法:
get(int index)
: 获取指定索引处的元素。set(int index, E element)
: 替换指定索引处的元素。add(int index, E element)
: 在指定位置插入元素。remove(int index)
: 移除指定位置的元素。indexOf(Object o)
: 返回指定元素第一次出现的位置。
-
示例:
import java.util.ArrayList;
import java.util.Collection;
public class CollectionExample {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("Hello");
collection.add("World");
System.out.println("Collection size: " + collection.size());
System.out.println("Is collection empty? " + collection.isEmpty());
for (String item : collection) {
System.out.println(item);
}
}
}
-
Set 接口
Set接口是Collection的子接口,表示一个不包含重复元素的集合。
基本方法:与Collection接口相同,因为Set不允许重复元素,所以
add()
方法会返回false
,如果试图添加的元素已经存在。add()方法通过hashCode和equals方法来判断元素是否相等。- 常见实现类
- HashSet:基于哈希表的实现。
- LinkedHashSet:维护插入顺序的哈希表实现。
- TreeSet:基于红黑树的实现,保证元素的自然顺序。
- 常见实现类
示例:
import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Hello");
set.add("World");
set.add("Hello"); // 尝试添加重复元素
System.out.println("Set size: " + set.size());
for (String item : set) {
System.out.println(item);
}
}
}
-
Queue 接口
Queue接口是Collection的子接口,表示一个先进先出的队列。
- 常见实现类
- PriorityQueue:基于优先级堆的实现。
- Deque(双端队列):如ArrayDeque和LinkedList,允许在两端插入和移除元素。
- 基本方法
offer(E e)
: 向队列添加元素。poll()
: 获取并移除队列的头元素。peek()
: 获取但不移除队列的头元素。
- 常见实现类
示例:
import java.util.LinkedList;
import java.util.Queue;
public class QueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("Hello");
queue.offer("World");
System.out.println("Queue size: " + queue.size());
System.out.println("Peek: " + queue.peek());
System.out.println("Poll: " + queue.poll());
System.out.println("Queue size after poll: " + queue.size());
}
}
三、Map 接口
Map接口不属于Collection接口体系,它表示一个键值对映射。每个键唯一对应一个值。
- Map 接口
- 常见实现类
- HashMap:基于哈希表的实现。
- LinkedHashMap:维护插入顺序的哈希表实现。
- TreeMap:基于红黑树的实现,保证键的自然顺序。
- Hashtable:线程安全的哈希表实现。
- ConcurrentHashMap:支持高并发的哈希表实现。
- 基本方法
put(K key, V value)
: 添加键值对。get(Object key)
: 获取键对应的值。remove(Object key)
: 移除键对应的键值对。containsKey(Object key)
: 检查是否包含指定的键。keySet()
: 获取所有键的集合。
- 常见实现类
示例:
import java.util.HashMap;
import java.util.Map;
public class MapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
System.out.println("Map size: " + map.size());
System.out.println("Alice's age: " + map.get("Alice"));
map.remove("Alice");
System.out.println("Map size after removal: " + map.size());
}
}
四、集合框架的实用工具类
- Collections 类
- Collections 类是一个操作集合的工具类,提供了一些静态方法来处理和操作集合。以下是一些常用的 Collections类方法及其简要说明:
- 排序:
sort(List<T> list)
- 对指定列表按升序排序。 - 反转:
reverse(List<?> list)
- 反转指定列表中元素的顺序。 - 随机打乱:
shuffle(List<?> list)
- 随机打乱指定列表的顺序。 - 查找:
binarySearch(List<? extends Comparable<? super T>> list, T key)
- 使用二分搜索算法查找指定列表中的元素。 - 最小/最大:
min(Collection<? extends T> coll)
和max(Collection<? extends T> coll)
- 返回指定集合中的最小/最大元素。 - 同步控制:
synchronizedList(List<T> list)
- 返回一个线程安全的列表。 - 不可变集合:
unmodifiableList(List<? extends T> list)
- 返回一个不可变的列表。
- 排序:
- Collections 类是一个操作集合的工具类,提供了一些静态方法来处理和操作集合。以下是一些常用的 Collections类方法及其简要说明:
示例:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Banana");
list.add("Apple");
list.add("Cherry");
// 排序
Collections.sort(list);
System.out.println("Sorted list: " + list);
// 反转
Collections.reverse(list);
System.out.println("Reversed list: " + list);
// 查找
int index = Collections.binarySearch(list, "Banana");
System.out.println("Index of Banana: " + index);
// 随机打乱
Collections.shuffle(list);
System.out.println("Shuffled list: " + list);
}
}
-
Arrays 类
Arrays 类是一个包含用来操作数组(如排序和搜索)的各种方法的工具类。以下是一些常用的 Arrays 类方法及其简要说明:
- 转换为列表:
asList(T... a)
- 将数组转换为固定大小的列表。 - 排序:
sort(T[] a)
- 对指定数组按升序排序。 - 二分查找:
binarySearch(T[] a, T key)
- 使用二分搜索算法查找指定数组中的元素。 - 填充:
fill(T[] a, T val)
- 将指定值分配给数组中的每个元素。 - 比较:
equals(T[] a, T[] a2)
- 判断两个数组是否相等。 - 复制:
copyOf(T[] original, int newLength)
- 复制指定数组,截取或填充以适应新的长度。
- 转换为列表:
示例:
import java.util.Arrays;
public class ArraysExample {
public static void main(String[] args) {
int[] numbers = {5, 3, 8, 2};
// 排序
Arrays.sort(numbers);
System.out.println("Sorted array: " + Arrays.toString(numbers));
// 二分查找
int index = Arrays.binarySearch(numbers, 3);
System.out.println("Index of 3: " + index);
// 填充
int[] filledArray = new int[5];
Arrays.fill(filledArray, 10);
System.out.println("Filled array: " + Arrays.toString(filledArray));
// 复制
int[] copiedArray = Arrays.copyOf(numbers, 6);
System.out.println("Copied array: " + Arrays.toString(copiedArray));
}
}
五、集合框架的高级特性
-
泛型
Java 的集合框架广泛使用泛型,以确保类型安全并减少强制类型转换的需要。泛型允许在声明集合时指定集合中元素的类型,从而在编译时提供类型检查。
-
泛型的定义和使用
泛型在声明时使用尖括号
<>
指定类型参数。例如,ArrayList<String>
表示一个存储String
类型元素的列表。
-
示例:
import java.util.ArrayList;
import java.util.List;
public class GenericsExample {
public static void main(String[] args) {
// 使用泛型声明一个字符串列表
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// 编译时检查类型
// stringList.add(123); // 编译错误,类型不匹配
for (String s : stringList) {
System.out.println(s);
}
}
}
-
迭代器
迭代器用于遍历集合中的元素。Java 提供了
Iterator
和ListIterator
接口。- Iterator 接口
hasNext()
: 如果迭代器还有更多元素,则返回true
。next()
: 返回迭代器的下一个元素。remove()
: 从迭代器指向的集合中移除最后一个返回的元素。
- ListIterator 接口:除了
Iterator
的所有功能外,还增加了双向遍历功能。hasPrevious()
: 如果有前一个元素,则返回true
。previous()
: 返回前一个元素。
- Iterator 接口
示例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
if ("Banana".equals(fruit)) {
iterator.remove();
} else {
System.out.println(fruit);
}
}
}
}
-
Stream API
Stream API 提供了一种高效且易于使用的方式来处理集合数据(类似于SQL语句)。Stream 可以执行过滤、映射、排序和聚合等操作。(对于集合来说十分好用。推荐使用Stream来对集合进行操作,推荐一个我觉得写的好的博客:https://www.cnblogs.com/owenma/p/12207330.html
- Stream 的创建和操作
stream()
: 创建一个顺序流。parallelStream()
: 创建一个并行流。
- 中间操作与终端操作
- 中间操作(如
filter
,map
)返回新的 Stream,允许链式调用。 - 终端操作(如
collect
,forEach
,reduce
)触发 Stream 的执行并返回结果。
- 中间操作(如
- Stream 的创建和操作
示例:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 使用 Stream 过滤和收集结果
List<String> filteredList = list.stream()
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());
filteredList.forEach(System.out::println);
}
}
-
集合框架中的比较器
Java 提供了
Comparable
和Comparator
接口,用于定义集合元素的自然顺序和定制顺序。- Comparable:定义元素的自然顺序,通过实现
compareTo
方法。 - Comparator:用于定义自定义顺序,通过实现
compare
方法。
- Comparable:定义元素的自然顺序,通过实现
示例:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Person implements Comparable<Person> {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
@Override
public String toString() {
return name + ": " + age;
}
}
public class ComparatorExample {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
// 使用 Comparable 进行排序
Collections.sort(people);
System.out.println("Sorted by age: " + people);
// 使用 Comparator 进行自定义排序
people.sort(Comparator.comparing(p -> p.name));
System.out.println("Sorted by name: " + people);
}
}
六、集合框架的性能和优化
- 不同集合的性能比较
- ArrayList vs LinkedList
- ArrayList:
- 优势:基于数组,提供快速的随机访问(时间复杂度 O(1))。
- 劣势:在中间插入或删除元素时性能较差(时间复杂度 O(n))。
- 适用场景:适用于频繁读取元素或在列表末尾添加元素的场景。
- LinkedList:
- 优势:基于链表,在列表中间插入和删除元素时性能较好(时间复杂度 O(1))。
- 劣势:随机访问性能较差(时间复杂度 O(n))。
- 适用场景:适用于频繁在列表中间插入或删除元素的场景。
示例:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ListPerformanceExample {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
// 添加元素到 ArrayList 和 LinkedList
for (int i = 0; i < 100000; i++) {
arrayList.add(i);
linkedList.add(i);
}
// 测试 ArrayList 随机访问性能
long startTime = System.nanoTime();
arrayList.get(50000);
long endTime = System.nanoTime();
System.out.println("ArrayList get time: " + (endTime - startTime) + " ns");
// 测试 LinkedList 随机访问性能
startTime = System.nanoTime();
linkedList.get(50000);
endTime = System.nanoTime();
System.out.println("LinkedList get time: " + (endTime - startTime) + " ns");
}
}
- HashSet vs TreeSet
- HashSet:
- 优势:基于哈希表,提供快速的插入、删除和查找操作(平均时间复杂度 O(1))。
- 劣势:不保证元素的顺序。
- 适用场景:适用于需要快速查找、不关心元素顺序的场景。
- TreeSet:
- 优势:基于红黑树,保证元素的自然顺序或定制顺序(时间复杂度 O(log n))。
- 劣势:插入、删除和查找操作比 HashSet 慢。
- 适用场景:适用于需要有序集合的场景。
- HashSet:
示例:
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class SetPerformanceExample {
public static void main(String[] args) {
Set<Integer> hashSet = new HashSet<>();
Set<Integer> treeSet = new TreeSet<>();
// 添加元素到 HashSet 和 TreeSet
for (int i = 0; i < 100000; i++) {
hashSet.add(i);
treeSet.add(i);
}
// 测试 HashSet 查找性能
long startTime = System.nanoTime();
hashSet.contains(50000);
long endTime = System.nanoTime();
System.out.println("HashSet contains time: " + (endTime - startTime) + " ns");
// 测试 TreeSet 查找性能
startTime = System.nanoTime();
treeSet.contains(50000);
endTime = System.nanoTime();
System.out.println("TreeSet contains time: " + (endTime - startTime) + " ns");
}
}
- HashMap vs TreeMap
- HashMap:
- 优势:基于哈希表,提供快速的插入、删除和查找操作(平均时间复杂度 O(1))。
- 劣势:不保证键的顺序。
- 适用场景:适用于需要快速查找、不关心键顺序的场景。
- TreeMap:
- 优势:基于红黑树,保证键的自然顺序或定制顺序(时间复杂度 O(log n))。
- 劣势:插入、删除和查找操作比 HashMap 慢。
- 适用场景:适用于需要有序键值对的场景。
- HashMap:
示例:
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class MapPerformanceExample {
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<>();
Map<Integer, String> treeMap = new TreeMap<>();
// 添加元素到 HashMap 和 TreeMap
for (int i = 0; i < 100000; i++) {
hashMap.put(i, "value" + i);
treeMap.put(i, "value" + i);
}
// 测试 HashMap 查找性能
long startTime = System.nanoTime();
hashMap.get(50000);
long endTime = System.nanoTime();
System.out.println("HashMap get time: " + (endTime - startTime) + " ns");
// 测试 TreeMap 查找性能
startTime = System.nanoTime();
treeMap.get(50000);
endTime = System.nanoTime();
System.out.println("TreeMap get time: " + (endTime - startTime) + " ns");
}
}
-
集合的线程安全
Java 提供了一些工具类来将非线程安全的集合转换为线程安全的集合。
- Collections.synchronizedXXX() 方法
java.util.Collections
类提供了若干静态方法,可以将标准的集合类包装成线程安全的集合。例如:Collections.synchronizedList(List<T> list)
Collections.synchronizedSet(Set<T> s)
Collections.synchronizedMap(Map<K, V> m)
- 使用 java.util.concurrent 包中的集合类
- ConcurrentHashMap:是一个线程安全的哈希表,允许多个线程并发读取,并且在某些条件下允许多个线程并发写入。
- CopyOnWriteArrayList:是一种线程安全的变体数组列表,所有修改操作(如
add
、set
)都会创建一个新的内部数组,因此读操作无需同步。 - CopyOnWriteArraySet:基于
CopyOnWriteArrayList
实现的线程安全的 Set。
- Collections.synchronizedXXX() 方法
为了确保集合在多线程环境下的安全性,可以使用 Collections.synchronizedXXX()
方法将集合包装为线程安全的版本,或者直接使用 java.util.concurrent
包中的并发集合类。对于更复杂的场景,可以使用显式锁来控制对集合的访问。选择具体的实现时,应根据应用程序的具体需求、性能考虑和使用场景来决定。
七、集合框架的常见使用模式
-
集合的遍历和操作
- 单列数据(List)
Map<String, Integer> hashMap = new HashMap<>(); Map<String, Integer> treeMap = new TreeMap<>(); hashMap.put("one", 1); hashMap.put("two", 2); hashMap.put("three", 3); // 使用 keySet 遍历键 for (String key : hashMap.keySet()) { System.out.println(key + " -> " + hashMap.get(key)); } // 使用 entrySet 遍历键值对 for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } // 使用 forEach 方法(Java 8 及以上) hashMap.forEach((key, value) -> { System.out.println(key + " -> " + value); });
- 双列数据(将Map转换为Set才能使用迭代器,Map本身没有迭代器)
Map<String, Integer> hashMap = new HashMap<>(); Map<String, Integer> treeMap = new TreeMap<>(); hashMap.put("one", 1); hashMap.put("two", 2); hashMap.put("three", 3); // 使用 keySet 遍历键 for (String key : hashMap.keySet()) { System.out.println(key + " -> " + hashMap.get(key)); } // 使用 entrySet 遍历键值对 for (Map.Entry<String, Integer> entry : hashMap.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } // 使用 forEach 方法(Java 8 及以上) hashMap.forEach((key, value) -> { System.out.println(key + " -> " + value); });
-
集合的转换
- List 转 Set(去除重复元素)
List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three", "one")); Set<String> set = new HashSet<>(list); System.out.println(set); // 输出: [one, two, three]
- Set转List(保留集合元素的顺序或访问元素时使用索引)
Set<String> set = new HashSet<>(Arrays.asList("one", "two", "three")); List<String> list = new ArrayList<>(set); System.out.println(list); // 输出: [one, two, three]
- List转Map(将列表转换为键值对映射,通常需要提供键值的生成规则)
List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three")); Map<String, Integer> map = list.stream().collect(Collectors.toMap( Function.identity(), String::length )); System.out.println(map); // 输出: {one=3, two=3, three=5}
- Map转List(将键或值提取为列表)
Map<String, Integer> map = new HashMap<>(); map.put("one", 1); map.put("two", 2); map.put("three", 3); // 提取键 List<String> keys = new ArrayList<>(map.keySet()); System.out.println(keys); // 输出: [one, two, three] // 提取值 List<Integer> values = new ArrayList<>(map.values()); System.out.println(values); // 输出: [1, 2, 3]
- 数组转List(方便使用集合操作)
String[] array = {"one", "two", "three"}; List<String> list = Arrays.asList(array); System.out.println(list); // 输出: [one, two, three] //注意:Arrays.asList 返回的列表是固定大小的,不能添加或删除元素。如果需要一个可变大小的列表,可以使用以下方式 List<String> list = new ArrayList<>(Arrays.asList(array)); list.add("four"); System.out.println(list); // 输出: [one, two, three, four] ```java 6. List转数组(需要时转换) ```java List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three")); String[] array = list.toArray(new String[0]); System.out.println(Arrays.toString(array)); // 输出: [one, two, three]
- Set转Map(将集合转换为映射,通常需要提供键值的生成规则)
Set<String> set = new HashSet<>(Arrays.asList("one", "two", "three")); Map<String, Integer> map = set.stream().collect(Collectors.toMap( Function.identity(), String::length )); System.out.println(map); // 输出: {one=3, two=3, three=5}
- Map转Set(将键或值转为集合)
Map<String, Integer> map = new HashMap<>(); map.put("one", 1); map.put("two", 2); map.put("three", 3); // 提取键 Set<String> keySet = map.keySet(); System.out.println(keySet); // 输出: [one, two, three] // 提取值 Set<Integer> valueSet = new HashSet<>(map.values()); System.out.println(valueSet); // 输出: [1, 2, 3]
八、总结
Java 集合框架是一个功能丰富、灵活且高效的数据结构和算法库。通过合理地选择和使用集合框架中的各个组件,可以极大地简化编程任务,提高代码的可读性和运行效率。掌握集合框架的使用是每个 Java 开发者的必备技能。