Java集合基础知识点系统性总结篇

目录

  • 集合
    • 一、图解集合的继承体系?([图片来源](https://www.cnblogs.com/mrhgw/p/9728065.html))
    • 点击查看大图
    • 二、List,Set,Map三者的区别?
    • 三、List接口的实现
      • 3.1、Arraylist 、 LinkedList、Vector
      • 3.2、Arraylist 、 LinkedList、Vector 三者的区别?
      • 3.3、对RandomAccess接口的理解
    • 四、Set接口的实现
      • 4.1、HashSet、LinkedHashSet、TreeSet
      • 4.2、HashSet、LinkedHashSet 、 TreeSet 三者的区别?
      • 4.3、HashSet如何检查重复
    • 五、Map接口的实现
      • 5.1、HashMap、LinkedHashMap、TreeMap、Hashtable
      • 5.2、HashMap、LinkedHashMap、TreeMap、Hashtable对比
      • 5.3、Map的五种遍历方式
    • 六、用Comparator和Comparable进行自定义排序
    • 七、Queue、Deque、BlockingQueue接口的实现
      • 7.1、Queue 、 Deque、BlockingQueue 接口的区别
      • 7.2、LinkedList 、ArrayDeque、PriorityQueue
      • 7.3、LinkedList 、ArrayDeque、PriorityQueue对比
      • 7.4、BlockingQueue的实现
      • 7.5、代码示例
        • ArrayDeque、LinkedList、PriorityQueue代码示例:
        • BlockingQueue的一些实现类代码示例
          • ArrayBlockingQueue 和 LinkedBlockingQueue代码示例:
          • PriorityBlockingQueue代码示例:
          • DelayQueue代码示例:
          • SynchronousQueue代码示例
          • LinkedTransferQueue代码示例
        • 7.6、利用双端队列实现栈
    • 八、常见的线程安全集合
    • 九、集合的适用场景总结

集合

集合是Java非常重要的基础知识点。

一、图解集合的继承体系?(图片来源)

在这里插入图片描述

点击查看大图

二、List,Set,Map三者的区别?

List、Set 是用于存放单值集合的接口 ,Map 是用于存放双值(key,value)集合的接口。

  • List(存取有顺,可重复): List接口存储一组不唯一且有序的对象(存储的元素允许重复,可以有多个元素引用相同的对象)。可以通过类似于数组的下标快速查找到对 应的值。
  • Set(不可重复): 不允许重复的集合。不会有多个元素引用相同的对象。(注意Set只是接口,其实现类HashSet存取无序,LinkedHashSet存取有序,所以不能说Set集合存取无序)
  • Map(双值集合,可以用Key来进行高效搜索): 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

三、List接口的实现

3.1、Arraylist 、 LinkedList、Vector

  • ①、Arraylist : 底层数据结构是Object 数组,可以存null(不建议存null值,可能出现NPE),线程不安全。
  • ②、LinkedList: 底层数据结构是双向链表 ,可以存null(不建议存null值,可能出现NPE),线程不安全。
  • ③、Vector :: 底层数据结构是Object 数组,可以存null(不建议存null值,可能出现NPE),线程安全,但是不建议使用,因为对外提供的全部是同步方法(被synchronized修饰),可能出现性能问题。

3.2、Arraylist 、 LinkedList、Vector 三者的区别?

特性ArrayListLinkedListVector
底层数据结构Object数组双向链表(JDK1.7之前为循环链表,JDK1.7之后取消循环)Object数组
插入和删除元素是否受位置影响受影响。尾部插入/删除时间复杂度O(1),指定位置O(n-i)不受影响。尾部插入/删除时间复杂度O(1),指定位置O(n)受影响。尾部插入/删除时间复杂度O(1),指定位置O(n-i)
内存空间占用结尾预留一定容量空间每个元素需要额外空间存放前驱和后继指针结尾预留一定容量空间
是否支持快速随机访问支持(实现了RandomAccess接口)不支持(需要从头遍历或从尾部遍历)支持(实现了RandomAccess接口)
线程安全性线程不安全线程不安全线程安全(public方法使用了synchronized修饰)
性能访问和遍历性能高,随机插入和删除性能受位置影响随机插入和删除性能高,访问和遍历性能低访问和遍历性能高,但由于同步开销性能低于ArrayList
适用场景频繁访问、遍历操作较多,插入、删除操作较少的场景频繁随机插入、删除操作较多,访问、遍历操作较少的场景多线程操作需要保证线程安全的场景
替代方案如果需要线程安全,可使用CopyOnWriteArrayList如果需要线程安全,可使用ConcurrentLinkedDequeCopyOnWriteArrayList(更好的线程安全集合)

3.3、对RandomAccess接口的理解

     Java 中的 RandomAccess 是一个标记接口(marker interface),它没有任何方法,只是用来标识某个类具备快速随机访问功能。这个接口主要是用在集合框架中的,特别是 List 接口的实现类上。

为什么需要 RandomAccess 接口?
在 Java 的集合框架中,有些集合是可以高效地进行随机访问的,而有些集合只能高效地进行顺序访问。比如:

数组(Array)和 ArrayList:可以通过索引在常数时间内(O(1))访问任意元素,适合随机访问。
链表(LinkedList):需要从头或尾开始遍历,查找某个特定位置的元素需要线性时间(O(n)),不适合随机访问。

JDK中是如何使用RandomAccess 接口的?
     JDK 中使用 RandomAccess 接口的主要目的是通过识别集合是否具备快速随机访问能力来优化某些算法的执行。在 JDK 内部,很多方法都会检查传入的 List 是否实现了 RandomAccess 接口,然后根据情况选择最优的访问策略。
例如:
     Collections.binarySearch 方法会对有序列表进行二分查找。如果列表实现了 RandomAccess 接口,则直接使用基于索引的二分查找,否则使用基于迭代器的方式。

四、Set接口的实现

4.1、HashSet、LinkedHashSet、TreeSet

  • ①、HashSet: 保存的元素无序且唯一。底层是基于 HashMap 实现的,利用 HashMap的key来保存元素。

  • ②、LinkedHashSet: 保存的元素有序且唯一。底层是基于 LinkedHashMap 实现的,是HashSet的子类。

  • ③、TreeSet: 保存的元素有序且唯一。底层是基于 红黑树(自平衡的排序二叉树)。

注意点:
        HashSet、LinkedHashSet 和 TreeSet 都是 Set 接口的实现类,都能保证元素唯一,且是线程不安全的。

4.2、HashSet、LinkedHashSet 、 TreeSet 三者的区别?

特性HashSetLinkedHashSetTreeSet
存储顺序无序存储按插入顺序存储按自然顺序或比较器顺序存储
底层实现哈希表(HashMap)哈希表(HashMap)+ 双向链表红黑树(TreeMap)
插入/删除/查找复杂度O(1)O(1)(维护链表使性能略低于 HashSet)O(log n)
唯一性保证方式hashCode() 和 equals()hashCode() 和 equals()compareTo() 或 Comparator
适用场景高效查找和修改,对顺序无要求保持插入顺序,高效查找和修改需要排序的场景

4.3、HashSet如何检查重复

把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。

hashCode()与equals()的相关规定:
如果两个对象相等,则hashcode一定也是相同的
两个对象相等,equals方法返回true
两个对象有相同的hashcode值,它们也不一定是相等的
综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

五、Map接口的实现

5.1、HashMap、LinkedHashMap、TreeMap、Hashtable

  • ①、HashMap: 平时使用最多的Map,存储键值对(key,value),不保证插入顺序,同时需要注意 value可能为null。
  • ②、LinkedHashMap: 如果需要保证插入的顺序,大部分情况下使用LinkedHashMap,同时需要注意 value可能为null。
  • ③、TreeMap: 一般在需要特定的排序规则时使用TreeMap,注意TreeMap不允许 null 键或 null 值。
  • ④、Hashtable: 基本上不再使用,如果需要保证线程安全 一般使用ConcurrentHashMap。

5.2、HashMap、LinkedHashMap、TreeMap、Hashtable对比

特性HashMapLinkedHashMapTreeMapHashtable
存储顺序无序按插入顺序存储按键的自然顺序或比较器顺序存储无序
底层实现哈希表(数组 + 链表/红黑树JDK.18)哈希表(数组 + 链表/红黑树JDK1.8) + 双向链表红黑树(平衡二叉搜索树)哈希表(数组 + 链表)
线程安全性非线程安全非线程安全非线程安全线程安全
是否允许 null 键值允许一个 null 键和多个 null 值允许一个 null 键和多个 null 值不允许 null 键或 null 值不允许 null 键或 null 值
性能插入、删除、查找操作时间复杂度为 O(1)插入、删除、查找操作时间复杂度为 O(1)插入、删除、查找操作时间复杂度为 O(log n)插入、删除、查找操作时间复杂度为 O(1)
适用场景高效查找和修改,对顺序无要求保持插入顺序,高效查找和修改需要按键排序的场景需要线程安全,且不关心顺序的场景 (不建议使用,线程安全的Map建议使用ConcurrentHashMap)

5.3、Map的五种遍历方式

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;public class TestA {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("秀逗","博美");map.put("四眼","哈士奇");map.put("大黄","田园犬");// ========== map遍历方式一: for循环 EntrySetfor (Map.Entry<String, String> entry : map.entrySet()) {String key = entry.getKey();String value = entry.getValue();System.out.println("key: " + key + " value: " + value);}// ========== map遍历方式二: for循环 KeySetfor (String key : map.keySet()) {System.out.println("key: " + key + " value: " + map.get(key));}// ========== map遍历方式三:  EntrySet 迭代器Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, String> entry = iterator.next();System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());}// ========== map遍历方式四:  KeySet 迭代器Iterator<String> keyIterator = map.keySet().iterator();while (keyIterator.hasNext()) {String key = keyIterator.next();System.out.println("key: " + key + " value: " + map.get(key));}// ========== map遍历方式五:  Lambda 表达式 forEach   (推荐使用,简单好记)map.forEach((key,value)->{System.out.println("key: " + key + " value: " + value);});}
}

推荐使用

map.forEach((key,value)->{// do something
});

六、用Comparator和Comparable进行自定义排序

先看下二者的区别:

特性ComparableComparator
所在包java.langjava.util
比较方法compareTo(T o)compare(T o1, T o2)
是否需要修改类需要修改比较的类本身不修改比较的类,通常在外部定义
适用场景类需要有固定的排序方式时类不修改自身排序逻辑的情况时,使用比较灵活

代码示例:

import java.util.ArrayList;
import java.util.Collections;public class TestA {public static void main(String[] args) {ArrayList<Dog> dogs = new ArrayList<>();dogs.add(new Dog("四眼", 4, "吃馒头喝稀饭"));dogs.add(new Dog("秀逗", 8, "吃骨头"));dogs.add(new Dog("大黄", 5, "吃烤鸭屁股"));dogs.add(new Dog("跳跳", 8, "跳"));// 使用 ComparableCollections.sort(dogs);System.out.println(dogs);ArrayList<Dog> dogss = new ArrayList<>();dogss.add(new Dog("哈士奇", 7, "啃沙发"));dogss.add(new Dog("拉布拉多", 8, "拉得多"));dogss.add(new Dog("博美", 5, "吃帝王蟹"));dogss.add(new Dog("泰迪", 5, "蹦迪"));// 使用 Comparatordogss.sort((o1, o2) -> {Integer age1 = o1.getAge();Integer age2 = o2.getAge();if (age1 > age2) {return 1;} else if (age1 < age2) {return -1;}int hobbyL1 = o1.getHobby().length();int hobbyL2 = o2.getHobby().length();if (hobbyL1 > hobbyL2) {return 1;} else if (hobbyL1 < hobbyL2) {return -1;}return 0;});System.out.println(dogss);}
}class Dog implements Comparable<Dog> {private String name;private Integer age;private String hobby;public Dog(String name, Integer age, String hobby) {this.name = name;this.age = age;this.hobby = hobby;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getHobby() {return hobby;}public void setHobby(String hobby) {this.hobby = hobby;}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +", age=" + age +", hobby='" + hobby + '\'' +'}';}@Overridepublic int compareTo(Dog o) {// 先按照年龄正序排序if (this.age > o.getAge()) {return 1;} else if (this.age < o.getAge()) {return -1;}// 再按照hobby属性值的长度正序排序if (this.hobby.length() > o.getHobby().length()) {return 1;} else if (this.hobby.length() < o.getHobby().length()) {return -1;}// 如何前面两种情况都相等 则直接返回相等return 0;}
}

七、Queue、Deque、BlockingQueue接口的实现

7.1、Queue 、 Deque、BlockingQueue 接口的区别

特性QueueDequeBlockingQueue
定义先进先出(FIFO)的队列双端队列,可以先进先出(FIFO)或后进先出(LIFO)支持阻塞操作的队列,用于生产者-消费者模型
插入元素只能从队尾插入可以从队头和队尾插入只能从队尾插入
移除元素只能从队头移除可以从队头和队尾移除只能从队头移除
阻塞操作不支持不支持支持插入和移除时阻塞操作
常用方法add(e), offer(e), remove(), poll(), element(), peek()addFirst(e), addLast(e), offerFirst(e), offerLast(e), removeFirst(), removeLast(), pollFirst(), pollLast(), getFirst(), getLast(), peekFirst(), peekLast()put(e), offer(e, timeout, unit), take(), poll(timeout, unit)
常用实现(非线程安全)LinkedList, PriorityQueue, ArrayDequeLinkedList, ArrayDeque
常用实现(线程安全)ConcurrentLinkedQueue, LinkedBlockingQueue, ArrayBlockingQueue, PriorityBlockingQueue, DelayQueue, SynchronousQueue, LinkedTransferQueueConcurrentLinkedDeque, LinkedBlockingDequeLinkedBlockingQueue, ArrayBlockingQueue, PriorityBlockingQueue, DelayQueue, SynchronousQueue, LinkedTransferQueue
适用场景通常用于简单的FIFO队列实现需要双端插入和删除操作时使用适用于需要阻塞操作的生产者-消费者模型

7.2、LinkedList 、ArrayDeque、PriorityQueue

  • ①、LinkedList : LinkedList 是基于链表的数据结构,实现了 ListDeque 接口,可以用作列表、队列(Queue)和双端队列(Deque)。
  • ②、ArrayDeque: ArrayDeque是一个基于数组和双指针的双端队列,提供了可变大小的数组实现(存在动态扩容),支持 Deque 接口的所有操作。还可以用于实现栈数据结构。注意:ArrayDeque不允许存null值。
  • ③、PriorityQueue : PriorityQueue 是一个基于优先级堆(默认小顶堆)数据结构实现的无界优先级队列,元素按自然顺序或指定的比较器排序。注意:PriorityQueue 不允许存null值。 插入和删除元素的时间复杂度为 O(log n)。比较适合需要维护按优先级顺序处理元素的场景,或者需要动态获取最小(或最大)元素的场景。

7.3、LinkedList 、ArrayDeque、PriorityQueue对比

特性LinkedListArrayDequePriorityQueue
底层数据结构双向链表动态数组 和 双指针优先级堆(最小堆)
实现接口List, DequeDequeQueue
插入效率对中间元素插入效率高队头和队尾插入效率高插入元素时间复杂度为 O(log n)
删除效率对中间元素删除效率高队头和队尾删除效率高删除最小元素时间复杂度为 O(log n)
随机访问效率效率低,需要从头或尾遍历效率一般,不如 ArrayList不支持高效的随机访问
是否支持双端队列
元素排序按插入顺序按插入顺序按自然顺序或指定的比较器排序
常用方法add(int index, E element), remove(int index), get(int index), addFirst(E e), addLast(E e), removeFirst(), removeLast()addFirst(E e), addLast(E e), removeFirst(), removeLast(), peekFirst(), peekLast()add(E e), offer(E e), remove(), poll(), peek()
线程安全性非线程安全,需要手动同步非线程安全,需要手动同步非线程安全,需要手动同步
适用场景需要频繁插入和删除操作的场景,或需要使用双端队列功能的场景需要高效队头和队尾插入删除操作的场景,或需要使用双端队列功能的场景需要维护按优先级顺序处理元素的场景,或需要动态获取最小(或最大)元素的场景

7.4、BlockingQueue的实现

  • ①、ArrayBlockingQueue: 使用线程池时相信大家都用过这个容器,ArrayBlockingQueue是一个基于数组的有界阻塞队列。队列的容量是在创建时指定的。支持公平和非公平锁的访问机制(默认非公平)。
  • ②、LinkedBlockingQueue: LinkedBlockingQueue 是一个基于链表的可选有界阻塞队列。如果没有指定容量,则默认大小为Integer.MAX_VALUE,使用时建议指定大小。仅支持非公平锁访问机制。
  • ③、PriorityBlockingQueue: 是一个支持优先级排序的无界阻塞队列。元素按照自然顺序或提供的比较器排序。注意:不能插入 null值,且构造时必须传入比较器Comparator或者保存的元素实现Comparable接口。
  • ④、DelayQueue: DelayQueue是一个支持延时获取元素的无界阻塞队列。队列中的元素必须实现 Delayed 接口,只有在延迟期满时才能从队列中获取到元素。
  • ⑤、SynchronousQueue: 这个队列是一个特殊的阻塞队列,它不存储元素。每个插入操作必须等待另一个线程进行相应的删除操作,反之亦然。所以这个队列通常被用来实现线程之前的同步。
  • ⑥、LinkedTransferQueue: 是一个基于链表的无界阻塞队列,提供 TransferQueue 接口的实现,在BlockingQueue接口的基础上又封装了一层。与其他阻塞队列相比,LinkedTransferQueue 提供了更多的控制选项,尤其是 transfer 方法,可以实现多个线程之间的直接数据交换。适用于多生产者多消费者场景。

7.5、代码示例

由于Queue、Deque、BlockingQueue接口的实现在实际项目的业务操作中使用的不多,所以记录一下简单的代码使用示例,方便后续查阅。

ArrayDeque、LinkedList、PriorityQueue代码示例:
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.PriorityQueue;public class TestA {public static void main(String[] args) {// ============= LinkedList使用示例 ===================LinkedList<Dog> dogs = new LinkedList<>();// 在队尾插入元素dogs.addLast(new Dog("秀逗", 8));dogs.addLast(new Dog("四眼", 6));// 在队头插入元素dogs.addFirst(new Dog("大黄", 4));System.out.println("LinkedList: " + dogs);// 移除队头元素Dog firstDog = dogs.removeFirst();System.out.println("移除队头元素: " + firstDog);// 移除队尾元素Dog lastDog = dogs.removeLast();System.out.println("移除队尾元素: " + lastDog);// 打印队列System.out.println("LinkedList 移除元素后: " + dogs);// ============= ArrayDeque 使用示例 参考LinkedList ===================ArrayDeque<Dog> dogsArrayDeque = new ArrayDeque<Dog>();dogsArrayDeque.addFirst(new Dog("秀逗", 8));dogsArrayDeque.addFirst(new Dog("四眼", 6));dogsArrayDeque.addLast(new Dog("二哈", 3));System.out.println(dogsArrayDeque);// 获取队列头部第一个元素Dog dogFirst = dogsArrayDeque.peekFirst();System.out.println("队列头部第一个元素: " + dogFirst);// 获取队列尾部第一个元素Dog dogLast = dogsArrayDeque.peekLast();System.out.println("获取队列尾部第一个元素: " + dogLast);// 移除队列头部第一个元素 (如果队列为空则返回 null元素)dogsArrayDeque.pollFirst();// 移除队列头部第一个元素 (如果队列为空则 抛出异常 NoSuchElementException)dogsArrayDeque.pop();// ============= PriorityQueue  使用示例  ===================PriorityQueue<Dog> dogsPriorityQueue = new PriorityQueue<>((dog1, dog2) -> {// 按照狗的年龄从小到大排序Integer age1 = dog1.getAge();Integer age2 = dog2.getAge();return age1.compareTo(age2);});// 插入元素  add 方法插入成功 返回true  插入失败 抛异常 IllegalStateException (尤其是有界队列满之后)dogsPriorityQueue.add(new Dog("大哈", 3));dogsPriorityQueue.add(new Dog("二哈", 2));dogsPriorityQueue.add(new Dog("细狗", 5));// 插入元素 offer 方法插入成功 返回true  插入失败返回 false  不会抛异常// 在PriorityQueue无界队列中 实际上 add方法 就是调用的 offer方法dogsPriorityQueue.offer(new Dog("柯基", 4));// 打印队列  注意点: PriorityQueue 的 toString 方法是从 AbstractCollection 类继承的,并没有重写。// 这意味着它直接返回内部数组的内容,而不展示实际的优先级顺序。 实际上展示的是 插入顺序。System.out.println("PriorityQueue: " + dogsPriorityQueue);// 依次移除并打印队头元素while (!dogsPriorityQueue.isEmpty()) {// 每次都是 年龄最小的被移除Dog dog = dogsPriorityQueue.poll();System.out.println("Removed: " + dog);}}
}class Dog {private String name;private Integer age;public Dog(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +", age=" + age +'}';}
}
BlockingQueue的一些实现类代码示例
ArrayBlockingQueue 和 LinkedBlockingQueue代码示例:
public static void main(String[] args) throws InterruptedException {// ============== ArrayBlockingQueue 和 LinkedBlockingQueue的使用示例 (大致相同)// 创建一个容量为3的ArrayBlockingQueueBlockingQueue<Dog> arrayQueue = new ArrayBlockingQueue<>(3);// ============== 示例1:ArrayBlockingQueue ======================System.out.println("ArrayBlockingQueue 示例:");arrayQueue.put(new Dog("秀逗", 8));arrayQueue.put(new Dog("二哈", 2));arrayQueue.put(new Dog("四眼", 5));// 试图插入第四个元素(将阻塞线程)new Thread(() -> {try {System.out.println("尝试插入第四个元素到已经满的ArrayBlockingQueue...");arrayQueue.put(new Dog("大黄", 4)); // 阻塞直到队列有空闲空间System.out.println("大黄 插入成功");} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();// 移除一个元素TimeUnit.SECONDS.sleep(2); // 等待2秒以模拟处理时间System.out.println("从ArrayBlockingQueue中移除第一个元素: " + arrayQueue.take());// 执行结果: // ArrayBlockingQueue 示例://尝试插入第四个元素到已经满的ArrayBlockingQueue...//从ArrayBlockingQueue中移除第一个元素: Dog{name='秀逗', age=8}//大黄 插入成功}
PriorityBlockingQueue代码示例:
public static void main(String[] args) throws InterruptedException {// 创建一个PriorityBlockingQueue,使用自定义的比较器按年龄排序BlockingQueue<Dog> priorityQueue = new PriorityBlockingQueue<>(10, (dog1, dog2) -> {Integer age1 = dog1.getAge();Integer age2 = dog2.getAge();return age1.compareTo(age2);});// 插入元素priorityQueue.put(new Dog("秀逗", 8));priorityQueue.put(new Dog("二哈", 2));priorityQueue.put(new Dog("四眼", 5));priorityQueue.put(new Dog("大黄", 4));// 打印队列内容  注意:PriorityBlockingQueue 重写了 toString 方法,展示了内部元素的优先级顺序(比较器比较后的顺序)。System.out.println("PriorityBlockingQueue: " + priorityQueue);// 依次移除并打印队头元素(按年龄排序)while (!priorityQueue.isEmpty()) {Dog dog = priorityQueue.take();System.out.println("移除: " + dog);}// 试图从空队列中取元素(将阻塞线程)System.out.println("从空的PriorityBlockingQueue中取元素...");new Thread(() -> {try {Dog dog = priorityQueue.take(); // 阻塞直到队列有新元素System.out.println("从PriorityBlockingQueue中取到了元素: " + dog);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();// 插入新元素以解除阻塞TimeUnit.SECONDS.sleep(2); // 等待2秒以模拟处理时间priorityQueue.put(new Dog("巴豆", 1));System.out.println("巴豆加入了PriorityBlockingQueue");// 运行结果 // PriorityBlockingQueue: [Dog{name='二哈', age=2}, Dog{name='大黄', age=4}, Dog{name='四眼', age=5}, Dog{name='秀逗', age=8}]//移除: Dog{name='二哈', age=2}//移除: Dog{name='大黄', age=4}//移除: Dog{name='四眼', age=5}//移除: Dog{name='秀逗', age=8}//从空的PriorityBlockingQueue中取元素...//巴豆加入了PriorityBlockingQueue//从PriorityBlockingQueue中取到了元素: Dog{name='巴豆', age=1}}

注意点:
        PriorityBlockingQueue 和 PriorityQueue 均在取出元素时按优先级顺序排列(比较器规则排序),但 toString 方法展示的内容有所不同。 PriorityBlockingQueue 的 toString 展示了排序后的内容,而 PriorityQueue 的 toString 则展示了插入顺序的内容。
PriorityQueue 的 toString 方法是从 AbstractCollection 类继承的,并没有重写。这意味着它直接返回内部数组的内容,而不展示实际的优先级顺序。PriorityBlockingQueue 重写了 toString 方法,展示了内部元素的优先级顺序。

DelayQueue代码示例:
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;public class TestA {public static void main(String[] args) throws InterruptedException {// 创建一个DelayQueueDelayQueue<DelayedDog> delayQueue = new DelayQueue<>();// 插入元素,设置不同的延迟时间delayQueue.put(new DelayedDog("秀逗", 8000));delayQueue.put(new DelayedDog("四眼", 5000));delayQueue.put(new DelayedDog("二哈", 2000));// 打印队列内容,仅仅是插入顺序System.out.println("DelayQueue: " + delayQueue);// 依次取出元素while (!delayQueue.isEmpty()) {DelayedDog dog = delayQueue.take();System.out.println("Removed from DelayQueue: " + dog);}// 执行结果// DelayQueue: [DelayedDog{name='二哈', delayTime=2000}, DelayedDog{name='秀逗', delayTime=8000}, DelayedDog{name='四眼', delayTime=5000}]//Removed from DelayQueue: DelayedDog{name='二哈', delayTime=2000}//Removed from DelayQueue: DelayedDog{name='四眼', delayTime=5000}//Removed from DelayQueue: DelayedDog{name='秀逗', delayTime=8000}}
}// 实现 Delayed 接口
class DelayedDog implements Delayed {private String name;        // 狗的名字private long delayTime;     // 延迟时间,以毫秒为单位private long expire;        // 到期时间点,以毫秒为单位// 构造方法,传入狗的名字和延迟时间(毫秒)public DelayedDog(String name, long delayTimeInMillis) {this.name = name;this.delayTime = delayTimeInMillis;// 计算到期时间点,当前时间加上延迟时间this.expire = System.currentTimeMillis() + delayTime;}// 获取剩余延迟时间,以给定时间单位表示@Overridepublic long getDelay(TimeUnit unit) {// 计算剩余时间,单位为毫秒long remainingTime = expire - System.currentTimeMillis();// 将剩余时间转换为指定单位return unit.convert(remainingTime, TimeUnit.MILLISECONDS);}// 比较两个 DelayedDog 对象的顺序@Overridepublic int compareTo(Delayed other) {// 强制转换为 DelayedDog 类型DelayedDog otherDog = (DelayedDog) other;// 比较两个对象的到期时间点,返回比较结果return Long.compare(this.expire, otherDog.expire);}// 返回 DelayedDog 对象的字符串表示形式@Overridepublic String toString() {return "DelayedDog{" +"name='" + name + '\'' +", delayTime=" + delayTime +'}';}
}

注意点:

getDelay() 方法返回的结果分为三种情况,分别对应着元素的状态:

  • 正数:
    如果 getDelay() 方法返回的延迟时间是一个正数,表示元素还需要等待一段时间才能被取出。
    这意味着元素的延迟时间还没有到达,处于等待状态,调用 take() 或 poll() 方法时会被阻塞,直到延迟时间到达。

  • 零:
    如果 getDelay() 方法返回的延迟时间是零,表示元素的延迟时间已经到达或者已经过去。
    这意味着元素可以被立即取出,调用 take() 或 poll() 方法会立即返回该元素。

  • 负数:
    如果 getDelay() 方法返回的延迟时间是一个负数,表示元素的延迟时间已经过去,但是元素还没有被取出。
    这种情况可能是因为延迟队列中的元素延迟时间被手动修改,导致元素的延迟时间已经过去,但是还没有被取出。

SynchronousQueue代码示例
public static void main(String[] args) {SynchronousQueue<String> queue = new SynchronousQueue<>();// 生产者线程Thread producer = new Thread(() -> {try {String[] items = {"秀逗", "二哈", "四眼"};for (String item : items) {System.out.println("放入队列: " + item);Thread.sleep(2000);queue.put(item); // 阻塞直到有消费者取走该元素}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 消费者线程Thread consumer = new Thread(() -> {try {for (int i = 0; i < 3; i++) {String item = queue.take(); // 阻塞直到有生产者放入一个元素System.out.println("拿出消费: " + item);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producer.start();consumer.start();try {producer.join();consumer.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
LinkedTransferQueue代码示例

public static void main(String[] args) {TransferQueue<String> queue = new LinkedTransferQueue<>();// 创建生产者线程1 (使用 transfer)Thread producer1 = new Thread(() -> {try {String[] items = {"transfer-1", "transfer-2", "transfer-3"};for (String item : items) {System.out.println("生产者1 生产消息 并 transfer:" + item);queue.transfer(item);System.out.println("生产者1 transfer 完成:" + item);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 创建生产者线程2 (使用 put)Thread producer2 = new Thread(() -> {try {String[] items = {"put-1", "put-2", "put-3"};for (String item : items) {System.out.println("生产者2 生产消息 并 put:" + item);queue.put(item);System.out.println("生产者2 put 完成:" + item);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 创建消费者线程Thread consumer = new Thread(() -> {try {while (true) {Thread.sleep(2000);  // 模拟消费者处理消息的延迟String item = queue.take();System.out.println("消费者 消费消息: " + item);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 启动生产者和消费者线程consumer.start();producer1.start();producer2.start();// 等待线程完成try {producer1.join();producer2.join();consumer.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
7.6、利用双端队列实现栈

代码示例:
使用LinkedList或者ArrayDeque可以很方便的实现栈的功能。

class StackOne<T> {//    // ================ LinkedList实现栈 ===========private LinkedList<T> list;public StackOne() {this.list = new LinkedList<>();}// ================ ArrayDeque实现栈 ===========
//    private ArrayDeque<T> list;
//    public StackOne() {
//        this.list = new ArrayDeque<>();
//    }/*** 往栈中添加元素* @param value 元素*/public void push(T value) {list.addFirst(value);}/*** @return 移除栈顶元素*/public T pop() {if (list.isEmpty()) {throw new IllegalStateException("Stack is empty");}return list.removeFirst();}/*** @return 返回栈顶元素*/public T peek() {if (list.isEmpty()) {throw new IllegalStateException("Stack is empty");}return list.peekFirst();}/*** 判断栈是否为空* @return true:空   false:非空*/public boolean isEmpty() {return list.isEmpty();}/*** @return 栈的元素个数*/public int size() {return list.size();}
}

八、常见的线程安全集合

这里先大致留个印象,后面会深入分析比较有代表性的集合实现细节。

类型名称适用场景
线程安全的ListCopyOnWriteArrayList适用于读多写少的场景[实际上写多的场景用也可以,尾插的时间复杂度是O(1)]
线程安全的SetCopyOnWriteArraySet适用于读多写少的场景
线程安全的MapConcurrentHashMap适用于高并发的场景,读操作和部分写操作不需要加锁,性能较好
线程安全的QueueLinkedBlockingQueue适用于生产者消费者模式,支持阻塞式的生产者消费者通信
ArrayBlockingQueue适用于固定容量的阻塞队列,生产者消费者线程间的同步和通信
ConcurrentLinkedQueue适用于高并发的无界队列,非阻塞式的线程安全队列
PriorityBlockingQueue适用于优先级队列,支持按优先级顺序存取元素

九、集合的适用场景总结

集合这个模块需要大量的实践,这里只做简单总结

类型名称适用场景
ListArrayList非线程安全,适用于读多写多,快速随机访问的场景(基本上都是尾插写多的场景用的也很多,用的最多的集合之一
LinkedList适用于频繁插入、删除操作的场景(实际上用的很少[我个人很少用],即使是用队列的功能也是ArrayDueue用的多)
CopyOnWriteArrayList适用于读多写少的线程安全场景(实际上用尾插,写也很快,除非随机写,但是随机写的场景很少)
Vector适用于需要线程安全的场景,别用这个,用CopyOnWriteArrayList代替即可
SetHashSet适用于快速查找和去重的场景 (快速查找可以理解为 contains方法,用的最多的集合之一)
LinkedHashSet适用于需要维护插入顺序,且去重的场景
TreeSet适用于需要自定义排序的场景
CopyOnWriteArraySet适用于读多写少的线程安全场景
MapHashMap适用于快速查找键值对的场景(用的最多的集合之一)
LinkedHashMap适用于需要维护插入顺序的场景
TreeMap适用于需要排序的键值对的场景
ConcurrentHashMap适用于高并发的场景,读操作和部分写操作不需要加锁,性能较好
QueueLinkedList非线程安全,适用于实现双端队列或栈的场景
PriorityQueue非线程安全,适用于需要优先级排序的场景
ArrayDeque非线程安全,适用于需要双端队列的场景
LinkedBlockingQueue线程安全,适用于生产者消费者模式,支持阻塞式的生产者消费者通信
ArrayBlockingQueue线程安全,适用于固定容量的阻塞队列,生产者消费者线程间的同步和通信
ConcurrentLinkedQueue线程安全,适用于高并发的无界队列,非阻塞式的线程安全队列
PriorityBlockingQueue线程安全,适用于优先级队列,支持按优先级顺序存取元素
DelayQueue线程安全,适用于需要延迟处理的任务,例如定时任务调度(适用于比较简单的延时处理)
SynchronousQueue线程安全,适用于不存储元素的场景,生产者必须等待消费者接受元素

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/340265.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MacOS - 为什么 Mac 安装软件后安装包在桌面上无法删除?

只要你将这磁盘里面的软件放到应用程序里面去了&#xff0c;那么用鼠标选中这个跟磁盘一样的东西&#xff0c;然后按下键盘上的 Command E 即可移除桌面上的这个磁盘。

气压、湿度、震动开关、声音、红外火焰传感器 | 配合Arduino使用案例

BMP180 气压传感器 BMP180 是一种用于测量气压的科学仪器。可以获取到温度、气压、海拔。 先在 arduino ide 中安装依赖 /****** Arduino 接线 ***** Arduino 传感器* VCC 5v* GND GND* A4 SDA * A5 SCL ***********************/#include &l…

ubuntu使用oh my zsh美化终端

ubuntu使用oh my zsh美化终端 文章目录 ubuntu使用oh my zsh美化终端1. 安装zsh和oh my zsh2. 修改zsh主题3. 安装zsh插件4. 将.bashrc移植到.zshrcReference 1. 安装zsh和oh my zsh 首先安装zsh sudo apt install zsh然后查看本地有哪些shell可以使用 cat /etc/shells 将默…

③单细胞学习-pbmc的Seurat 流程

目录 1&#xff0c;数据读取 2&#xff0c;线粒体基因查看 3&#xff0c;数据标准化 4&#xff0c;识别高变基因 5&#xff0c;进行数据归一化 6&#xff0c;进行线性降维 7&#xff0c;确定细胞簇 8&#xff0c;UMAP/tSNE降维&#xff08;保存pbmc_tutorial.rds&#…

如何用TCC方案轻松实现分布式事务一致性

本文作者:小米,一个热爱技术分享的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 哈喽,大家好!我是小米,一个热爱技术的活力小青年,今天要和大家分享的是一种在分布式系统中实现事务的一种经典方案——TCC(Try Confirm Canc…

第一篇 逻辑门(与门、或门、非门、异或门)

一、实验目的 了解DE1-SOC开发板一些外设。 掌握常用组合逻辑门电路的基本原理。 学习Verilog HDL的基本语法。 学习使用ModelSim工具对设计的电路进行仿真&#xff0c;包括编写Testbench仿真代码&#xff0c;以及ModelSim工具的使用。 熟悉使用Quartus软件从创建Quartus工…

汽车MCU虚拟化--对中断虚拟化的思考(2)

目录 1.引入 2.TC4xx如何实现中断虚拟化 3.小结 1.引入 其实不管内核怎么变&#xff0c;针对中断虚拟化无非就是上面两种&#xff0c;要么透传给VM&#xff0c;要么由Hypervisor统一分发。汽车MCU虚拟化--对中断虚拟化的思考(1)-CSDN博客 那么&#xff0c;作为车规MCU龙头…

Docker基础篇之将本地镜像发布到私有库

文章目录 1. Docker Registry简介2. 将本地镜像推送到私有库 1. Docker Registry简介 Docker Registry是官方提供的工具&#xff0c;可以用于构建私有镜像仓库。 2. 将本地镜像推送到私有库 下载Docker Registry docker pull registry现在我们可以从镜像中看到下载的Regist…

Go语言垃圾回收(GC原理)

1. GC回收机制 1.1 V1.3标记清除法 (1)概述 1.STW暂停 STW(暂停业务逻辑,找出可达和不可达对象) 2.对可达对象做上标记 标记完成之后,对象5和对象6不可达,被GC清除.之后STW结束. (2).缺点 STW :让程序暂停,程序出现卡顿.标记需要扫描整个heap.清除数据会产生heap碎片. 1.…

【 0 基础 Docker 极速入门】镜像、容器、常用命令总结

Docker Images&#xff08;镜像&#xff09;生命周期 Docker 是一个用于创建、部署和运行应用容器的平台。为了更好地理解 Docker 的生命周期&#xff0c;以下是相关概念的介绍&#xff0c;并说明它们如何相互关联&#xff1a; Docker&#xff1a; Docker 是一个开源平台&#…

JAVA基础|File,IO流

File类只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据 IO流就是对文件进行读写的 一. File类 &#xff08;一&#xff09;创建对象 import java.io.File;public class FileTest1 {public static void main(String[] args) {// 1. 创建一个File对象&#xff…

Parallels Desktop for Mac 19.4.0更新了哪些内容?有什么改进?

带来了重新设计的共享 Mac 文件夹版本&#xff0c;这些文件夹现在是符号链接&#xff0c;像指针一样指向您的 Mac 文件夹中的文件&#xff0c;同时仍然显示在 Windows 的本地磁盘上。 修复了由于共享文件夹问题导致 NinjaTrader 无法正常启动的问题。 修复了由于共享文件夹问…

Linux VSCode和Debug相关的备忘

在使用Linux时&#xff0c;总是会遇到一些意想不到的困难。而且似乎无穷无尽。这里打算写一个笔记&#xff0c;以后逐步来完善。特别是&#xff1a;调试的技能&#xff0c;总是占程序员的主要部分。在设置可视化调试环境过程中&#xff0c;常会同样陷入困境&#xff0c;有时深感…

小猪APP分发:如何轻松进行在线封装APP

你是否曾经因为需要封装一个新版本的APP而感到头疼&#xff1f;传统的封装过程往往繁琐且耗时。但是&#xff0c;别担心&#xff0c;现在有了“小猪APP分发”&#xff0c;一切变得如此简单。 小猪APP分发www.appzhu.net是一个专门提供在线APP封装服务的平台。无论你是开发者还…

Assignement5: Multi-threaded Producer Consumer Pipeline

Assignement5: Multi-threaded Producer Consumer Pipeline. 一起来打卡学习&#xff01;

NetApp 面向医疗保健行业的解决方案

NetApp 面向医疗保健行业的解决方案 旨在为您提供令人惊叹的临床应用程序性能&#xff0c;并通过内置的安全性和监控功能&#xff0c;由无中断的运营连续性提供支持。 为护理而设计 对于专注于整个医疗保健产业链&#xff08;包括提供商、付款方、生命科学&#xff09;的创新能…

跟着AI学AI_02, 时域频域和MFCC

AI&#xff1a;ChatGPT4o 时域和频域是信号处理中的两个基本概念&#xff0c;用于描述信号的不同特性。 时域 时域&#xff08;Time Domain&#xff09; 是对信号随时间变化的描述。在时域中&#xff0c;信号是作为时间的函数来表示的。 时域表示&#xff1a;例如&#xff0…

【热点】老黄粉碎摩尔定律被,量产Blackwell解决ChatGPT耗电难题

6月3日&#xff0c;老黄又高调向全世界秀了一把&#xff1a;已经量产的Blackwell&#xff0c;8年内将把1.8万亿参数GPT-4的训练能耗狂砍到1/350&#xff1b; 英伟达惊人的产品迭代&#xff0c;直接原地冲破摩尔定律&#xff1b;Blackwell的后三代路线图&#xff0c;也一口气被…

【康耐视国产案例】智能AI相机联合OSARO为Zenni眼镜实现订单履约自动化

在电商潮流下&#xff0c;Zenni眼镜作为全球领先的在线眼镜零售商&#xff0c;每年销售超过600万副眼镜&#xff0c;却面临着一个独特而复杂的问题——需要通过扫描眼镜盒内的条形码来处理订单。传统手动处理已经到达流程瓶颈&#xff0c;急需一种更加自动化、可扩展的方法。为…

Pikachu靶场下载、配置

目录 下载 配置 新版小蓝皮 搭建网站 搭建数据库 初始化靶场 旧版小绿皮 配置数据库 配置网站 下载 GitHub下载地址&#xff1a; 百度网盘&#xff1a;https://pan.baidu.com/s/1j2WpdLvwAbmpAH76d_yitw?pwdwf8j 提取码&#xff1a;wf8j 迅雷链接&#xff1a;http…