上期回顾:https://blog.csdn.net/AWSDN/article/details/140822782
三、Queue
Queue,也就是队列,通常遵循先进先出(FIFO)的原则,新元素插入到队列的尾部,访问元素返回队列的头部。
(1)ArrayDeque
从名字上可以看得出,ArrayDeque 是一个基于数组实现的双端队列,为了满足可以同时在数组两端插入或删除元素的需求,数组必须是循环的,也就是说数组的任何一点都可以被看作是起点或者终点。
这是一个包含了 4 个元素的双端队列,和一个包含了 5 个元素的双端队列。
head 指向队首的第一个有效的元素,tail 指向队尾第一个可以插入元素的空位,因为是循环数组,所以 head 不一定从是从 0 开始,tail 也不一定总是比 head 大。
来一段 ArrayDeque 的增删改查吧。
// 创建一个ArrayDeque
ArrayDeque<String> deque = new ArrayDeque<>();
// 添加元素
deque. Add("aa");
deque. Add("bb");
deque. Add("ccc");
// 删除元素
deque. Remove("bb");
// 修改元素
deque. Remove("aa");
deque. Add("aaaaa");
// 查找元素
Boolean hasChenQingYang = deque. Contains("ccc");
System.out.println("deque包含ccc吗?" + hasccc);
(2)LinkedList
LinkedList 一般应该归在 List 下,只不过,它也实现了 Deque 接口,可以作为队列来使用。等于说,LinkedList 同时实现了 Stack、Queue、PriorityQueue 的所有功能。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{}
换句话说,LinkedList 和 ArrayDeque 都是 Java 集合框架中的双向队列(deque),它们都支持在队列的两端进行元素的插入和删除操作。不过,LinkedList 和 ArrayDeque 在实现上有一些不同:
- 底层实现方式不同:LinkedList 是基于链表实现的,而 ArrayDeque 是基于数组实现的。
- 随机访问的效率不同:由于底层实现方式的不同,LinkedList 对于随机访问的效率较低,时间复杂度为 O(n),而 ArrayDeque 可以通过下标随机访问元素,时间复杂度为 O(1)。
- 迭代器的效率不同:LinkedList 对于迭代器的效率比较低,因为需要通过链表进行遍历,时间复杂度为 O(n),而 ArrayDeque 的迭代器效率比较高,因为可以直接访问数组中的元素,时间复杂度为 O(1)。
- 内存占用不同:由于 LinkedList 是基于链表实现的,它在存储元素时需要额外的空间来存储链表节点,因此内存占用相对较高,而 ArrayDeque 是基于数组实现的,内存占用相对较低。
因此,在选择使用 LinkedList 还是 ArrayDeque 时,需要根据具体的业务场景和需求来选择。如果需要在双向队列的两端进行频繁的插入和删除操作,并且需要随机访问元素,可以考虑使用 ArrayDeque;如果需要在队列中间进行频繁的插入和删除操作,可以考虑使用 LinkedList。
来一段 LinkedList 作为队列时候的增删改查吧,注意和它作为 List 的时候有很大的不同。
// 创建一个 LinkedList 对象
LinkedList<String> queue = new LinkedList<>();
// 添加元素
queue. Offer("aa");
queue. Offer("bb");
queue. Offer("ccc");
System.out.println(queue); // 输出 [aa, bb, ccc]
// 删除元素
queue. Poll();
System.out.println(queue); // 输出 [bb, ccc]
// 修改元素:LinkedList 中的元素不支持直接修改,需要先删除再添加
String first = queue.poll();
queue. Offer("bbb");
System.out.println(queue); // 输出 [cc, bbb]
// 查找元素:LinkedList 中的元素可以使用 get() 方法进行查找
System.out.println(queue. Get(0)); // 输出 ccc
System.out.println(queue. Contains("aa")); // 输出 false
// 查找元素:使用迭代器的方式查找ccc
// 使用迭代器依次遍历元素并查找
Iterator<String> iterator = queue.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element. Equals("ccc")) {
System.out.println("找到了:" + element);
break;
}
}
在使用 LinkedList 作为队列时,可以使用 offer() 方法将元素添加到队列的末尾,使用 poll() 方法从队列的头部删除元素。另外,由于 LinkedList 是链表结构,不支持随机访问元素,因此不能使用下标访问元素,需要使用迭代器或者 poll() 方法依次遍历元素。
(3)PriorityQueue
PriorityQueue 是一种优先级队列,它的出队顺序与元素的优先级有关,执行 remove 或者 poll 方法,返回的总是优先级最高的元素。
// 创建一个 PriorityQueue 对象
PriorityQueue<String> queue = new PriorityQueue<>();
// 添加元素
queue. Offer("aa");
queue. Offer("bb");
queue. Offer("ccc");
System.out.println(queue); // 输出 [aa, bb, ccc]
// 删除元素
queue. Poll();
System.out.println(queue); // 输出 [bb, ccc]
// 修改元素:PriorityQueue 不支持直接修改元素,需要先删除再添加
String first = queue.poll();
queue. Offer("ee");
System.out.println(queue); // 输出 [ee, ccc]
// 查找元素:PriorityQueue 不支持随机访问元素,只能访问队首元素
System.out.println(queue. Peek()); // 输出 ee
System.out.println(queue. Contains("ccc")); // 输出 true
// 通过 for 循环的方式查找陈清扬
for (String element : queue) {
if (element. Equals("ccc")) {
System.out.println("找到了:" + element);
break;
}
}
要想有优先级,元素就需要实现 Comparable 接口或者 Comparator 接口(我们后面会讲)。
这里先来一段通过实现 Comparator 接口按照年龄姓名排序的优先级队列吧。
import java.util.Comparator;
import java.util.PriorityQueue;
class Student {
private String name;
private int chineseScore;
private int mathScore;
public Student(String name, int chineseScore, int mathScore) {
this.name = name;
this.chineseScore = chineseScore;
this.mathScore = mathScore;
}
public String getName() {
return name;
}
public int getChineseScore() {
return chineseScore;
}
public int getMathScore() {
return mathScore;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", 总成绩=" + (chineseScore + mathScore) +
'}';
}
}
class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
// 比较总成绩
return Integer.compare(s2.getChineseScore() + s2.getMathScore(),
s1.getChineseScore() + s1.getMathScore());
}
}
public class PriorityQueueComparatorExample {
public static void main(String[] args) {
// 创建一个按照总成绩排序的优先级队列
PriorityQueue<Student> queue = new PriorityQueue<>(new StudentComparator());
// 添加元素
queue.offer(new Student("王二", 80, 90));
System.out.println(queue);
queue.offer(new Student("陈清扬", 95, 95));
System.out.println(queue);
queue.offer(new Student("小驼铃", 90, 95));
System.out.println(queue);
queue.offer(new Student("沉默", 90, 80));
while (!queue.isEmpty()) {
System.out.print(queue.poll() + " ");
}
}
}
Student 是一个学生对象,包含姓名、语文成绩和数学成绩。
StudentComparator 实现了 Comparator 接口,对总成绩做了一个排序。
PriorityQueue 是一个优先级队列,参数为 StudentComparator,然后我们添加了 4 个学生对象进去。
来看一下输出结果:
[Student{name='王二', 总成绩=170}]
[Student{name='陈清扬', 总成绩=190}, Student{name='王二', 总成绩=170}]
[Student{name='陈清扬', 总成绩=190}, Student{name='王二', 总成绩=170}, Student{name='小驼铃', 总成绩=185}]
Student{name='陈清扬', 总成绩=190} Student{name='小驼铃', 总成绩=185} Student{name='沉默', 总成绩=170} Student{name='王二', 总成绩=170}
我们使用 offer 方法添加元素,最后用 while 循环遍历元素(通过 poll 方法取出元素),从结果可以看得出,PriorityQueue按照学生的总成绩由高到低进行了排序。
四、Map
Map 保存的是键值对,键要求保持唯一性,值可以重复。
(1)HashMap
HashMap 实现了 Map 接口,可以根据键快速地查找对应的值——通过哈希函数将键映射到哈希表中的一个索引位置,从而实现快速访问.
这里先大致了解一下 HashMap 的特点:
- HashMap 中的键和值都可以为 null。如果键为 null,则将该键映射到哈希表的第一个位置。
- 可以使用迭代器或者 forEach 方法遍历 HashMap 中的键值对。
- HashMap 有一个初始容量和一个负载因子。初始容量是指哈希表的初始大小,负载因子是指哈希表在扩容之前可以存储的键值对数量与哈希表大小的比率。默认的初始容量是 16,负载因子是 0.75。
来个简单的增删改查吧。
// 创建一个 HashMap 对象
HashMap<String, String> hashMap = new HashMap<>();
// 添加键值对
hashMap.put("aa", "aa");
hashMap.put("bb", "bb");
hashMap.put("ccc", "ccc");
// 获取指定键的值
String value1 = hashMap.get("aa");
System.out.println("aa对应的值为:" + value1);
// 修改键对应的值
hashMap.put("aa", "aa");
String value2 = hashMap.get("aa");
System.out.println("修改后aa对应的值为:" + value2);
// 删除指定键的键值对
hashMap.remove("bb");
// 遍历 HashMap
for (String key : hashMap.keySet()) {
String value = hashMap.get(key);
System.out.println(key + " 对应的值为:" + value);
}
(2)LinkedHashMap
HashMap 已经非常强大了,但它是无序的。如果我们需要一个有序的 Map,就要用到LinkedHashMap。LinkedHashMap 是 HashMap 的子类,它使用链表来记录插入/访问元素的顺序。
LinkedHashMap 可以看作是 HashMap + LinkedList 的合体,它使用了哈希表来存储数据,又用了双向链表来维持顺序。
来一个简单的例子。
// 创建一个 LinkedHashMap,插入的键值对为 aa bb ccc
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("aa", "aa");
linkedHashMap.put("bb", "bb");
linkedHashMap.put("ccc", "ccc");
// 遍历 LinkedHashMap
for (String key : linkedHashMap.keySet()) {
String value = linkedHashMap.get(key);
System.out.println(key + " 对应的值为:" + value);
}
来看输出结果:
aa 对应的值为:aa
bb 对应的值为:bb
ccc 对应的值为:ccc
从结果中可以看得出来,LinkedHashMap 维持了键值对的插入顺序
为了和 LinkedHashMap 做对比,我们用同样的数据试验一下 HashMap。
// 创建一个HashMap,插入的键值对为 aa bb ccc
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("aa", "aa");
hashMap.put("bb", "bb");
hashMap.put("ccc", "ccc");
// 遍历 HashMap
for (String key : hashMap.keySet()) {
String value = hashMap.get(key);
System.out.println(key + " 对应的值为:" + value);
}
输出结果:
aa 对应的值为:aa
ccc 对应的值为:ccc
bb 对应的值为:bb
HashMap 没有维持键值对的插入顺序
(3)TreeMap
TreeMap实现了 SortedMap 接口,可以自动将键按照自然顺序或指定的比较器顺序排序,并保证其元素的顺序。内部使用红黑树来实现键的排序和查找。
同样来一个增删改查的 demo:
// 创建一个 TreeMap 对象
Map<String, String> treeMap = new TreeMap<>();
// 向 TreeMap 中添加键值对
treeMap.put("aa", "aa");
treeMap.put("bb", "bb");
treeMap.put("ccc", "ccc");
// 查找键值对
String name = "aa";
if (treeMap.containsKey(name)) {
System.out.println("找到了 " + name + ": " + treeMap.get(name));
} else {
System.out.println("没有找到 " + name);
}
// 修改键值对
name = "bb";
if (treeMap.containsKey(name)) {
System.out.println("修改前的 " + name + ": " + treeMap.get(name));
treeMap.put(name, "newWanger");
System.out.println("修改后的 " + name + ": " + treeMap.get(name));
} else {
System.out.println("没有找到 " + name);
}
// 删除键值对
name = "ccc";
if (treeMap.containsKey(name)) {
System.out.println("删除前的 " + name + ": " + treeMap.get(name));
treeMap.remove(name);
System.out.println("删除后的 " + name + ": " + treeMap.get(name));
} else {
System.out.println("没有找到 " + name);
}
// 遍历 TreeMap
for (Map.Entry<String, String> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
与 HashMap 不同的是,TreeMap 会按照键的顺序来进行排序。
// 创建一个 TreeMap 对象
Map<String, String> treeMap = new TreeMap<>();
// 向 TreeMap 中添加键值对
treeMap.put("c", "cat");
treeMap.put("a", "apple");
treeMap.put("b", "banana");
// 遍历 TreeMap
for (Map.Entry<String, String> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
来看输出结果:
a: apple
b: banana
c: cat
默认情况下,已经按照键的自然顺序排过了。