【java学习】集合框架

1,Collection接口

Object与集合
Collection继承树

1)Collector

java8新特性。stream里有一个collect(Collector c)方法,这个方法里面接收一个Collector的实例。可以将一个管道流的结果集到一个list中。

2)Collection和Collections区别

a.Collection:在Java.util下的一个接口,是所有集合的root接口。Collection接口继承于Iterator.
b.Collections: Java.util下的一个专用静态类,它包含有各种有关集合操作的静态方法。

2,Arrays数组

1)Array与Arrays的区别

①Array(数组类)

提供了动态创建和访问 Java 数组的方法。其中的元素的类型必须相同。
效率高,但容量固定且无法动态改变。
它无法判断其中实际存有多少元素,length只是告诉我们array的容量。

②Arrays(静态类)

此静态类专门用来操作array ,提供搜索、排序、复制等静态方法。
equals():比较两个array是否相等。array拥有相同元素个数,且所有对应元素两两相等。
sort():用来对array进行排序。
binarySearch():在排好序的array中寻找元素。

2)数组定义

 int [] a;//一开始不定义长度
 int [] a = new int[10]; 

3)功能

①asList()

将数组转换成java.util.ArrayList类型;

②sort()

数组的排序;

③binarySearch()

数组的二分查找;

④equals()

两个数组的比较;

⑤fill()

给数组赋初值。

类似C语言中的memset()应用

char *p = new char[90];
memset((void *)p, -2, 90);
//把90个char都赋成-2,因为C++里的char是一个byte(8bit);

java写法:

java.util.Arrays.fill( float[], float) 

4)数组名

数组名不等价于指针,只有数组名作为函数参数时,才退化为指针,此时数组名的sizeof()就是指针大小,除了这种情况外,均是整个指整个数组的大小。

char *string_a=(char *)malloc(100*sizeof(char));//对于64位机:sizeof(string_a)为8
char string_b[100];//sizeof(string_b)为100.

5)概念

①三维数组:数组A[0..4,-1..-3,5..7]共有5*3*3=45个元素。

3,Iterator接口

1)概念

Iterator是所有集合的总接口,其他所有接口都继承于它,该接口定义了集合的遍历操作,Collection接口继承于Iterator,是集合的次级接口(Map独立存在,除外),定义了集合的一些通用操作。

2)使用

Iterator支持从源集合中安全地删除对象(在Iterator上调用remove方法)。
java.util.Iterator其接口定义如下:

public interface Iterator {  
  boolean hasNext(); //判断容器内是否还有可供访问的元素 
  Object next();  //返回迭代器刚越过的元素的引用,返回值是Object,需要强制转换成自己需要的类型
  void remove();  //删除迭代器刚越过的元素
}  

Collection类的Iterator遍历list(Vector, ArrayList, LinkedList等集合元素):
Collection list= new Vector();

    Iterator iterator = list.iterator();  
        while(iterator.hasNext()){  
            String s = iterator.next();  
            if (s.contains("1")) {
                iterator.remove();
            }
        }  

3)集合的安全删除

如果在循环的过程中调用集合的remove()方法,就会导致循环出错。
如果你想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,因为它的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。

4,Set接口

Java.util.Set。

1)特点

①不可重复(作用:询问某个对象是否在某个Set);
②允许null值;
③无序。

2)方法

①新建实例

Set<String> set = new LinkedHashSet<String>();

②遍历
Iterator遍历;
③删除
移除的是值为”a”的项。不能按索引移除。
set.remove(“a”);

3)AbstractSet 抽象类

①架构

这里写图片描述
父类:

java.lang.Object
    java.util.AbstractCollection<E>
        java.util.AbstractSet<E> 
public abstract class AbstractSet<E>
extends AbstractCollection<E>
implements Set<E>

AbstractSet 是一个抽象类,它继承于AbstractCollection。AbstractCollection实现了Set中的绝大部分函数,为Set的实现类提供了便利。
HastSet 和 TreeSet 是Set的两个实现类。
HashSet依赖于HashMap,它实际上是通过HashMap实现的。HashSet中的元素是无序的。
TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。TreeSet中的元素是有序的。

