一、Java开发中(非线程安全)集合容器相关API及其最佳实践
- 一、Java开发中(非线程安全)集合容器相关API及其最佳实践
- 对于容器(集合)的讲解
- (一)详细分析和比较 Java 集合框架,包括它们的特点、优势、劣势、性质和本质等方面
- (二)ArrayList
- (三)LinkedList
- (四)HashMap
- (五)TreeMap
- (六)LinkedHashMaps
- (七)HashSet
- (八)TreeSet
- (九)LinkedHashSet
- (十)PriorityQueue
- (十一)ArrayDeque
一、Java开发中(非线程安全)集合容器相关API及其最佳实践
注意:以下的所有这些集合框架都不是线程安全的,需要使用
Collections.synchronizedXXX
方法或Concurrent
包中的类来实现线程安全。
对于容器(集合)的讲解
在Java开发中,容器(Collections)是存储和操作数据的基本工具之一。使用容器的最佳实践能够极大地提高代码的性能、可读性和维护性。下面,将详细讲解Java开发中使用容器的最佳实践。
1. 容器的基本概念
定义
容器是用来存储和操作数据对象的集合类。Java提供了一系列的容器类和接口,它们位于 java.util
包中,主要包括 List
, Set
, Map
等。
分类
容器主要分为三类:
- List: 有序集合,可以包含重复元素。例如,
ArrayList
,LinkedList
. - Set: 无序集合,不包含重复元素。例如,
HashSet
,TreeSet
. - Map: 键值对集合,不包含重复键。例如,
HashMap
,TreeMap
.
2. 常用容器及其使用场景
List
ArrayList
: 适用于频繁读取数据的场景,因为其基于数组实现,访问速度快。但插入和删除操作较慢,因为需要移动元素。
List<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
LinkedList
: 适用于频繁插入和删除操作的场景,因为其基于链表实现,插入和删除速度快,但访问速度较慢。
List<String> linkedList = new LinkedList<>();
linkedList.add("Cherry");
linkedList.add("Date");
Set
HashSet
: 适用于需要快速查找的场景,因为其基于哈希表实现,查找速度快。但不保证顺序。
Set<String> hashSet = new HashSet<>();
hashSet.add("Elephant");
hashSet.add("Frog");
TreeSet
: 适用于需要排序的场景,因为其基于红黑树实现,能够对元素进行排序。
Set<String> treeSet = new TreeSet<>();
treeSet.add("Giraffe");
treeSet.add("Horse");
Map
HashMap
: 适用于需要快速查找键值对的场景,因为其基于哈希表实现,查找速度快。但不保证顺序。
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("Key1", 1);
hashMap.put("Key2", 2);
TreeMap
: 适用于需要按键排序的场景,因为其基于红黑树实现,能够对键进行排序。
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("KeyA", 100);
treeMap.put("KeyB", 200);
3. 容器使用的最佳实践
选择合适的容器
根据具体的使用场景选择合适的容器类型。例如,如果需要频繁进行插入和删除操作,可以选择LinkedList
;如果需要快速查找,可以选择HashMap
或HashSet
。
使用泛型(Generics)
使用泛型可以确保类型安全,避免在运行时发生 ClassCastException
。
List<String> list = new ArrayList<>();
list.add("Apple");
// list.add(123); // 编译错误
避免使用过时的容器类
使用现代的集合框架类(如ArrayList
、HashMap
等),避免使用过时的类(如Vector
、Hashtable
等)。
使用接口进行编程
尽量使用接口类型(如List
、Map
等)进行声明,而不是具体实现类(如ArrayList
、HashMap
等)。
List<String> list = new ArrayList<>();
使用 Collections
工具类
Java提供了Collections
工具类,包含了各种操作集合的方法,如排序、查找、线程安全处理等。
Collections.sort(list);
合理设置初始容量
在创建集合时,合理设置初始容量可以提高性能,避免频繁扩容带来的开销。
Map<String, Integer> map = new HashMap<>(16);
使用线程安全的集合
在多线程环境中,使用线程安全的集合类,如ConcurrentHashMap
、CopyOnWriteArrayList
等。
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
使用不可变集合
在需要只读集合的场景下,使用不可变集合可以提高安全性和性能。
List<String> immutableList = Collections.unmodifiableList(new ArrayList<>(list));
避免集合中的空值
尽量避免在集合中存储空值,尤其是在Map
中,这样可以避免NullPointerException
和其他潜在问题。
map.put("Key1", null); // 避免这种情况
4. 容器的高级用法
自定义比较器
对于需要自定义排序的场景,可以实现Comparator
接口。
class CustomComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
List<String> customList = new ArrayList<>(Arrays.asList("apple", "banana", "cherry"));
Collections.sort(customList, new CustomComparator());
使用流(Stream)进行操作
Java 8引入了Stream API,使得对集合的操作更加简洁和高效。
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
List<String> filteredFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("a"))
.collect(Collectors.toList());
使用 Optional
避免空指针异常
在需要处理可能为空的值时,使用Optional
可以提高代码的健壮性。
Optional<String> fruit = fruits.stream().filter(f -> f.startsWith("a")).findFirst();
fruit.ifPresent(System.out::println);
5. 性能优化
避免频繁扩容
在初始化集合时,尽量预估需要存储的元素数量,合理设置初始容量,避免频繁扩容带来的性能损耗。
使用适当的数据结构
选择合适的数据结构,以提高操作的性能。例如,如果需要频繁进行插入和删除操作,可以选择LinkedList
;如果需要快速查找,可以选择HashMap
或TreeSet
。
减少不必要的同步
在单线程环境中,避免使用线程安全的集合类(如Vector
、Hashtable
等),因为它们会带来额外的同步开销。
6. 结论
容器在Java开发中扮演着至关重要的角色,通过掌握和实践容器的最佳实践,可以编写出高效、可维护和健壮的代码。
(一)详细分析和比较 Java 集合框架,包括它们的特点、优势、劣势、性质和本质等方面
有关的分析内容都汇聚在该表格中
为了便于理解和比较这些 Java 集合框架,我将接下来的有关的分析内容都汇聚在一个详细的表格中。这将帮助你更直观地理解和熟练运用这些集合框架。
集合框架 | 实现方式 | 特点 | 优势 | 劣势 | 有序性 | 线程安全性 | 本质 | 常见使用场景 |
---|---|---|---|---|---|---|---|---|
ArrayList | 动态数组 | 支持随机访问 | 随机访问快,尾部操作快 | 中间插入、删除慢,需要移动元素 | 有序 | 非线程安全 | 动态数组 | 随机访问频繁,尾部操作频繁 |
LinkedList | 双向链表 | 高效的插入和删除 | 插入、删除快,不移动元素 | 随机访问慢 | 有序 | 非线程安全 | 双向链表 | 插入、删除频繁 |
HashMap | 哈希表 | 支持快速查找、插入和删除 | 查找、插入、删除快 | 无序 | 无序 | 非线程安全 | 哈希表 | 快速查找 |
TreeMap | 红黑树 | 有序键值对存储 | 有序,支持范围查询 | 插入、删除相对慢 | 有序 | 非线程安全 | 红黑树 | 有序查找,范围查询 |
LinkedHashMap | 哈希表 + 双向链表 | 支持插入顺序或访问顺序 | 有序,查找、插入、删除快 | 比 HashMap 占用更多内存 | 有序 | 非线程安全 | 哈希表 + 双向链表 | 插入顺序,访问顺序,缓存实现 |
HashSet | 哈希表 | 支持快速查找、插入和删除 | 查找、插入、删除快 | 无序 | 无序 | 非线程安全 | 哈希表 | 快速查找,不允许重复元素 |
TreeSet | 红黑树 | 有序元素存储 | 有序,支持范围查询 | 插入、删除相对慢 | 有序 | 非线程安全 | 红黑树 | 有序查找,不允许重复元素 |
LinkedHashSet | 哈希表 + 双向链表 | 支持插入顺序 | 有序,查找、插入、删除快 | 比 HashSet 占用更多内存 | 有序 | 非线程安全 | 哈希表 + 双向链表 | 插入顺序,不允许重复元素 |
PriorityQueue | 堆 | 支持优先级排序 | 优先级排序,高效插入、删除 | 不支持随机访问 | 无序 | 非线程安全 | 堆 | 优先级排序,高效处理任务 |
ArrayDeque | 动态数组 | 支持双端队列操作 | 双端操作快,高效插入、删除 | 中间插入、删除慢,需要移动元素 | 有序 | 非线程安全 | 动态数组 | 双端队列操作 |
下面将详细分析和比较这些 Java 集合框架,包括它们的特点、优势、劣势、性质和本质以及最佳实践的选择等。
比较总结
- 有序性:
ArrayList
,LinkedList
,TreeMap
,TreeSet
,LinkedHashMap
,LinkedHashSet
,ArrayDeque
是有序的;HashMap
,HashSet
,PriorityQueue
是无序的。 - 随机访问:
ArrayList
,HashMap
,TreeMap
,LinkedHashMap
,HashSet
,TreeSet
,LinkedHashSet
,PriorityQueue
支持随机访问;LinkedList
,ArrayDeque
不支持随机访问。 - 插入和删除:
LinkedList
,LinkedHashMap
,LinkedHashSet
,ArrayDeque
插入和删除快;ArrayList
,TreeMap
,TreeSet
,PriorityQueue
插入和删除相对慢。 - 内存占用:
LinkedHashMap
,LinkedHashSet
比对应的HashMap
,HashSet
占用更多内存。 - 线程安全:所有这些集合框架都不是线程安全的,需要使用
Collections.synchronizedXXX
方法或Concurrent
包中的类来实现线程安全。
最佳实践
- 随机访问频繁:使用
ArrayList
。 - 插入和删除频繁:使用
LinkedList
。 - 快速查找:使用
HashMap
。 - 有序查找:使用
TreeMap
。 - 插入顺序:使用
LinkedHashMap
或LinkedHashSet
。 - 优先级排序:使用
PriorityQueue
。 - 双端队列:使用
ArrayDeque
。
1. ArrayList
特点:基于动态数组实现,支持随机访问。
优势:随机访问快,尾部插入和删除快。
劣势:中间插入和删除慢,需要移动元素。
性质:有序,非线程安全。
本质:动态数组。
2. LinkedList
特点:基于双向链表实现,支持高效的插入和删除。
优势:插入和删除快,不需要移动元素。
劣势:随机访问慢,需要遍历链表。
性质:有序,非线程安全。
本质:双向链表。
3. HashMap
特点:基于哈希表实现,支持快速的查找、插入和删除。
优势:查找、插入和删除快。
劣势:无序,不保证元素的顺序。
性质:无序,非线程安全。
本质:哈希表。
4. TreeMap
特点:基于红黑树实现,支持有序的键值对存储。
优势:有序,支持范围查询。
劣势:插入和删除相对慢。
性质:有序,非线程安全。
本质:红黑树。
5. LinkedHashMap
特点:基于哈希表和双向链表实现,支持插入顺序或访问顺序。
优势:有序,查找、插入和删除快。
劣势:比 HashMap
占用更多内存。
性质:有序,非线程安全。
本质:哈希表 + 双向链表。
6. HashSet
特点:基于哈希表实现,支持快速的查找、插入和删除。
优势:查找、插入和删除快。
劣势:无序,不保证元素的顺序。
性质:无序,非线程安全。
本质:哈希表。
7. TreeSet
特点:基于红黑树实现,支持有序的元素存储。
优势:有序,支持范围查询。
劣势:插入和删除相对慢。
性质:有序,非线程安全。
本质:红黑树。
8. LinkedHashSet
特点:基于哈希表和双向链表实现,支持插入顺序。
优势:有序,查找、插入和删除快。
劣势:比 HashSet
占用更多内存。
性质:有序,非线程安全。
本质:哈希表 + 双向链表。
9. PriorityQueue
特点:基于堆实现,支持优先级排序。
优势:优先级排序,高效的插入和删除。
劣势:不支持随机访问。
性质:无序,非线程安全。
本质:堆。
10. ArrayDeque
特点:基于动态数组实现,支持双端队列操作。
优势:双端操作快,高效的插入和删除。
劣势:中间插入和删除慢,需要移动元素。
性质:有序,非线程安全。
本质:动态数组。
- 有序性:
ArrayList
,LinkedList
,TreeMap
,TreeSet
,LinkedHashMap
,LinkedHashSet
,ArrayDeque
是有序的;HashMap
,HashSet
,PriorityQueue
是无序的。 - 随机访问:
ArrayList
,HashMap
,TreeMap
,LinkedHashMap
,HashSet
,TreeSet
,LinkedHashSet
,PriorityQueue
支持随机访问;LinkedList
,ArrayDeque
不支持随机访问。 - 插入和删除:
LinkedList
,LinkedHashMap
,LinkedHashSet
,ArrayDeque
插入和删除快;ArrayList
,TreeMap
,TreeSet
,PriorityQueue
插入和删除相对慢。 - 内存占用:
LinkedHashMap
,LinkedHashSet
比对应的HashMap
,HashSet
占用更多内存。 - 线程安全:所有这些集合框架都不是线程安全的,需要使用
Collections.synchronizedXXX
方法或Concurrent
包中的类来实现线程安全。
(二)ArrayList
Java ArrayList 详解
ArrayList
是Java集合框架中的一个重要类,它是一种基于动态数组的数据结构。相比于传统固定大小的数组,ArrayList
在内存分配和管理上更为灵活。下面将详细介绍ArrayList
的使用、内部原理及最佳实践。
1. ArrayList 基本概念
定义
ArrayList
是Java集合框架中的类,位于java.util
包中,它实现了List
接口。ArrayList
是一个可调整大小的数组实现,允许所有元素(包括null
)。
主要特性
- 动态数组:可以自动调整大小。
- 有序:维护元素的插入顺序。
- 允许重复:可以包含重复的元素。
- 随机访问:基于索引的随机访问性能优越。
- 线程不安全:
ArrayList
不是线程安全的,适用于单线程环境。
2. ArrayList 的创建与初始化
2.1 无参构造函数
默认初始化一个空的ArrayList
。
ArrayList<String> list = new ArrayList<>();
2.2 指定初始容量
初始化一个具有指定初始容量的ArrayList
。
ArrayList<String> list = new ArrayList<>(10);
2.3 使用已有集合
通过已有集合初始化一个ArrayList
。
List<String> existingList = Arrays.asList("a", "b", "c");
ArrayList<String> list = new ArrayList<>(existingList);
3. ArrayList 的基本操作
3.1 添加元素
add
:在列表末尾添加元素。
list.add("Hello");
list.add("World");
add
:在指定索引位置添加元素。
list.add(1, "Java");
3.2 获取元素
get
:通过索引获取元素。
String value = list.get(0); // "Hello"
3.3 修改元素
set
:替换指定索引位置的元素。
list.set(1, "Python"); // 将索引1处的元素替换为"Python"
3.4 删除元素
remove
:通过索引删除元素。
list.remove(1); // 删除索引1处的元素
remove
:通过元素删除元素。
list.remove("Python"); // 删除值为"Python"的元素
3.5 检查元素
contains
:检查列表中是否包含某个元素。
boolean containsHello = list.contains("Hello"); // true
indexOf
:返回指定元素第一次出现的索引。
int index = list.indexOf("Hello"); // 0
3.6 遍历列表
- 传统for循环:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
- 增强型for循环:
for (String s : list) {
System.out.println(s);
}
- 使用Iterator:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
3.7 大小和清空
size
:获取列表的大小。
int size = list.size();
isEmpty
:检查列表是否为空。
boolean isEmpty = list.isEmpty(); // false
clear
:清空列表。
list.clear();
4. ArrayList 的高级操作
4.1 批量操作
addAll
:将一个集合中的所有元素添加到列表中。
List<String> newElements = Arrays.asList("a", "b", "c");
list.addAll(newElements);
removeAll
:移除列表中存在于指定集合中的所有元素。
list.removeAll(newElements);
retainAll
:保留列表中存在于指定集合中的所有元素。
list.retainAll(newElements);
4.2 子列表
subList
:获取列表的一个子部分。
List<String> sublist = list.subList(0, 2);
4.3 并发修改
ArrayList
是非线程安全的,如果在多线程环境中使用,可能导致并发修改异常。可以通过以下方式解决:
- 使用同步:
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
- 使用并发集合:
List<String> concurrentList = new CopyOnWriteArrayList<>();
5. ArrayList 的内存管理与性能
5.1 动态扩容机制
ArrayList
在默认情况下初始容量为10。- 当添加元素超过当前容量时,
ArrayList
会自动扩容,通常为原容量的1.5倍。 - 频繁的扩容操作会影响性能,因此在已知大致容量时,可以通过构造函数指定初始容量。
5.2 性能特点
- 随机访问快:基于数组实现,支持O(1)的随机访问。
- 插入和删除慢:插入和删除操作需要移动元素(平均O(n))。
- 空间浪费:可能会有未使用的预分配空间。
6. ArrayList 的使用示例
6.1 基本使用
public class ArrayListExample {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.add(1, "Java");
for (String s : list) {
System.out.println(s);
}
if (list.contains("World")) {
list.remove("World");
}
System.out.println("List size: " + list.size());
}
}
6.2 实现自定义排序
public class ArrayListSorting {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Banana");
list.add("Apple");
list.add("Cherry");
Collections.sort(list);
System.out.println("Sorted list: " + list);
Collections.sort(list, Collections.reverseOrder());
System.out.println("Reverse sorted list: " + list);
}
}
6.3 与其他集合转换
public class ArrayListConversion {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
// 转换为数组
String[] array = list.toArray(new String[0]);
System.out.println("Array: " + Arrays.toString(array));
// 转换为其他集合
Set<String> set = new HashSet<>(list);
System.out.println("Set: " + set);
}
}
7. ArrayList 的最佳实践
- 避免频繁扩容:在知道大致元素数量时,指定初始容量。
- 使用泛型:确保类型安全,避免类型转换异常。
- 线程安全:在多线程环境中使用同步列表或并发集合。
- 适当选择数据结构:根据需求选择合适的数据结构,
ArrayList
适用于随机访问频繁且插入删除较少的场景。
结论
ArrayList
是Java中一个常用且强大的集合类,它提供了灵活的动态数组实现,适用于各种场景。通过掌握ArrayList
的创建、基本操作、高级操作以及最佳实践,能够写出高效且易维护的代码。
(三)LinkedList
Java LinkedList 详解
LinkedList
是Java集合框架中的一个重要类,它实现了List
和Deque
接口,是一种基于双向链表的数据结构。与ArrayList
相比,LinkedList
在插入和删除操作上更为高效,但在随机访问方面则稍逊一筹。下面将详细介绍LinkedList
的使用、内部原理及最佳实践。
1. LinkedList 基本概念
定义
LinkedList
是Java集合框架中的类,位于java.util
包中,它实现了List
和Deque
接口。LinkedList
是一个双向链表实现,允许所有元素(包括null
)。
主要特性
- 双向链表:每个元素都包含前驱和后继节点的引用。
- 有序:维护元素的插入顺序。
- 允许重复:可以包含重复的元素。
- 随机访问慢:基于链表实现,随机访问性能较差。
- 插入和删除快:在链表头尾或中间插入和删除元素性能优越。
- 线程不安全:
LinkedList
不是线程安全的,适用于单线程环境。
2. LinkedList 的创建与初始化
2.1 无参构造函数
默认初始化一个空的LinkedList
。
LinkedList<String> list = new LinkedList<>();
2.2 使用已有集合
通过已有集合初始化一个LinkedList
。
List<String> existingList = Arrays.asList("a", "b", "c");
LinkedList<String> list = new LinkedList<>(existingList);
3. LinkedList 的基本操作
3.1 添加元素
add
:在列表末尾添加元素。
list.add("Hello");
list.add("World");
add
:在指定索引位置添加元素。
list.add(1, "Java");
addFirst
:在列表头部添加元素。
list.addFirst("First");
addLast
:在列表尾部添加元素。
list.addLast("Last");
3.2 获取元素
get
:通过索引获取元素。
String value = list.get(0); // "First"
getFirst
:获取列表头部元素。
String first = list.getFirst(); // "First"
getLast
:获取列表尾部元素。
String last = list.getLast(); // "Last"
3.3 修改元素
set
:替换指定索引位置的元素。
list.set(1, "Python"); // 将索引1处的元素替换为"Python"
3.4 删除元素
remove
:通过索引删除元素。
list.remove(1); // 删除索引1处的元素
remove
:通过元素删除元素。
list.remove("Python"); // 删除值为"Python"的元素
removeFirst
:删除列表头部元素。
list.removeFirst(); // 删除头部元素
removeLast
:删除列表尾部元素。
list.removeLast(); // 删除尾部元素
3.5 检查元素
contains
:检查列表中是否包含某个元素。
boolean containsHello = list.contains("Hello"); // true
indexOf
:返回指定元素第一次出现的索引。
int index = list.indexOf("Hello"); // 0
3.6 遍历列表
- 传统for循环:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
- 增强型for循环:
for (String s : list) {
System.out.println(s);
}
- 使用Iterator:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
3.7 大小和清空
size
:获取列表的大小。
int size = list.size();
isEmpty
:检查列表是否为空。
boolean isEmpty = list.isEmpty(); // false
clear
:清空列表。
list.clear();
4. LinkedList 的高级操作
4.1 队列操作
LinkedList
实现了Deque
接口,因此可以作为队列或双端队列使用。
offer
:在队列尾部添加元素。
list.offer("QueueElement");
poll
:移除并返回队列头部元素。
String head = list.poll();
peek
:返回队列头部元素,但不移除。
String head = list.peek();
4.2 栈操作
LinkedList
也可以作为栈使用。
push
:在栈顶添加元素。
list.push("StackElement");
pop
:移除并返回栈顶元素。
String top = list.pop();
peek
:返回栈顶元素,但不移除。
String top = list.peek();
5. LinkedList 的内存管理与性能
5.1 内存结构
LinkedList
的每个元素(节点)都包含前驱和后继节点的引用,因此每个节点占用更多的内存空间。- 由于是链表结构,
LinkedList
不需要预分配空间,因此内存使用更为灵活。
5.2 性能特点
- 插入和删除快:在链表头尾或中间插入和删除元素性能优越,时间复杂度为O(1)。
- 随机访问慢:基于链表实现,随机访问性能较差,时间复杂度为O(n)。
- 空间灵活:不需要预分配空间,内存使用更为灵活。
6. LinkedList 的使用示例
6.1 基本使用
public class LinkedListExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("Hello");
list.add("World");
list.add(1, "Java");
for (String s : list) {
System.out.println(s);
}
if (list.contains("World")) {
list.remove("World");
}
System.out.println("List size: " + list.size());
}
}
6.2 实现队列
public class LinkedListQueue {
public static void main(String[] args) {
LinkedList<String> queue = new LinkedList<>();
queue.offer("First");
queue.offer("Second");
queue.offer("Third");
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
}
}
6.3 实现栈
public class LinkedListStack {
public static void main(String[] args) {
LinkedList<String> stack = new LinkedList<>();
stack.push("First");
stack.push("Second");
stack.push("Third");
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
}
7. LinkedList 的最佳实践
- 避免频繁随机访问:由于
LinkedList
随机访问性能较差,尽量避免频繁的随机访问操作。 - 使用泛型:确保类型安全,避免类型转换异常。
- 线程安全:在多线程环境中使用同步列表或并发集合。
- 适当选择数据结构:根据需求选择合适的数据结构,
LinkedList
适用于插入和删除频繁且随机访问较少的场景。
结论
LinkedList
是Java中一个常用且强大的集合类,它提供了灵活的双向链表实现,适用于各种场景。通过掌握LinkedList
的创建、基本操作、高级操作以及最佳实践,能够写出高效且易维护的代码。
(四)HashMap
Java HashMap 详解
HashMap
是Java集合框架中的一个重要类,它基于哈希表实现,是一种用于存储键值对的哈希映射。HashMap
提供了高效的插入、删除和查找操作。下面将详细介绍HashMap
的使用、内部原理及最佳实践。
1. HashMap 基本概念
定义
HashMap
是Java集合框架中的类,位于java.util
包中,它实现了Map
接口。HashMap
基于哈希表实现,是一个用于存储键值对的哈希映射。
主要特性
- 键值对存储:每个元素存储为一个键值对。
- 无序:不保证元素的插入顺序。
- 允许
null
键和值:最多允许一个null
键和多个null
值。 - 基于哈希表:提供高效的插入、删除和查找操作。
- 线程不安全:
HashMap
不是线程安全的,适用于单线程环境。
2. HashMap 的创建与初始化
2.1 无参构造函数
默认初始化一个空的HashMap
。
HashMap<String, Integer> map = new HashMap<>();
2.2 指定初始容量和加载因子
可以指定初始容量和加载因子来初始化一个HashMap
。
HashMap<String, Integer> map = new HashMap<>(16, 0.75f);
2.3 使用已有映射
通过已有映射初始化一个HashMap
。
Map<String, Integer> existingMap = new HashMap<>();
existingMap.put("a", 1);
existingMap.put("b", 2);
HashMap<String, Integer> map = new HashMap<>(existingMap);
3. HashMap 的基本操作
3.1 添加和替换元素
put
:向映射中添加键值对,如果键已存在,则替换其对应的值。
map.put("Hello", 1);
map.put("World", 2);
map.put("Hello", 3); // 替换 "Hello" 的值为 3
putIfAbsent
:仅当键不存在时,才向映射中添加键值对。
map.putIfAbsent("Java", 4);
3.2 获取元素
get
:通过键获取对应的值,如果键不存在则返回null
。
Integer value = map.get("Hello"); // 3
getOrDefault
:通过键获取对应的值,如果键不存在则返回指定的默认值。
Integer value = map.getOrDefault("Python", 0); // 0
3.3 检查元素
containsKey
:检查映射中是否包含指定的键。
boolean containsHello = map.containsKey("Hello"); // true
containsValue
:检查映射中是否包含指定的值。
boolean containsValue = map.containsValue(3); // true
3.4 删除元素
remove
:通过键删除对应的键值对。
map.remove("World");
remove
:通过键和值删除对应的键值对,仅当键和值同时匹配时才删除。
map.remove("Hello", 3); // 仅当 "Hello" 对应的值为 3 时删除
3.5 大小和清空
size
:获取映射的大小。
int size = map.size();
isEmpty
:检查映射是否为空。
boolean isEmpty = map.isEmpty(); // false
clear
:清空映射。
map.clear();
3.6 遍历映射
- 遍历键值对:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
- 遍历键:
for (String key : map.keySet()) {
System.out.println("Key: " + key);
}
- 遍历值:
for (Integer value : map.values()) {
System.out.println("Value: " + value);
}
4. HashMap 的高级操作
4.1 批量操作
putAll
:将一个映射中的所有键值对添加到HashMap
中。
Map<String, Integer> newEntries = new HashMap<>();
newEntries.put("Python", 5);
newEntries.put("JavaScript", 6);
map.putAll(newEntries);
4.2 计算操作
compute
:对指定的键进行重新计算并更新其值。
map.compute("Hello", (key, value) -> (value == null) ? 1 : value + 1);
computeIfAbsent
:仅当键不存在时进行计算并更新其值。
map.computeIfAbsent("Ruby", key -> 7);
computeIfPresent
:仅当键存在时进行计算并更新其值。
map.computeIfPresent("Java", (key, value) -> value + 1);
4.3 合并操作
merge
:如果键不存在,则添加键值对;如果键存在,则对值进行合并。
map.merge("Java", 1, Integer::sum);
4.4 替换操作
replace
:替换指定键的值,仅当键存在时才替换。
map.replace("Python", 8);
replace
:仅当键和值同时匹配时才替换。
map.replace("JavaScript", 6, 9);
5. 内部原理与性能
5.1 哈希表实现
HashMap
内部使用哈希表(数组 + 链表/红黑树)来存储键值对。键的哈希码通过哈希函数转换为哈希表的索引。
5.2 哈希函数与冲突解决
元素的哈希码通过哈希函数转换为哈希表的索引。哈希冲突(即不同键的哈希码映射到同一索引)通过链地址法(链表或红黑树)解决。
5.3 扩容机制
HashMap
的默认加载因子为0.75,当映射中的元素数量超过当前容量与加载因子的乘积时,哈希表会进行扩容(通常扩容为原容量的2倍),以减少冲突。
5.4 性能特点
- 插入、删除、查找:平均时间复杂度为O(1)。
- 遍历:时间复杂度为O(n),元素无序。
6. HashMap 的使用示例
6.1 基本使用
public class HashMapExample {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("Hello", 1);
map.put("World", 2);
map.put("Hello", 3); // 替换 "Hello" 的值为 3
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
if (map.containsKey("World")) {
map.remove("World");
}
System.out.println("Map size: " + map.size());
}
}
6.2 批量操作
public class HashMapBatchOperations {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("Hello", 1);
map.put("World", 2);
Map<String, Integer> newEntries = new HashMap<>();
newEntries.put("Python", 5);
newEntries.put("JavaScript", 6);
map.putAll(newEntries);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
6.3 计算操作
public class HashMapComputeOperations {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("Hello", 1);
map.put("World", 2);
// 计算并更新 "Hello" 的值
map.compute("Hello", (key, value) -> (value == null) ? 1 : value + 1);
System.out.println("Hello: " + map.get("Hello")); // 2
// 仅当 "Ruby" 不存在时添加
map.computeIfAbsent("Ruby", key -> 7);
System.out.println("Ruby: " + map.get("Ruby")); // 7
// 仅当 "World" 存在时更新
map.computeIfPresent("World", (key, value) -> value + 1);
System.out.println("World: " + map.get("World")); // 3
}
}
7. HashMap 的最佳实践
- 使用合适的初始容量:在已知元素数量时,指定合适的初始容量以减少扩容次数。
- 实现
hashCode
和equals
方法:确保自定义对象的hashCode
和equals
方法正确实现,以保证HashMap
的正确性。 - 线程安全:在多线程环境中使用同步映射或并发映射(如
ConcurrentHashMap
)。 - 避免高负载因子:高负载因子会增加哈希冲突的概率,影响性能。
- 根据需求选择数据结构:
HashMap
适用于需要快速查找、不关心顺序且允许null
键和值的场景。
结论
HashMap
是Java中一个常用且强大的集合类,它提供了基于哈希表的高效映射实现,适用于各种场景。通过掌握HashMap
的创建、基本操作、高级操作以及最佳实践,能够写出高效且易维护的代码。
(五)TreeMap
Java TreeMap 详解
TreeMap
是Java集合框架中的一个重要类,它基于红黑树实现,是一种有序的键值对映射。TreeMap
提供了按键排序、范围查找和高效的插入、删除、查找操作。下面将详细介绍TreeMap
的使用、内部原理及最佳实践。
1. TreeMap 基本概念
定义
TreeMap
是Java集合框架中的类,位于java.util
包中,它实现了NavigableMap
接口,并继承了AbstractMap
类。TreeMap
基于红黑树实现,是一个按键排序的有序映射。
主要特性
- 键值对存储:每个元素存储为一个键值对。
- 有序:按键的自然顺序或通过提供的比较器排序。
- 不允许
null
键:TreeMap
不允许null
键,但允许null
值。 - 基于红黑树:提供高效的插入、删除和查找操作。
- 线程不安全:
TreeMap
不是线程安全的,适用于单线程环境。
2. TreeMap 的创建与初始化
2.1 无参构造函数
默认初始化一个空的TreeMap
,使用键的自然顺序进行排序。
TreeMap<String, Integer> map = new TreeMap<>();
2.2 使用比较器
通过指定比较器来初始化一个TreeMap
,使用比较器对键进行排序。
TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
2.3 使用已有映射
通过已有映射初始化一个TreeMap
。
Map<String, Integer> existingMap = new HashMap<>();
existingMap.put("a", 1);
existingMap.put("b", 2);
TreeMap<String, Integer> map = new TreeMap<>(existingMap);
3. TreeMap 的基本操作
3.1 添加和替换元素
put
:向映射中添加键值对,如果键已存在,则替换其对应的值。
map.put("Hello", 1);
map.put("World", 2);
map.put("Hello", 3); // 替换 "Hello" 的值为 3
putIfAbsent
:仅当键不存在时,才向映射中添加键值对。
map.putIfAbsent("Java", 4);
3.2 获取元素
get
:通过键获取对应的值,如果键不存在则返回null
。
Integer value = map.get("Hello"); // 3
getOrDefault
:通过键获取对应的值,如果键不存在则返回指定的默认值。
Integer value = map.getOrDefault("Python", 0); // 0
3.3 检查元素
containsKey
:检查映射中是否包含指定的键。
boolean containsHello = map.containsKey("Hello"); // true
containsValue
:检查映射中是否包含指定的值。
boolean containsValue = map.containsValue(3); // true
3.4 删除元素
remove
:通过键删除对应的键值对。
map.remove("World");
remove
:通过键和值删除对应的键值对,仅当键和值同时匹配时才删除。
map.remove("Hello", 3); // 仅当 "Hello" 对应的值为 3 时删除
3.5 大小和清空
size
:获取映射的大小。
int size = map.size();
isEmpty
:检查映射是否为空。
boolean isEmpty = map.isEmpty(); // false
clear
:清空映射。
map.clear();
3.6 遍历映射
- 遍历键值对:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
- 遍历键:
for (String key : map.keySet()) {
System.out.println("Key: " + key);
}
- 遍历值:
for (Integer value : map.values()) {
System.out.println("Value: " + value);
}
4. TreeMap 的高级操作
4.1 获取子映射
subMap
:获取从fromKey
(包括)到toKey
(不包括)的子映射。
SortedMap<String, Integer> subMap = map.subMap("a", "c");
headMap
:获取到toKey
(不包括)的子映射。
SortedMap<String, Integer> headMap = map.headMap("c");
tailMap
:获取从fromKey
(包括)的子映射。
SortedMap<String, Integer> tailMap = map.tailMap("b");
4.2 获取边界元素
firstKey
:获取映射中的第一个(最小)键。
String firstKey = map.firstKey();
lastKey
:获取映射中的最后一个(最大)键。
String lastKey = map.lastKey();
higherKey
:获取映射中大于给定键的最小键。
String higherKey = map.higherKey("b"); // c
lowerKey
:获取映射中小于给定键的最大键。
String lowerKey = map.lowerKey("b"); // a
ceilingKey
:获取映射中大于或等于给定键的最小键。
String ceilingKey = map.ceilingKey("b"); // b
floorKey
:获取映射中小于或等于给定键的最大键。
String floorKey = map.floorKey("b"); // b
4.3 批量操作
putAll
:将一个映射中的所有键值对添加到TreeMap
中。
Map<String, Integer> newEntries = new HashMap<>();
newEntries.put("Python", 5);
newEntries.put("JavaScript", 6);
map.putAll(newEntries);
4.4 计算操作
compute
:对指定的键进行重新计算并更新其值。
map.compute("Hello", (key, value) -> (value == null) ? 1 : value + 1);
computeIfAbsent
:仅当键不存在时进行计算并更新其值。
map.computeIfAbsent("Ruby", key -> 7);
computeIfPresent
:仅当键存在时进行计算并更新其值。
map.computeIfPresent("World", (key, value) -> value + 1);
4.5 合并操作
merge
:如果键不存在,则添加键值对;如果键存在,则对值进行合并。
map.merge("Java", 1, Integer::sum);
4.6 替换操作
replace
:替换指定键的值,仅当键存在时才替换。
map.replace("Python", 8);
replace
:仅当键和值同时匹配时才替换。
map.replace("JavaScript", 6, 9);
5. 内部原理与性能
5.1 红黑树实现
TreeMap
内部使用红黑树(Red-Black Tree)来存储键值对。红黑树是一种自平衡二叉搜索树,确保插入、删除和查找操作的时间复杂度为O(log n)。
5.2 性能特点
- 插入、删除、查找:时间复杂度为O(log n)。
- 遍历:时间复杂度为O(n),元素按键排序顺序遍历。
6. TreeMap 的使用示例
6.1 基本使用
public class TreeMapExample {
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<>();
map.put("Hello", 1);
map.put("World", 2);
map.put("Hello", 3); // 替换 "Hello" 的值为 3
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
if (map.containsKey("World")) {
map.remove("World");
}
System.out.println("Map size: " + map.size());
}
}
6.2 获取子映射和边界元素
public class TreeMapSubMapExample {
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<>(Arrays.asList("a", "b", "c", "d", "e"));
// 获取子映射
SortedMap<String, Integer> subMap = map.subMap("b", "d");
System.out.println("SubMap: " + subMap);
// 获取边界元素
String firstKey = map.firstKey();
String lastKey = map.lastKey();
String higherKey = map.higherKey("b");
String lowerKey = map.lowerKey("b");
System.out.println("First Key: " + firstKey);
System.out.println("Last Key: " + lastKey);
System.out.println("Higher than 'b': " + higherKey);
System.out.println("Lower than 'b': " + lowerKey);
}
}
6.3 使用比较器
public class TreeMapComparatorExample {
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<>(Comparator.reverseOrder());
map.put("Hello", 1);
map.put("World", 2);
map.put("Java", 3);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
7. TreeMap 的最佳实践
- 使用合适的比较器:根据需求提供自定义比较器,实现自定义排序。
- 避免使用
null
键:TreeMap
不允许null
键,避免引发NullPointerException
。 - 线程安全:在多线程环境中使用同步映射或并发映射(如
ConcurrentSkipListMap
)。 - 根据需求选择数据结构:
TreeMap
适用于需要排序、范围查找且不允许null
键的场景。
结论
TreeMap
是Java中一个常用且强大的集合类,它提供了基于红黑树的有序映射实现,适用于各种场景。通过掌握TreeMap
的创建、基本操作、高级操作以及最佳实践,能够写出高效且易维护的代码。
(六)LinkedHashMaps
LinkedHashMap
是 Java 集合框架中的一个非常有用的类,它结合了 HashMap
的高效性和 LinkedList
的有序性。它不仅继承了 HashMap
的所有特性,还维护了一个双向链表,记录元素的插入顺序或访问顺序。接下来,将详细讲解 LinkedHashMap
及其所有的方法。
1. 基本概念
LinkedHashMap
是 HashMap
的子类,它不仅继承了 HashMap
的所有特性,还维护了一个双向链表,在插入元素时保留其插入顺序或访问顺序。这使得 LinkedHashMap
可以在迭代时按照插入顺序或最近访问顺序返回元素。
2. 构造方法
LinkedHashMap
提供了以下几种构造方法:
LinkedHashMap()
:创建一个初始容量为 16、加载因子为 0.75 的空LinkedHashMap
。LinkedHashMap(int initialCapacity)
:创建一个具有指定初始容量和默认加载因子的空LinkedHashMap
。LinkedHashMap(int initialCapacity, float loadFactor)
:创建一个具有指定初始容量和加载因子的空LinkedHashMap
。LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
:创建一个具有指定初始容量、加载因子和顺序模式(插入顺序或访问顺序)的空LinkedHashMap
。LinkedHashMap(Map<? extends K, ? extends V> m)
:创建一个包含指定映射中的所有映射关系的LinkedHashMap
,这些映射关系将按照集合的迭代器返回的顺序排列。
3. 常见方法
LinkedHashMap
继承了 HashMap
和 AbstractMap
的方法,并新增了一些方法。以下是详细介绍:
添加和修改元素
V put(K key, V value)
:将指定值与此映射中的指定键相关联。如果映射之前包含一个该键的映射关系,则替换旧值。void putAll(Map<? extends K, ? extends V> m)
:将指定映射的所有映射关系复制到此映射中。V putIfAbsent(K key, V value)
:如果指定键尚未在映射中关联值,则将其与指定值相关联。V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
:尝试计算指定键及其当前映射值(如果存在)的映射关系,并将其重新映射(可以为null
)。
移除元素
V remove(Object key)
:从此映射中移除指定键的映射关系(如果存在)。boolean remove(Object key, Object value)
:仅在指定键当前映射到指定值时,移除该键的映射关系。void clear()
:从此映射中移除所有映射关系。
查找元素
V get(Object key)
:返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回null
。boolean containsKey(Object key)
:如果此映射包含指定键的映射关系,则返回true
。boolean containsValue(Object value)
:如果此映射将一个或多个键映射到指定值,则返回true
。
大小和判断
int size()
:返回此映射中的键值对数量。boolean isEmpty()
:如果此映射不包含键值对,则返回true
。
遍历元素
Set<K> keySet()
:返回此映射中包含的键的Set
视图。Collection<V> values()
:返回此映射中包含的值的Collection
视图。Set<Map.Entry<K,V>> entrySet()
:返回此映射中包含的映射关系的Set
视图。
顺序控制
boolean accessOrder()
:返回此映射是否按访问顺序(从最近访问到最远访问)维护键的顺序。
4. 例子
以下是一些使用 LinkedHashMap
的示例代码:
基本使用
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapExample {
public static void main(String[] args) {
LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>();
// 添加元素
linkedHashMap.put(1, "One");
linkedHashMap.put(2, "Two");
linkedHashMap.put(3, "Three");
// 检查元素是否存在
System.out.println("Contains key 2? " + linkedHashMap.containsKey(2)); // 输出 true
System.out.println("Contains value 'Two'? " + linkedHashMap.containsValue("Two")); // 输出 true
// 遍历元素
System.out.println("LinkedHashMap elements:");
for (Map.Entry<Integer, String> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 移除元素
linkedHashMap.remove(2);
System.out.println("After removing key 2, LinkedHashMap elements:");
for (Map.Entry<Integer, String> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 清空集合
linkedHashMap.clear();
System.out.println("Is LinkedHashMap empty? " + linkedHashMap.isEmpty()); // 输出 true
}
}
使用指定初始容量和加载因子的构造方法
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapWithCapacityExample {
public static void main(String[] args) {
LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>(10, 0.75f);
// 添加元素
linkedHashMap.put(1, "One");
linkedHashMap.put(2, "Two");
linkedHashMap.put(3, "Three");
// 遍历元素
System.out.println("LinkedHashMap elements:");
for (Map.Entry<Integer, String> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
使用访问顺序的构造方法
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapAccessOrderExample {
public static void main(String[] args) {
LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>(10, 0.75f, true);
// 添加元素
linkedHashMap.put(1, "One");
linkedHashMap.put(2, "Two");
linkedHashMap.put(3, "Three");
// 访问元素
linkedHashMap.get(1);
linkedHashMap.get(3);
// 遍历元素
System.out.println("LinkedHashMap elements (access order):");
for (Map.Entry<Integer, String> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
5. 内部实现
LinkedHashMap
通过 HashMap
和双向链表实现。这意味着 LinkedHashMap
维护了一个链接列表,该列表将所有插入的元素按插入顺序或访问顺序存储,同时使用哈希表来实现快速的查找。
关键点
- 哈希表:用于快速查找元素。
- 双向链表:用于维护元素的插入顺序或访问顺序。
LinkedHashMap
的基本结构
- 头指针和尾指针:用于维护插入或访问顺序。
- 哈希表:用于快速查找和插入元素。
6. 使用场景
- 保留插入顺序:当需要保留元素的插入顺序时,
LinkedHashMap
是一个理想选择。 - 按访问顺序迭代:当需要按最近访问顺序迭代元素时,
LinkedHashMap
可以通过设置访问顺序模式实现。 - 缓存实现:可以基于
LinkedHashMap
实现简单的 LRU(最近最少使用)缓存。
7. 注意事项
- 性能:
LinkedHashMap
的查找、插入、删除操作的时间复杂度为 O(1)。 - 非线程安全:
LinkedHashMap
不是线程安全的,如果需要在多线程环境中使用,可以使用Collections.synchronizedMap
。
(七)HashSet
Java HashSet 详解
HashSet
是Java集合框架中的一个重要类,它基于哈希表实现,是一种不允许有重复元素且不保证顺序的集合。HashSet
提供了高效的插入、删除和查找操作。下面将详细介绍HashSet
的使用、内部原理及最佳实践。
1. HashSet 基本概念
定义
HashSet
是Java集合框架中的类,位于java.util
包中,它实现了Set
接口。HashSet
基于哈希表实现,是一个不允许有重复元素的集合。
主要特性
- 无重复:不允许有重复元素。
- 无序:不保证元素的插入顺序。
- 允许null:可以包含一个空值(null)。
- 基于哈希表:提供高效的插入、删除和查找操作。
- 线程不安全:
HashSet
不是线程安全的,适用于单线程环境。
2. HashSet 的创建与初始化
2.1 无参构造函数
默认初始化一个空的HashSet
。
HashSet<String> set = new HashSet<>();
2.2 指定初始容量和加载因子
可以指定初始容量和加载因子来初始化一个HashSet
。
HashSet<String> set = new HashSet<>(16, 0.75f);
2.3 使用已有集合
通过已有集合初始化一个HashSet
。
Set<String> existingSet = new HashSet<>(Arrays.asList("a", "b", "c"));
HashSet<String> set = new HashSet<>(existingSet);
3. HashSet 的基本操作
3.1 添加元素
add
:向集合中添加元素,如果元素已存在则不添加。
set.add("Hello");
set.add("World");
3.2 删除元素
remove
:从集合中删除指定元素,如果元素不存在则不做任何操作。
set.remove("World");
3.3 检查元素
contains
:检查集合中是否包含指定元素。
boolean containsHello = set.contains("Hello"); // true
3.4 大小和清空
size
:获取集合的大小。
int size = set.size();
isEmpty
:检查集合是否为空。
boolean isEmpty = set.isEmpty(); // false
clear
:清空集合。
set.clear();
3.5 遍历集合
- 增强型for循环:
for (String s : set) {
System.out.println(s);
}
- 使用Iterator:
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
4. HashSet 的高级操作
4.1 批量操作
addAll
:将一个集合中的所有元素添加到HashSet
中,忽略重复元素。
Set<String> newElements = new HashSet<>(Arrays.asList("a", "b", "c"));
set.addAll(newElements);
removeAll
:移除集合中存在于指定集合中的所有元素。
set.removeAll(newElements);
retainAll
:保留集合中存在于指定集合中的所有元素。
set.retainAll(newElements);
4.2 转换为其他集合
- 转换为
ArrayList
:
List<String> list = new ArrayList<>(set);
- 转换为数组:
String[] array = set.toArray(new String[0]);
5. 内部原理与性能
5.1 哈希表实现
HashSet
内部使用HashMap
来存储元素。每个添加到HashSet
中的元素实际上是作为HashMap
的键来存储的,值则是一个固定的常量。
5.2 哈希函数与冲突
元素的哈希码通过哈希函数转换为哈希表的索引,如果发生哈希冲突(即不同元素的哈希码映射到同一索引),使用链地址法来解决冲突。
5.3 加载因子与扩容
HashSet
的默认加载因子是0.75,当集合中的元素数量超过当前容量与加载因子的乘积时,哈希表会进行扩容(通常扩容为原容量的2倍),以减少冲突。
5.4 性能特点
- 插入、删除、查找:平均时间复杂度为O(1)。
- 遍历:时间复杂度为O(n)。
6. HashSet 的使用示例
6.1 基本使用
public class HashSetExample {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("Hello");
set.add("World");
set.add("Hello"); // 重复元素不会被添加
for (String s : set) {
System.out.println(s);
}
if (set.contains("World")) {
set.remove("World");
}
System.out.println("Set size: " + set.size());
}
}
6.2 集合操作
public class HashSetOperations {
public static void main(String[] args) {
HashSet<String> set1 = new HashSet<>(Arrays.asList("a", "b", "c"));
HashSet<String> set2 = new HashSet<>(Arrays.asList("b", "c", "d"));
// 并集
HashSet<String> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("Union: " + union);
// 交集
HashSet<String> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("Intersection: " + intersection);
// 差集
HashSet<String> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("Difference: " + difference);
}
}
6.3 去除重复元素
public class RemoveDuplicates {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "a", "b", "c");
HashSet<String> set = new HashSet<>(list);
System.out.println("Unique elements: " + set);
}
}
7. HashSet 的最佳实践
- 使用合适的初始容量:在已知元素数量时,指定合适的初始容量以减少扩容次数。
- 实现
hashCode
和equals
方法:确保自定义对象的hashCode
和equals
方法正确实现,以保证HashSet
的正确性。 - 避免过高的加载因子:加载因子过高会增加哈希冲突的概率,影响性能。
- 线程安全:在多线程环境中使用同步集合或并发集合。
- 根据需求选择数据结构:
HashSet
适用于需要快速查找、不允许重复且不关心顺序的场景。
结论
HashSet
是Java中一个常用且强大的集合类,它提供了基于哈希表的高效集合实现,适用于各种场景。通过掌握HashSet
的创建、基本操作、高级操作以及最佳实践,能够写出高效且易维护的代码。
(八)TreeSet
Java TreeSet 详解
TreeSet
是Java集合框架中的一个重要类,它基于红黑树实现,是一个有序且不允许重复元素的集合。TreeSet
提供了排序、范围查找和高效的插入、删除、查找操作。下面将详细介绍TreeSet
的使用、内部原理及最佳实践。
1. TreeSet 基本概念
定义
TreeSet
是Java集合框架中的类,位于java.util
包中,它实现了NavigableSet
接口,并继承了AbstractSet
类。TreeSet
基于红黑树实现,是一个有序且不允许重复元素的集合。
主要特性
- 有序:自然顺序或通过提供的比较器排序。
- 不允许重复:不允许有重复元素。
- 基于红黑树:提供高效的插入、删除和查找操作。
- 允许null:不允许
null
元素(从Java 7开始)。 - 线程不安全:
TreeSet
不是线程安全的,适用于单线程环境。
2. TreeSet 的创建与初始化
2.1 无参构造函数
默认初始化一个空的TreeSet
,使用元素的自然顺序进行排序。
TreeSet<String> set = new TreeSet<>();
2.2 使用比较器
通过指定比较器来初始化一个TreeSet
,使用比较器排序。
TreeSet<String> set = new TreeSet<>(Comparator.reverseOrder());
2.3 使用已有集合
通过已有集合初始化一个TreeSet
。
Set<String> existingSet = new HashSet<>(Arrays.asList("a", "b", "c"));
TreeSet<String> set = new TreeSet<>(existingSet);
3. TreeSet 的基本操作
3.1 添加元素
add
:向集合中添加元素,如果元素已存在则不添加。
set.add("Hello");
set.add("World");
3.2 删除元素
remove
:从集合中删除指定元素,如果元素不存在则不做任何操作。
set.remove("World");
3.3 检查元素
contains
:检查集合中是否包含指定元素。
boolean containsHello = set.contains("Hello"); // true
3.4 大小和清空
size
:获取集合的大小。
int size = set.size();
isEmpty
:检查集合是否为空。
boolean isEmpty = set.isEmpty(); // false
clear
:清空集合。
set.clear();
3.5 遍历集合
- 增强型for循环:
for (String s : set) {
System.out.println(s);
}
- 使用Iterator:
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
4. TreeSet 的高级操作
4.1 获取子集
subSet
:获取从fromElement
(包括)到toElement
(不包括)的子集。
NavigableSet<String> subset = set.subSet("a", true, "c", false);
headSet
:获取到toElement
(不包括)的子集。
NavigableSet<String> headset = set.headSet("c", false);
tailSet
:获取从fromElement
(包括)的子集。
NavigableSet<String> tailset = set.tailSet("b", true);
4.2 获取边界元素
first
:获取集合中的第一个(最小)元素。
String first = set.first();
last
:获取集合中的最后一个(最大)元素。
String last = set.last();
higher
:获取集合中大于给定元素的最小元素。
String higher = set.higher("b");
lower
:获取集合中小于给定元素的最大元素。
String lower = set.lower("b");
ceiling
:获取集合中大于或等于给定元素的最小元素。
String ceiling = set.ceiling("b");
floor
:获取集合中小于或等于给定元素的最大元素。
String floor = set.floor("b");
4.3 批量操作
addAll
:将一个集合中的所有元素添加到TreeSet
中,忽略重复元素。
Set<String> newElements = new HashSet<>(Arrays.asList("a", "b", "c"));
set.addAll(newElements);
removeAll
:移除集合中存在于指定集合中的所有元素。
set.removeAll(newElements);
retainAll
:保留集合中存在于指定集合中的所有元素。
set.retainAll(newElements);
4.4 转换为其他集合
- 转换为
ArrayList
:
List<String> list = new ArrayList<>(set);
- 转换为数组:
String[] array = set.toArray(new String[0]);
5. 内部原理与性能
5.1 红黑树实现
TreeSet
内部使用红黑树(Red-Black Tree)来存储元素。红黑树是一种自平衡二叉搜索树,确保插入、删除和查找操作的时间复杂度为O(log n)。
5.2 性能特点
- 插入、删除、查找:时间复杂度为O(log n)。
- 遍历:时间复杂度为O(n),元素按排序顺序遍历。
6. TreeSet 的使用示例
6.1 基本使用
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<String> set = new TreeSet<>();
set.add("Hello");
set.add("World");
set.add("Hello"); // 重复元素不会被添加
for (String s : set) {
System.out.println(s);
}
if (set.contains("World")) {
set.remove("World");
}
System.out.println("Set size: " + set.size());
}
}
6.2 获取子集和边界元素
public class TreeSetSubSetExample {
public static void main(String[] args) {
TreeSet<String> set = new TreeSet<>(Arrays.asList("a", "b", "c", "d", "e"));
// 获取子集
NavigableSet<String> subset = set.subSet("b", true, "d", true);
System.out.println("Subset: " + subset);
// 获取边界元素
String first = set.first();
String last = set.last();
String higher = set.higher("b");
String lower = set.lower("b");
System.out.println("First: " + first);
System.out.println("Last: " + last);
System.out.println("Higher than 'b': " + higher);
System.out.println("Lower than 'b': " + lower);
}
}
6.3 使用比较器
public class TreeSetComparatorExample {
public static void main(String[] args) {
TreeSet<String> set = new TreeSet<>(Comparator.reverseOrder());
set.add("Hello");
set.add("World");
set.add("Java");
for (String s : set) {
System.out.println(s);
}
}
}
7. TreeSet 的最佳实践
- 使用合适的比较器:根据需求提供自定义比较器,实现自定义排序。
- 避免使用
null
元素:TreeSet
不允许null
元素,避免引发NullPointerException
。 - 线程安全:在多线程环境中使用同步集合或并发集合。
- 根据需求选择数据结构:
TreeSet
适用于需要排序、范围查找且不允许重复的场景。
结论
TreeSet
是Java中一个常用且强大的集合类,它提供了基于红黑树的有序集合实现,适用于各种场景。通过掌握TreeSet
的创建、基本操作、高级操作以及最佳实践,能够写出高效且易维护的代码。
(九)LinkedHashSet
LinkedHashSet
是 Java 集合框架中的一个非常有用的类,它结合了 HashSet
的高效性和 LinkedList
的有序性。
1. 基本概念
LinkedHashSet
是 HashSet
的子类,它不仅继承了 HashSet
的所有特性,还维护了一个双向链表,在插入元素时保留其插入顺序。这使得 LinkedHashSet
可以在迭代时按照插入顺序返回元素。
2. 构造方法
LinkedHashSet
提供了以下几种构造方法:
LinkedHashSet()
:创建一个初始容量为 16、加载因子为 0.75 的空LinkedHashSet
。LinkedHashSet(int initialCapacity)
:创建一个具有指定初始容量和默认加载因子的空LinkedHashSet
。LinkedHashSet(int initialCapacity, float loadFactor)
:创建一个具有指定初始容量和加载因子的空LinkedHashSet
。LinkedHashSet(Collection<? extends E> c)
:创建一个包含指定集合元素的LinkedHashSet
,这些元素将按照集合的迭代器返回的顺序排列。
3. 常见方法
LinkedHashSet
继承了 HashSet
和 AbstractSet
的方法,并没有添加新的方法,它的所有方法都来自于 HashSet
。以下是详细介绍:
添加元素
boolean add(E e)
:将指定元素添加到LinkedHashSet
中,如果元素已经存在则返回false
。boolean addAll(Collection<? extends E> c)
:将指定集合中的所有元素添加到LinkedHashSet
。
移除元素
boolean remove(Object o)
:移除LinkedHashSet
中指定的元素,如果存在则返回true
。void clear()
:移除LinkedHashSet
中的所有元素。boolean removeAll(Collection<?> c)
:移除LinkedHashSet
中那些在指定集合中也存在的元素。boolean retainAll(Collection<?> c)
:仅保留LinkedHashSet
中那些在指定集合中也存在的元素。
查找元素
boolean contains(Object o)
:如果LinkedHashSet
中包含指定元素,则返回true
。boolean containsAll(Collection<?> c)
:如果LinkedHashSet
包含指定集合中的所有元素,则返回true
。
大小和判断
int size()
:返回LinkedHashSet
中的元素数量。boolean isEmpty()
:如果LinkedHashSet
为空,则返回true
。
遍历元素
Iterator<E> iterator()
:返回LinkedHashSet
中元素的迭代器。Spliterator<E> spliterator()
:返回支持分割操作的Spliterator
。
其他
Object[] toArray()
:返回包含LinkedHashSet
中所有元素的数组。<T> T[] toArray(T[] a)
:返回包含LinkedHashSet
中所有元素的数组,数组类型与参数一致。
4. 例子
以下是一些使用 LinkedHashSet
的示例代码:
基本使用
import java.util.LinkedHashSet;
import java.util.Iterator;
public class LinkedHashSetExample {
public static void main(String[] args) {
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
// 添加元素
linkedHashSet.add(1);
linkedHashSet.add(2);
linkedHashSet.add(3);
linkedHashSet.add(1); // 重复元素不会被添加
// 检查元素是否存在
System.out.println("Contains 2? " + linkedHashSet.contains(2)); // 输出 true
// 遍历元素
System.out.println("LinkedHashSet elements:");
for (Integer element : linkedHashSet) {
System.out.println(element); // 输出顺序为 1, 2, 3
}
// 移除元素
linkedHashSet.remove(2);
System.out.println("After removing 2, LinkedHashSet elements:");
for (Integer element : linkedHashSet) {
System.out.println(element); // 输出顺序为 1, 3
}
// 清空集合
linkedHashSet.clear();
System.out.println("Is LinkedHashSet empty? " + linkedHashSet.isEmpty()); // 输出 true
}
}
使用集合初始化
import java.util.LinkedHashSet;
import java.util.Arrays;
public class LinkedHashSetFromCollectionExample {
public static void main(String[] args) {
// 使用集合初始化 LinkedHashSet
LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>(Arrays.asList("A", "B", "C", "A"));
// 遍历元素
System.out.println("LinkedHashSet elements:");
for (String element : linkedHashSet) {
System.out.println(element); // 输出顺序为 A, B, C
}
}
}
去除重复元素,并保持顺序
假设你有一个包含重复元素的列表,你想要去除重复元素并保持元素的顺序:
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ArrayList;
public class RemoveDuplicates {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("One");
list.add("Two");
list.add("Three");
list.add("Two");
list.add("One");
LinkedHashSet<String> set = new LinkedHashSet<>(list);
System.out.println(set); // 输出: [One, Two, Three]
// 如果需要返回一个 List
List<String> uniqueList = new ArrayList<>(set);
System.out.println(uniqueList); // 输出: [One, Two, Three]
}
}
5. 内部实现
LinkedHashSet
通过 LinkedHashMap
来实现。这意味着 LinkedHashSet
维护了一个链接列表,该列表将所有插入的元素按插入顺序存储,同时使用哈希表来实现快速的查找。
关键点
- 哈希表:用于快速查找元素。
- 双向链表:用于维护元素的插入顺序。
LinkedHashSet
的基本结构
- 头指针和尾指针:用于维护插入顺序。
- 哈希表:用于快速查找和插入元素。
6. 使用场景
- 保留插入顺序:当需要保留元素的插入顺序时,
LinkedHashSet
是一个理想选择。 - 避免重复元素:用于需要快速查找且不允许重复元素的场景。
- 迭代有序集合:适用于需要以插入顺序迭代集合的应用。
7. 注意事项
- 性能:
LinkedHashSet
的查找、插入、删除操作的时间复杂度为 O(1)。 - 非线程安全:
LinkedHashSet
不是线程安全的,如果需要在多线程环境中使用,可以使用Collections.synchronizedSet
。
(十)PriorityQueue
PriorityQueue
是 Java 提供的一种特殊队列,它能够保证每次访问的元素都是队列中优先级最高的元素。
1. 基本概念
PriorityQueue
是 Java 集合框架中的一个类,它实现了 Queue
接口。PriorityQueue
基于优先级堆(通常是二叉堆)实现,能够确保每次从队列中取出的元素都是队列中优先级最高的元素。
2. 特性
- 优先级排序:
PriorityQueue
中的元素会按照自然顺序(或者通过提供的比较器)进行排序。 - 线程不安全:
PriorityQueue
不是线程安全的;如果需要在多线程环境中使用,可以使用PriorityBlockingQueue
。 - 无界队列:
PriorityQueue
是一个无界队列,但它的内部容量会根据需要自动增加。
3. 构造方法
PriorityQueue
提供了多种构造方法,常用的有以下几种:
PriorityQueue()
:创建一个默认初始容量(11)的空优先级队列,元素按照自然顺序排序。PriorityQueue(int initialCapacity)
:创建一个具有指定初始容量的空优先级队列,元素按照自然顺序排序。PriorityQueue(int initialCapacity, Comparator<? super E> comparator)
:创建一个具有指定初始容量的空优先级队列,并使用指定的比较器对元素进行排序。PriorityQueue(Collection<? extends E> c)
:创建一个包含指定集合元素的优先级队列,元素按照自然顺序排序。PriorityQueue(PriorityQueue<? extends E> c)
:创建一个包含指定优先级队列元素的新优先级队列,元素按照自然顺序排序。PriorityQueue(SortedSet<? extends E> c)
:创建一个包含指定有序集合元素的优先级队列,元素按照自然顺序排序。
4. 常见方法
PriorityQueue
提供了许多方法,以下是一些常用方法的详细介绍:
添加元素
boolean add(E e)
:将指定元素插入到优先级队列中。成功返回true
,失败抛出异常。boolean offer(E e)
:将指定元素插入到优先级队列中。成功返回true
,失败返回false
。
移除元素
E poll()
:获取并移除优先级队列的头部,如果队列为空,则返回null
。E remove()
:获取并移除优先级队列的头部,如果队列为空,则抛出NoSuchElementException
。boolean remove(Object o)
:从优先级队列中移除指定元素,成功返回true
,失败返回false
。
检索元素
E peek()
:获取但不移除优先级队列的头部,如果队列为空,则返回null
。E element()
:获取但不移除优先级队列的头部,如果队列为空,则抛出NoSuchElementException
。
大小和判断
int size()
:返回优先级队列中的元素数量。boolean isEmpty()
:如果优先级队列为空,则返回true
,否则返回false
。
其他
void clear()
:移除优先级队列中的所有元素。boolean contains(Object o)
:如果优先级队列中包含指定元素,则返回true
,否则返回false
。Object[] toArray()
:返回包含优先级队列中所有元素的数组。<T> T[] toArray(T[] a)
:返回包含优先级队列中所有元素的数组,数组类型与参数一致。Iterator<E> iterator()
:返回优先级队列中元素的迭代器。
5. 例子
以下是一些使用 PriorityQueue
的例子:
基本使用
import java.util.PriorityQueue;
public class PriorityQueueExample {
public static void main(String[] args) {
// 创建一个优先级队列
PriorityQueue<Integer> pq = new PriorityQueue<>();
// 添加元素
pq.add(10);
pq.add(20);
pq.add(15);
// 检索头部元素
System.out.println("Head of the queue: " + pq.peek()); // 输出 10
// 遍历优先级队列
System.out.println("PriorityQueue elements:");
for (Integer value : pq) {
System.out.println(value);
}
// 移除并获取头部元素
System.out.println("Poll: " + pq.poll()); // 输出 10
System.out.println("Poll: " + pq.poll()); // 输出 15
System.out.println("Poll: " + pq.poll()); // 输出 20
}
}
使用比较器(Comparator)
import java.util.PriorityQueue;
import java.util.Comparator;
public class CustomPriorityQueueExample {
public static void main(String[] args) {
// 创建一个优先级队列,并使用自定义比较器
PriorityQueue<Integer> pq = new PriorityQueue<>(Comparator.reverseOrder());
pq.add(10);
pq.add(20);
pq.add(15);
System.out.println("PriorityQueue elements:");
while (!pq.isEmpty()) {
System.out.println(pq.poll()); // 输出 20, 15, 10
}
}
}
6. 内部实现
PriorityQueue
的内部实现基于数组的二叉堆。默认的初始容量是 11,当容量不足时,队列的容量会自动扩展到原来的 1.5 倍。
主要字段
- Object[] queue:存储元素的数组。
- int size:当前队列中的元素数量。
- Comparator<? super E> comparator:用于元素比较的比较器,如果为
null
,则使用元素的自然顺序。
主要操作
- 插入:将新元素插入到数组末尾,然后上浮(heapify-up)以维护堆的性质。
- 删除:将堆顶元素移除,用数组末尾元素填补堆顶位置,然后下沉(heapify-down)以维护堆的性质。
7. 使用场景
- 任务调度:
PriorityQueue
可以用于实现基于优先级的任务调度。 - 事件处理:在实时系统中,
PriorityQueue
常用于事件驱动的模拟和处理。 - 路径搜索算法:如 Dijkstra 和 A* 算法,
PriorityQueue
经常用于实现这些算法中的优先级队列。
(十一)ArrayDeque
ArrayDeque
是 Java 集合框架中的一个非常灵活且高效的双端队列(Deque)。它是双端队列的一个实现,能够在两端进行插入和删除操作。接下来,将尽可能详细地讲解 ArrayDeque
及其所有的方法。
1. 基本概念
ArrayDeque
是一个基于动态数组实现的双端队列,支持在队列两端进行高效的插入和删除操作。它是非线程安全的,如果需要在多线程环境中使用,可以使用 Collections.synchronizedDeque
。
2. 构造方法
ArrayDeque
提供了以下几种构造方法:
ArrayDeque()
:创建一个初始容量为 16 的空双端队列。ArrayDeque(int numElements)
:创建一个具有指定初始容量的空双端队列。ArrayDeque(Collection<? extends E> c)
:创建一个包含指定集合中的元素的双端队列,这些元素将按照集合的迭代器返回的顺序排列。
3. 常见方法
ArrayDeque
提供了许多方法,以下是详细介绍:
添加元素
void addFirst(E e)
:将指定元素插入到双端队列的开头。void addLast(E e)
:将指定元素插入到双端队列的末尾。boolean offerFirst(E e)
:将指定元素插入到双端队列的开头,成功返回true
,失败返回false
。boolean offerLast(E e)
:将指定元素插入到双端队列的末尾,成功返回true
,失败返回false
。boolean add(E e)
:将指定元素插入到双端队列的末尾,等同于addLast
。boolean offer(E e)
:将指定元素插入到双端队列的末尾,等同于offerLast
。void push(E e)
:将元素推入双端队列的栈顶,等同于addFirst
。
移除元素
E removeFirst()
:移除并返回双端队列的第一个元素,如果队列为空则抛出NoSuchElementException
。E removeLast()
:移除并返回双端队列的最后一个元素,如果队列为空则抛出NoSuchElementException
。E pollFirst()
:移除并返回双端队列的第一个元素,如果队列为空则返回null
。E pollLast()
:移除并返回双端队列的最后一个元素,如果队列为空则返回null
。boolean removeFirstOccurrence(Object o)
:移除双端队列中首次出现的指定元素,成功返回true
,失败返回false
。boolean removeLastOccurrence(Object o)
:移除双端队列中最后一次出现的指定元素,成功返回true
,失败返回false
。E remove()
:移除并返回双端队列的第一个元素,等同于removeFirst
。E poll()
:移除并返回双端队列的第一个元素,等同于pollFirst
。E pop()
:移除并返回双端队列的栈顶元素,等同于removeFirst
。
检索元素
E getFirst()
:返回双端队列的第一个元素,不移除它,如果队列为空则抛出NoSuchElementException
。E getLast()
:返回双端队列的最后一个元素,不移除它,如果队列为空则抛出NoSuchElementException
。E peekFirst()
:返回双端队列的第一个元素,不移除它,如果队列为空则返回null
。E peekLast()
:返回双端队列的最后一个元素,不移除它,如果队列为空则返回null
。E element()
:返回双端队列的第一个元素,等同于getFirst
。E peek()
:返回双端队列的第一个元素,等同于peekFirst
。
大小和判断
int size()
:返回双端队列中的元素数量。boolean isEmpty()
:如果双端队列为空,则返回true
,否则返回false
。
其他
void clear()
:移除双端队列中的所有元素。boolean contains(Object o)
:如果双端队列中包含指定元素,则返回true
,否则返回false
。Object[] toArray()
:返回包含双端队列中所有元素的数组。<T> T[] toArray(T[] a)
:返回包含双端队列中所有元素的数组,数组类型与参数一致。Iterator<E> iterator()
:返回双端队列中元素的迭代器。Iterator<E> descendingIterator()
:返回双端队列中元素的逆向迭代器。
4. 例子
以下是一些使用 ArrayDeque
的例子:
基本使用
import java.util.ArrayDeque;
public class ArrayDequeExample {
public static void main(String[] args) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
// 添加元素
deque.addFirst(1);
deque.addLast(2);
deque.add(3); // 等同于 addLast(3)
// 检索元素
System.out.println("First Element: " + deque.getFirst()); // 输出 1
System.out.println("Last Element: " + deque.getLast()); // 输出 3
// 遍历双端队列
System.out.println("Deque elements:");
for (Integer element : deque) {
System.out.println(element);
}
// 移除元素
System.out.println("Removed First: " + deque.removeFirst()); // 输出 1
System.out.println("Removed Last: " + deque.removeLast()); // 输出 3
// 检索但不移除元素
System.out.println("First Element: " + deque.peekFirst()); // 输出 2
}
}
作为栈使用
import java.util.ArrayDeque;
public class StackExample {
public static void main(String[] args) {
ArrayDeque<String> stack = new ArrayDeque<>();
// 压栈
stack.push("A");
stack.push("B");
stack.push("C");
// 弹栈
System.out.println("Popped: " + stack.pop()); // 输出 C
System.out.println("Peek: " + stack.peek()); // 输出 B
// 遍历栈
System.out.println("Stack elements:");
for (String element : stack) {
System.out.println(element);
}
}
}
作为队列使用
import java.util.ArrayDeque;
public class QueueExample {
public static void main(String[] args) {
ArrayDeque<String> queue = new ArrayDeque<>();
// 入队
queue.offer("X");
queue.offer("Y");
queue.offer("Z");
// 出队
System.out.println("Polled: " + queue.poll()); // 输出 X
System.out.println("Peek: " + queue.peek()); // 输出 Y
// 遍历队列
System.out.println("Queue elements:");
for (String element : queue) {
System.out.println(element);
}
}
}
5. 内部实现
ArrayDeque
是基于循环数组实现的,这使得它能够高效地在数组两端进行插入和删除操作。以下是一些关键点:
动态数组
ArrayDeque
内部维护一个动态扩展的数组,当容量不够时,它会自动扩展。
索引管理
- 头部索引(head):指向队列头部的索引。
- 尾部索引(tail):指向队列尾部的索引。
操作实现
- 插入:在头部插入时,
head
向前移动;在尾部插入时,tail
向后移动。 - 删除:在头部删除时,
head
向后移动;在尾部删除时,tail
向前移动。
6. 使用场景
- 双端队列:
ArrayDeque
可以作为双端队列使用,支持在两端进行高效的插入和删除操作。 - 栈:
ArrayDeque
可以作为栈使用,支持后进先出(LIFO)操作。 - 队列:
ArrayDeque
可以作为队列使用,支持先进先出(FIFO)操作。