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循环 EntrySet
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("key: " + key + " value: " + value);
        }

        // ========== map遍历方式二: for循环 KeySet
        for (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, "跳"));

        // 使用 Comparable
        Collections.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, "蹦迪"));

        // 使用 Comparator
        dogss.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;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobby='" + hobby + '\'' +
                '}';
    }

    @Override
    public 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;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

BlockingQueue的一些实现类代码示例
ArrayBlockingQueue 和 LinkedBlockingQueue代码示例:
public static void main(String[] args) throws InterruptedException {

        // ============== ArrayBlockingQueue 和 LinkedBlockingQueue的使用示例 (大致相同)

        // 创建一个容量为3的ArrayBlockingQueue
        BlockingQueue<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 {
        // 创建一个DelayQueue
        DelayQueue<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;
    }

    // 获取剩余延迟时间,以给定时间单位表示
    @Override
    public long getDelay(TimeUnit unit) {
        // 计算剩余时间,单位为毫秒
        long remainingTime = expire - System.currentTimeMillis();
        // 将剩余时间转换为指定单位
        return unit.convert(remainingTime, TimeUnit.MILLISECONDS);
    }

    // 比较两个 DelayedDog 对象的顺序
    @Override
    public int compareTo(Delayed other) {
        // 强制转换为 DelayedDog 类型
        DelayedDog otherDog = (DelayedDog) other;
        // 比较两个对象的到期时间点,返回比较结果
        return Long.compare(this.expire, otherDog.expire);
    }

    // 返回 DelayedDog 对象的字符串表示形式
    @Override
    public 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线程安全,适用于不存储元素的场景,生产者必须等待消费者接受元素

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java如何实现pdf转base64以及怎么反转?

问题需求 今天在做发送邮件功能的时候&#xff0c;发现邮件的附件部分&#xff0c;比如pdf文档&#xff0c;要求先把pdf转为base64&#xff0c;邮件才会发送。那接下来就先看看Java 如何把 pdf文档转为base64。 两种方式&#xff0c;一种是通过插件 jar 包的方式引入&#xf…

pyside6安装

目录 1. 安装2. 配置PyCharm环境3. 测试 1. 安装 打开Anaconda Prompt&#xff0c;执行以下命令创建虚拟环境并激活 # 创建名为 myEnv, python版本为3.9 的虚拟环境 conda create -n myEnv python3.9 # 激活创建的虚拟环境 conda avtivate myEnv使用pip安装Pyside6&#xff0…

git拉去代码报错“Failed to connect to 127.0.0.1 port 31181: Connection refused“

最近参与了一个新项目&#xff0c;在使用git clone 克隆代码时遇到了一个报错"fatal: unable to access ‘https://example.git/’: Failed to connect to 127.0.0.1 port 31181: Connection refused",今天就和大家分享下解决过程。 报错详情 在使用git clone 克隆…

回溯算法题模板与实战详解

文章目录 回溯算法模板实战详解全排列问题N皇后问题 组合总和问题子集问题括号生成问题单词搜索问题 回溯算法是一种通过试错的方式来寻找问题解决方案的算法&#xff0c;常用于解决约束满足问题、组合优化问题和排列组合问题。其核心思想是深度优先搜索(DFS)与剪枝策略的结合&…

Apipost IDEA 插件使用说明

Apipost Helper作为IDEA插件&#xff0c;可以快速生成和查询API文档&#xff0c;直观友好地在IDE中调试接口。它简化了开发流程并提升效率&#xff0c;即使新手也能够迅速掌握。Apipost Helper提供了诸多便捷功能&#xff0c;如通过代码查找接口或者通过接口查找代码等&#xf…

第十五届蓝桥杯物联网试题(国赛)

好&#xff0c;很好&#xff0c;国赛直接来个阅读理解&#xff0c;我猛做4个小时40分钟&#xff0c;cpu都干冒烟了&#xff0c;也算是勉强做完吧&#xff0c;做的很仓促&#xff0c;没多检查就交了&#xff0c;方波不会&#xff0c;A板有个指示灯没做&#xff0c;其他应该都还凑…

师彼长技以助己(3)逻辑思维

师彼长技以助己&#xff08;3&#xff09;逻辑思维 前言 上一篇文章进行了工程思维和产品思维的测试&#xff0c;并介绍了几个比较重要的产品思维模型。接下来本篇介绍工程思维。&#xff08;注意产品思维并不代表产品经理思维&#xff0c;工程思维也并不代表工程师思维&…

学习小心意——python的构造方法和析构方法

构造方法和析构方法分别用于初始化对象的属性和释放类占有的资源 构造方法_init_() 语法格式如下&#xff1a; class 类名:def __init__(self, 参数1, 参数2, ...):# 初始化代码self.属性1 参数1self.属性2 参数2# ... 示例代码如下 class Student:def __init__(self):s…

期权的权利金怎么算的

期权权利金的计算涉及多个因素&#xff0c;包括敲定价格、到期时间以及整个期权合约的具体情况。期权的权利金具体的计算公式和因素可能因不同的期权合约和市场条件而有所不同&#xff0c;下文为大家介绍期权的权利金怎么算的 &#xff1f;本文来自&#xff1a;期权酱 一、期权…

实战:Zig 编写高性能 Web 服务(1)

1.1 认识 std.http std.http 是 Zig 标准库中用于处理 HTTP 相关操作的类库。以我学习新的编程语言的经历来看&#xff0c;编写web程序是最常见的技术场景&#xff0c;所以熟练掌握 HTTP server/client 服务相关的编程知识是比较重要的。 std.http 主要包含以下API: Client…

19、matlab信号预处理中的中值滤波(medfilt1()函数)和萨维茨基-戈雷滤波滤(sgolayfilt()函数)

1、中值滤波&#xff1a;medfilt1()函数 说明&#xff1a;一维中值滤波 1&#xff09;语法 语法1&#xff1a;y medfilt1(x) 将输入向量x应用3阶一维中值滤波器。 语法2&#xff1a;y medfilt1(x,n) 将一个n阶一维中值滤波器应用于x。 语法3&#xff1a;y medfilt1(x,n…

Django中使用ModelForm保存数据

相对来说&#xff0c;使用ModelForm保存数据在Django中算是比较简单的。主要原因是ModelForm是建立在Django的模型&#xff08;Model&#xff09;之上的&#xff0c;它可以自动根据模型的定义生成表单&#xff0c;包括字段和验证规则。这样可以大大简化开发人员处理表单数据的工…

低空经济发展报告

低空经济是指利用低空空间进行商业开发和经济活动的概念。随着航空技术的发展和无人机的普及&#xff0c;低空经济逐渐成为一个新兴的经济领域。 低空经济可以涵盖的领域非常广泛&#xff0c;包括但不限于物流配送、农业植保、城市交通、旅游观光等。利用无人机等飞行器进行物…

C\C++内存管理(未完结)

文章目录 一.C\C内存分布二.C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free三.C内存管理方式3.1.new/delete操作内置类型3.2.new和delete操作自定义类型 四.operator new与operator delete函数&#xff08;重要点进行讲解&#xff09;4.1. operator new与oper…

MySQL—约束—演示(基础)

一、引言 这篇博客主要演示&#xff1a;前面博客在约束概念与分类中讲到的&#xff1a;非空约束、唯一约束、检查约束、默认约束、主键约束、外键约束等等操作。 二、需求 根据下列需求&#xff0c;去完成表结构的创建。 注意&#xff1a;&#xff08;对于一个字段我们可以添…

语言模型解构——Tokenizer

1. 认识Tokenizer 1.1 为什么要有tokenizer&#xff1f; 计算机是无法理解人类语言的&#xff0c;它只会进行0和1的二进制计算。但是呢&#xff0c;大语言模型就是通过二进制计算&#xff0c;让你感觉计算机理解了人类语言。 举个例子&#xff1a;单1&#xff0c;双2&#x…

新农大杏之所向,心之所往团队实地调研与行动

南疆实地调研背景 近期&#xff0c;我团队前往南疆的喀什、和田地区&#xff0c;深入英吉沙县调研“赛买提”杏农民采摘后的收益情况。调研中发现&#xff0c;由于“赛买提”杏属于呼吸跃变型水果&#xff0c;采摘后呼吸作用加剧&#xff0c;加之收获季节易受链格孢侵染引起的…

DevOps后时代,构建基于价值流的平台化工程

本文来自腾讯蓝鲸智云社区用户: CanWay 平台化工程涉及双重核心意义。一方面&#xff0c;是类似利用IDE等工具提高工程师效率的平台化工程&#xff0c;如GitOps或命令行调度般便捷。然而&#xff0c;本文重点探讨的是基于价值流的平台化工程&#xff0c;尤其针对传统金融行业&a…

家宽动态公网IP,使用docker+ddns 实现动态域名解析

官方地址&#xff1a;https://github.com/jeessy2/ddns-go 安装docker docker pull jeessy/ddns-godocker run -d --name ddns-go --restartalways --nethost -v /opt/ddns-go:/root jeessy/ddns-go然后访问ip端口 配置时注意如下

剪画小程序:音频提取:学会这个方法可以提取任何音频!

我是测试了几天&#xff0c;发现是真的好用&#xff0c;所以写了这篇文章给宝子们做下分享 现在各大主流音乐平台都要开通会员才能听取完整版的歌曲&#xff0c; 有些歌甚至只能一个平台上播放&#xff0c;需要来回切换不同的音乐平台十分麻烦 当你正想将这首歌曲收藏到歌单…