LinkedHashSet继承于HashSet,是具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。

4)HashSet

①概念

基于HashMap实现,非线程安全,地址不连续,查询慢增删快。适合插入和删除操作频繁的场景。
底层用hashCode()算法实现,保证元素的无序唯一,自定义对象存进HashSet为了保证元素内容不重复需要覆盖hashCode()与equals()方法。

②特点

a.能够最快的获取集合中的元素,效率非常高(以空间换时间).
b.元素不能重复
会根据hashcode和equals来判断是否是同一个对象,如果hashcode一样,并且equals返回true,则是同一个对象,不能重复存放。
c.无序
哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定顺序来存放。

5)SortedSet接口

有序(Unicode升序)。(存放对象不能排序则报错、可指定排序规则)
唯一实现类:TreeSet.

6)TreeSet

①概念

继承SortedSet;要求元素有序,自定义的对象需要实现Comparable接口的 compareTo(object o)方法。基于红黑树实现,对象以升序存储,访问和遍历快。

②特点

i>非线程安全
ii>有序(自动排序)
可以按照自然顺序或者自定义顺序自动排序,是J2SE中唯一可实现自动排序的类型。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
iii>不允许插入null值。

7)LinkedHashSet

①概念

根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

②特点

有序(按插入顺序输出)

5,List接口

List 是一个接口,它继承于Collection的接口。

1)特点

①有序队列(怎么存怎么取);
对象以线性方式存储(查询快,增删慢),只有一个开头和一个结尾。
②可重复;

2)AbstractList

这里写图片描述

3)ArrayList

JDK1.2。
①底层的数据结构:动态数组(数组长度是可变的百分之五十延长)
地址连续,查询快,增删慢。适合查询操作频繁的场景。
②非线程安全;
③当ArrayList中的元素超过它的初始大小时,ArrayList只增加50%的大小。(节约内存)

4)LinkedList

①底层的数据结构:基于链表结构;
是一个双向链表,常用堆栈与队列的实现,地址不连续,查询慢,增删快。
适合插入和删除操作频繁的场景。
②非线程安全

5)Vector

JDK1.0。
①底层是数据结构:动态数组(数组长度是可变的百分之百延长。在内存中占用连续的空间);
②线程安全;(是线程安全的ArrayList)
在内存中占用连续的空间(查询、增删都很慢,效率低,被ArrayList替代了)
③可以设置增长因子;
当Vector中的元素超过它的初始大小时,Vector会将它的容量翻倍。

6)Stack(栈)

它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。

7)常用方法

    int size();        
    boolean isEmpty();        
    boolean contains(Object o);        
    //以正确的顺序返回list中元素的迭代器    
    Iterator<E> iterator();
    Object[] toArray();     
    //在list的末尾插入元素(实现类可以选择插入的位置)
    boolean add(E e);
    boolean addAll(Collection<? extends E> c);
    //在指定位置插入指定集合
    boolean addAll(int index, Collection<? extends E> c);
    //如果指定元素存在list中,移除list中第一次出现的指定元素(实现类可以选择具体的实现)
    boolean remove(Object o);
    //判断list中是否包含某个集合
    boolean containsAll(Collection<?> c);
    //删除list中包含的Collection中的所有元素
    boolean removeAll(Collection<?> c);
    //保留list中包含的Collection中的所有元素
    boolean retainAll(Collection<?> c);
    //将该列表的每个元素替换为将该运算符应用于该元素的结果。
    default void replaceAll(UnaryOperator<E> operator);
    //对list中的元素排列
    default void sort(Comparator<? super E> c);
    void clear();
    boolean equals(Object o);
    int hashCode();
    E get(int index);
    E set(int index, E element);
    //在指定位置上增加指定元素
    void add(int index, E element);
    //删除指定索引上的元素
    E remove(int index);
    //获取对象的第一个索引
    int indexOf(Object o);
    //获取对象的最后一个索引
    int lastIndexOf(Object o);
    //返回list的list 迭代器
    ListIterator<E> listIterator();
    //从指定位置返回list的迭代器
    ListIterator<E> listIterator(int index);
    //返回list的子list
    List<E> subList(int fromIndex, int toIndex);

