力洋深圳做网站公司,网站建设的特征,模板网站也可以做优化,网站开发 flex目录 集合一、图解集合的继承体系#xff1f;#xff08;[图片来源](https://www.cnblogs.com/mrhgw/p/9728065.html)#xff09;点击查看大图二、List,Set,Map三者的区别#xff1f;三、List接口的实现3.1、Arraylist 、 LinkedList、Vector3.2、Arraylist 、 LinkedList、… 目录 集合一、图解集合的继承体系[图片来源](https://www.cnblogs.com/mrhgw/p/9728065.html)点击查看大图二、List,Set,Map三者的区别三、List接口的实现3.1、Arraylist 、 LinkedList、Vector3.2、Arraylist 、 LinkedList、Vector 三者的区别?3.3、对RandomAccess接口的理解 四、Set接口的实现4.1、HashSet、LinkedHashSet、TreeSet4.2、HashSet、LinkedHashSet 、 TreeSet 三者的区别4.3、HashSet如何检查重复 五、Map接口的实现5.1、HashMap、LinkedHashMap、TreeMap、Hashtable5.2、HashMap、LinkedHashMap、TreeMap、Hashtable对比5.3、Map的五种遍历方式 六、用Comparator和Comparable进行自定义排序七、Queue、Deque、BlockingQueue接口的实现7.1、Queue 、 Deque、BlockingQueue 接口的区别7.2、LinkedList 、ArrayDeque、PriorityQueue7.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)维护链表使性能略低于 HashSetO(log n)唯一性保证方式hashCode() 和 equals()hashCode() 和 equals()compareTo() 或 Comparator适用场景高效查找和修改对顺序无要求保持插入顺序高效查找和修改需要排序的场景
4.3、HashSet如何检查重复
把对象加入HashSet时HashSet会先计算对象的hashcode值来判断对象加入的位置同时也会与其他加入的对象的hashcode值作比较如果没有相符的hashcodeHashSet会假设对象没有重复出现。但是如果发现有相同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) {MapString, String map new HashMap();map.put(秀逗,博美);map.put(四眼,哈士奇);map.put(大黄,田园犬);// map遍历方式一 for循环 EntrySetfor (Map.EntryString, 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 迭代器IteratorMap.EntryString, String iterator map.entrySet().iterator();while (iterator.hasNext()) {Map.EntryString, String entry iterator.next();System.out.println(key: entry.getKey() value: entry.getValue());}// map遍历方式四 KeySet 迭代器IteratorString 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) {ArrayListDog 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);ArrayListDog 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 ComparableDog {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 是基于链表的数据结构实现了 List 和 Deque 接口可以用作列表、队列(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使用示例 LinkedListDog 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 ArrayDequeDog dogsArrayDeque new ArrayDequeDog();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 使用示例 PriorityQueueDog 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的ArrayBlockingQueueBlockingQueueDog arrayQueue new ArrayBlockingQueue(3);// 示例1ArrayBlockingQueue 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秀逗, age8}//大黄 插入成功}PriorityBlockingQueue代码示例
public static void main(String[] args) throws InterruptedException {// 创建一个PriorityBlockingQueue使用自定义的比较器按年龄排序BlockingQueueDog 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二哈, age2}, Dog{name大黄, age4}, Dog{name四眼, age5}, Dog{name秀逗, age8}]//移除: Dog{name二哈, age2}//移除: Dog{name大黄, age4}//移除: Dog{name四眼, age5}//移除: Dog{name秀逗, age8}//从空的PriorityBlockingQueue中取元素...//巴豆加入了PriorityBlockingQueue//从PriorityBlockingQueue中取到了元素: Dog{name巴豆, age1}}注意点 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 {// 创建一个DelayQueueDelayQueueDelayedDog 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二哈, delayTime2000}, DelayedDog{name秀逗, delayTime8000}, DelayedDog{name四眼, delayTime5000}]//Removed from DelayQueue: DelayedDog{name二哈, delayTime2000}//Removed from DelayQueue: DelayedDog{name四眼, delayTime5000}//Removed from DelayQueue: DelayedDog{name秀逗, delayTime8000}}
}// 实现 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) {SynchronousQueueString 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) {TransferQueueString 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 StackOneT {// // LinkedList实现栈 private LinkedListT list;public StackOne() {this.list new LinkedList();}// ArrayDeque实现栈
// private ArrayDequeT 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线程安全适用于不存储元素的场景生产者必须等待消费者接受元素