6,Queue

在jdk5.0以前,通常的实现方式是使用java.util.List集合来模仿Queue。Queue的概念通过把对象添加(称为enqueuing的操作)到List的尾部(即Queue的后部)并通过从List的头部(即Queue的前部)提取对象而从 List中移除(称为dequeuing的操作)来模拟。你需要执行先进先出的动作时可以直接使用Queue接口就可以了。
元素并没有以某种特定顺序来存放。

7,Map接口

Map的继承树
与Collection接口无关,有一个子接口SortedMap。

1)Map特点

①键值对存储

Map提供了一种映射关系,其中元素以键值对(key-value)存储。

②key值不可重复,value值可以重复;键和值都可以为null。

一个value值可以和很多key值形成对应关系,每个key最多只能映射到一个value。

③无序

2)操作

①添加

Object put(Object key, Object value): 添加。若关键字已存在,那么取代旧值,返回关键字的旧值。若关键字原先不存在,则返回null
void putAll(Map t): 将来自特定映像的所有元素添加给该映像

②移除

Object remove(Object key): 从映像中删除与key相关的映射
void clear(): 从映像中删除所有映射

③查询

Object get(Object key): 获得值,返回相关的对象,如果没有在该映像中找到该关键字,则返回null。
boolean containsKey(Object key): 判断映像中是否存在关键字key
boolean containsValue(Object value): 判断映像中是否存在值value
int size(): 返回当前映像中映射的数量
boolean isEmpty() :判断映像中是否有任何映射

④Entry

Map中的键值对以Entry类型的对象实例形式存在。Map的entrySet()方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键/值对。通过这个集合的迭代器,您可以获得每一个条目(唯一获取方式)的键或值并对值进行更改。

3)AbstractMap抽象类

这里写图片描述
AbstractMap 的成员变量

transient volatile Set<K>        keySet;//keySet, 保存 map 中所有键的 Set
transient volatile Collection<V> values;//values, 保存 map 中所有值的集合

他们都是 transient, volatile, 分别表示不可序列化、并发环境下变量的修改能够保证线程可见性。
需要注意的是 volatile 只能保证可见性,不能保证原子性,需要保证操作是原子性操作,才能保证使用 volatile 关键字的程序在并发时能够正确执行。

4)SortedMap接口

特点: key唯一,有序(Unicode升序)
实现类:TreeMap

5)TreeMap

①概念

实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

②特点

i>有序,非null插入;
基于红黑树实现,可以按照自然顺序或者自定义顺序自动排序,不允许插入null值,查找效率比较高,适合需要排序的场景。
ii>非线程安全

6)Hashtable

①概念

JDK1.0。

②特点

i>不允许键或值为空;
ii>线程安全但效率低下;
即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
Properties是HashTable的子类,主键和值都是字符串。

7)HashMap

①概念

JDK1.2。
定义:

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

基于HashMap.Node数组加单向链表实现,是基于哈希表来实现的,用链表定址法来解决哈希冲突。

根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。

②特点

i>HashMap中的Entry对象是无序排列的。
ii>Key值和value值都可以为null,但是一个HashMap只能有一个key值为null的映射(key值不可重复,value可重复)。
iii>非线程安全;
iv>基于数组实现,数组里的元素是一个单向链表。
地址不连续,查询慢增删快;适合插入和删除操作频繁的场景。

③原理

HashMap基于数组实现,数组里的元素是一个单向链表。
i>查找流程
计算哈希值,根据哈希值与数组容量计算它所在的索引,根据索引查找它所在的链表。
在单向链表中查找该元素
ii>删除流程
计算哈希值,根据哈希值与数组容量计算它所在的索引,根据索引查找它所在的链表。
从起始节点开始遍历,查找要删除的元素,删除该节点,将节点的后继添加为它前驱的后继
iii>插入流程
根据key计算hash值,并根据hash值和数组容量,找到索引值,该位置即为存储该元素的链表所在处。
遍历table[i]位置的链表,查找相同的key,若找到则则用新的value替换掉oldValue.
若没有查找到相同的key,则添加key到table[i]位置,新添加的元素总是添加在单向链表的表头位置,后面的元素称为它的后继。

8)ConcurrentHashMap

方法访问数组中的数据,可能只涉及到数组的部分,对整个数组加锁会降低线程并发执行的效率,所以如果对数组分段加锁,使用segment分片锁,这样一个线程只会锁住数组的一片,其他线程仍可以访问数组的其他片进行写操作,具有这样的分片锁的机制是就是ConcurrentHashMap。
ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。

①概念

基于hash表实现。
ConcurrentHashMap 使用segment来分段和管理锁(分段锁,提高了效率),segment继承自ReentrantLock。故ConcurrentHashMap使用ReentrantLock来保证线程安全。

②优点

线程安全且高效

③原理

ConcurrentHashMap与HashMap一样用数组加链表存储元素,用链表定址法来解决哈希冲突,不同之处在于当链表长度大于8的时候会将链表转换为一棵红黑树,查找时间复杂度由O(N)变成O(lgN)
ConcurrentHashMap并发控制的关键在于一个变量,如下所示:

private transient volatile int sizeCtl;

sizeCtl被volatile关键字修饰是一个多线程共享的变量,当它的值为负数的时候说明某个线程正在操作这个Map,想要去操作这个Map的线程就要一直去竞争这个sizeCtl,没有得到这个变量的值就要一直自旋等待这个变量,当占用这个变量的线程操作完成后,要将这个变量的值设置回来,以便让其他线程走出自旋,竞争到该变量。
这种同步进制事实上是一种CAS的做法。

CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。

④与HashTable区别

区别在于HashTable是对整个代码块加锁,而ConcurrentHashMap是使用分片锁,粒度较小,不用对整个代码块加锁,提高了读写速率。

9)LinkedHashMap

①有序(可设为true/false)

保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时用带参数,按照应用次数排序。
在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

②输出的顺序和输入的相同

LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。

10)SpareArray

①概念

SparseArray是android里为

②优缺点

i>优点:
key保存在int mKeys[]数组中,相对于HashMap不再对key进行自动装箱,避免资源消耗。但是vaule是保存在Object[] mValues数组中还是需要自动装箱的。
相对于HashMap,不再使用额外的Entry对象来存储数据,减少了内存开销。
数据量小的情况下,随机访问效率更高。
ii>缺点
插入操作需要复制数组,增删效率低。
数据量巨大时,复制数组成本巨大,gc()成本也巨大。
数据量巨大时,查询效率也会明显下降。

③原理

SpareArray的key是一个int有序数组,查找过程使用的二分查找。
i>插入流程
用二分查找法查找元素的key。
如果插入的数据冲突了,则直接覆盖原则。
如果当前插入的key上的数据为DELETE,则直接覆盖。
如果前面几步都失败了,则检查是否需要gc()并且在索引上插入数据。

11) WeakHashMap

①概念

继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。

这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。
和HashMap一样,WeakHashMap是不同步的。

9,对比

1)ArrayList、Vector、LinkedList

①ArrayList、Vector底层都采用数组方式存储数据。Vector效率低,ArrayList查询快,增删慢。
LinkedList底层采用链表存储数据。查询慢,增删快。
②对于线程安全
Vector是线程安全的,也就是说是同步的,但效率很低;
ArrayList是非线程安全的;
③数据增长
当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半

2)HashMap与HashTable对比

hashmap的底层是数组+链表的结构,是线程不安全的。
hastable是在hashmap的基础上,对整个哈希表(数组)加锁 sychronized实现线程安全。–>因为加锁效率低

①同步性

HashMap是线程序不安全的(所以效率高),不是同步的.
Hashtable是线程安全的(Synchronize),也就是说是同步的。

②值

HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。

③快速失败

迭代HashMap采用快速失败机制,而HashTable不是。
快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。

④继承不同

都实现了Map接口。
HashMap继承AbstractMap类。
HashTable继承了Dictionary类。

⑤两个遍历方式的内部实现不同

都使用了Iterator。
由于历史原因,Hashtable还使用了Enumeration的方式。

⑥哈希值的使用不同

HashTable直接使用对象的hashCode;
HashMap重新计算hash值,用与代替求模。

⑦内部实现方式的数组的初始大小和扩容的方式不同。

HasnTable中的hash数组默认大小是11,增加的方式是old*2 + 1。
HashMap中的hash数组的默认大小是16,而且一定是2的指数。

10,排序规则

①自然排序

排序对象通过实现java.lang.Comparable接口,并且重写comparaTo()方法(自然比较方法),返回0则表示是同一个对象,否则为不同对象。

package com.set;

import java.util.Set;
import java.util.TreeSet;

class Student1 implements Comparable<Student1>{
    int id;
    public Student1(int id) {
        this.id = id;
    }
    @Override
    public String toString() {
        return this.id+"";
    }
    @Override
    public int hashCode() {
        return this.id;
    }
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Student1){
            Student1  stu = (Student1) obj;
            if (stu.id == this.id)
                return true;
        }
        return false;
    }
    public int compareTo(Student1 o) {
        return (this.id-o.id);
    }
}

public class TreeSetTest {
    public static void main(String[] args) {
        Set<Student1> set = new TreeSet<Student1>();
        Student1 s1 = new Student1(5);
        Student1 s2 = new Student1(1);
        Student1 s3 = new Student1(2);
        Student1 s4 = new Student1(4);
        Student1 s5 = new Student1(3);
        set.add(s1);
        set.add(s2);
        set.add(s3);
        set.add(s4);
        set.add(s5);
        for (Student1 s : set) {
            System.out.println(s);
        }
    }

}

②客户排序

建立一个第三方类并实现java.util.Comparator接口。并重写方法。
Comparable接口将比较代码嵌入自身类中,而后者在一个独立的类中实现比较。然后排序:Collections.sort(list, new MySort ());的第二个参数返回一个int型的值,就相当于一个标志,告诉sort方法按什么顺序来对list进行排序。
如:定义集合形式为TreeSet ts = new TreeSet(new 第三方类());

package com.set;

import java.util.Set;
import java.util.TreeSet;

class MySort implements java.util.Comparator<Student2>{

    public int compare(Student2 o1, Student2 o2) {
        return o2.id-o1.id;
    }
}
class Student2{
    int id;
    public Student2(int id) {
        this.id = id;
    }
    @Override
    public String toString() {
        return this.id+"";
    }
    @Override
    public int hashCode() {
        return this.id;
    }
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Student2){
            Student2  stu = (Student2) obj;
            if (stu.id == this.id)
                return true;
        }
        return false;
    }
}
public class TreeSetTest2 {
    public static void main(String[] args) {
        Set<Student2> set = new TreeSet<Student2>(new MySort());
        Student2 s1 = new Student2(5);
        Student2 s2 = new Student2(1);
        Student2 s3 = new Student2(2);
        Student2 s4 = new Student2(4);
        Student2 s5 = new Student2(3);
        set.add(s1);
        set.add(s2);
        set.add(s3);
        set.add(s4);
        set.add(s5);
        for (Student2 s : set) {
            System.out.println(s);
        }
    }

}

11,性能优化

1)使用优化过的数据集合SparseArray代替HashMap,HashMap为每个键值都提供一个对象入口,使用SparseArray可以免去基本对象类型转换为引用数据类想的时间。

12,快速失败和安全失败

1)快速失败(fail—fast)

①概念

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
fail-fast是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。

②原理

迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

③场景

java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

2)安全失败(fail—safe)

①概念

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

②原理

由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。

③缺点

基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

④场景

java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

猜你喜欢

转载自blog.csdn.net/sunshinetan/article/details/72599483