day17_18_java中集合的详解

集合

一、数组

数组:是一个容器,可以存放相同类型的多个数据元素。

数组的局限性:

  • 长度固定(添加-扩容,删除-缩容)
  • 元素类型要相同(兼容)

二、集合

集合:是java中提供的一种容器,可以用来存放多个数据。

(一)集合的特点:

  1. 长度不固定
  2. 可以存储不同类型(但一般存储同一类型)

(二)集合和数组都是容器,那么他们的区别是什么?

  1. 数组的长度固定不变,集合的长度是可变的
  2. 数组中存储的是同一类型的元素,可以存储基本类型和引用类型。集合存储的都是对象。而且对象对象的类型可以不一致。在开发中一般当对象多的时候使用集合进行存储。

(三)集合体系

day17_18_java中集合的详解_第1张图片

1、List 有序,重复
  • ArrayList
    底层数据结构是数组,查询快,增删慢。
    线程不安全,效率高
  • Vector
    底层数据结构是数组,查询快,增删慢。
    线程安全,效率低
  • LinkedList
    底层数据结构是双链表,查询慢,增删快。
    线程不安全,效率高
2、Set 无序,唯一
  • HashSet
    底层数据结构是哈希表。(无序,唯一)
    如何来保证元素唯一性?
    1.依赖两个方法:hashCode()和equals()

  • TreeSet
    底层数据结构是红黑树。(唯一,有序)

  • LinkedHashSet
    底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
    1.由链表保证元素有序
    2.由哈希表保证元素唯一

如何保证元素排序的呢?
自然排序
比较器排序
如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定

(四)Collection常用的方法

1、基本方法
  • public boolean add(E e):把给定的对象添加到当前集合中。
  • public void clear():清空集合中的所有元素。
  • public boolean remove(E e):把给定的对象从当前集合中删除。
  • public boolean contains(E e):判断当前集合中是否包含给定的对象
  • public boolean isEmpty():判断当前集合是否为空。
  • public int size():返回集合中元素的个数。
  • public Object[] toArray():把集合中的元素存放到Object数组中
2、高阶方法
  • boolean addAll(Collection c):添加一个集合到当前集合。
  • boolean removeAll(Collection c):移除一个子集合。
  • boolean retainAll(Collection c):取当前集合和传入集合的交集给当前集合,如果当前集合的长度改变则返回true
  • boolean containsAll(collection c):判断当前集合是否包含指定集合中所有元素

(五)集合的遍历

1、通过toArray()方法转换为数组

用集合提供的toArray()将集合转换成object数组。使用数组的遍历方式遍历

// 1、创建一个集合对象
Collection collection = new ArrayList();
collection.add("hello");
collection.add("world");
// 2、将集合转换为数组
Object[] array = collection.toArray();
// 3、数组遍历
// 用遍历数组的方式遍历
for(int i = 0;i<array.length;i++){
    //array[i]就是集合的元素
    System.out.println(array[i]);
}
2、Iterator

迭代器接口定义了几个方法,最常用的是以下三个:

  • next() - 返回迭代器的下一个元素,并将迭代器的指针移到下一个位置。
  • hasNext() - 用于判断集合中是否还有下一个元素可以访问。
  • remove() - 从集合中删除迭代器最后访问的元素(可选操作)。

获取一个迭代器

// 1、创建一个集合对象
Collection collection = new ArrayList();
collection.add("hello");
collection.add("world");
// 2、通过集合的方法获取一个迭代器
Iterator it = collection.iterator();

迭代器遍历元素

while(it.hasNext()) {
    System.out.println(it.next());
}
3、增强for遍历集合

语法

for(数据类型 变量名 : 集合名){
    // 将集合中的元素取出来作为变量去操作
}

// 1、创建一个集合对象
Collection collection = new ArrayList();
collection.add("hello");
collection.add("world");
// 2、使用增强for遍历集合
for(Object obj : collection){
    System.out.println(obj);
}

注意:如果集合为空将会报异常:nullpointexception空指针异常。

(六)List接口

1、List接口的方法
  • void add(E e) :向list末尾添加一个元素
  • void add(int index,E e):在list中的指定下标添加一个元素
  • Object get(int index):获取list中指定下标的元素
  • ListIterator listIterator():返回列表中的列表迭代器(按适当的顺序)。
list的listiterator迭代器
  1. 迭代器中的方法:

    • boolean hasNext() :返回true如果遍历正向列表,列表迭代器有多个元素。
  • E next():返回列表中的下一个元素,并且前进光标位置。
    • boolean hasPrevious():返回true如果遍历反向列表,列表迭代器有多个元素。
    • E previous():返回列表中的上一个元素,并向后移动光标位置。
    • int nextIndex():返回由后续调用返回的元素的索引next()
    • int previousIndex():返回由后续调用previous()返回的元素的索引。
    • void remove():从列表中删除由next()previous()返回的最后一个元素(可选操作)。
    • void set(E e):用指定的元素(可选操作)替换由next()返回的最后一个元素或previous()
    • void add(E e):将指定的元素插入列表(可选操作)。 该元素将被返回的元素之前立即插入next() ,如果有的话,那会被返回的元素之后previous() ,如果有的话。

ListIterator迭代器不但可以正向迭代还可以反向迭代和增删

注意如果在操作list时出现//ConcurrentModificationException(并发修改异常)

  • 可能发生异常的情况:在迭代器迭代元素时,用了集合去操作某个数据
  • 解决方法:迭代器在进行迭代元素时,就用迭代器去操作。集合遍历时就用集合去操作。
2、ArrayList类

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。

ArrayList 继承了 AbstractList ,并实现了 List 接口。

AarryList的方法使用

四种遍历

ArrayList list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");

//数组
Object[] arr  = list.toArray();
for(int i=0; i<arr.length;i++){
    String s = (String)arr[i];
    System.out.println(arr[i]);
}
//迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}
//列表迭代器
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()){
    System.out.println(listIterator.next());
}
//size  get()
for(int i=0; i<list.size();i++){
    String s = (String)list.get(i);
    System.out.println(arr[i]);
}
常用方法
方法 描述
add(E e) 将元素插入到指定位置的 arraylist 中
addAll(Collection c) 添加集合中的所有元素到 arraylist 中
contains(E e) 判断元素是否在 arraylist
get(int index) 通过索引值获取 arraylist 中的元素
indexOf(E e) 返回 arraylist 中元素的索引值
removeAll(Collection c) 删除存在于指定集合中的 arraylist 里的所有元素
remove(E e) 删除 arraylist 里的单个元素
size() 返回 arraylist 里元素数量
isEmpty() 判断 arraylist 是否为空
subList(int fromindex,int endindex) 截取部分 arraylist 的元素
set(int index,E e) 替换 arraylist 中指定索引的元素
sort() 对 arraylist 元素进行排序
toArray() 将 arraylist 转换为数组
lastIndexOf(E e) 返回指定元素在 arraylist 中最后一次出现的位置
containsAll(Collection c) 查看 arraylist 是否包含指定集合中的所有元素
3、LinkedList类

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。

在这里插入图片描述

以下情况使用 ArrayList :

  • 频繁访问列表中的某一个元素。
  • 只需要在列表末尾进行添加和删除元素操作。

以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
常用方法
方法 描述
public boolean add(E e) 链表末尾添加元素,返回是否成功,成功为 true,失败为 false。
public void add(int index, E element) 向指定位置插入元素。
public boolean addAll(Collection c) 将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。
public boolean addAll(int index, Collection c) 将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。
public void addFirst(E e) 元素添加到头部。
public void addLast(E e) 元素添加到尾部。
public boolean offerFirst(E e) 头部插入元素,返回是否成功,成功为 true,失败为 false。
public boolean offerLast(E e) 尾部插入元素,返回是否成功,成功为 true,失败为 false。
public void clear() 清空链表。
public E removeFirst() 删除并返回第一个元素。
public E removeLast() 删除并返回最后一个元素。
public boolean remove(Object o) 删除某一元素,返回是否成功,成功为 true,失败为 false。
public E remove(int index) 删除指定位置的元素。
public boolean contains(Object o) 判断是否含有某一元素。
public E get(int index) 返回指定位置的元素。
public E getFirst() 返回第一个元素。
public E getLast() 返回最后一个元素。
public int indexOf(Object o) 查找指定元素从前往后第一次出现的索引。
public int lastIndexOf(Object o) 查找指定元素最后一次出现的索引。
public E set(int index, E element) 设置指定位置的元素。
public Object clone() 克隆该列表。
public Iterator descendingIterator() 返回倒序迭代器。
public int size() 返回链表元素个数。
public ListIterator listIterator(int index) 返回从指定位置开始到末尾的迭代器。
public Object[] toArray() 返回一个由链表元素组成的数组。
public T[] toArray(T[] a) 返回一个由链表元素转换类型而成的数组。
4、Vector类

Vector 类实现了一个动态数组。和 ArrayList 很相似,但是两者是不同的:

  • Vector 是同步访问的。
  • Vector 包含了许多传统的方法,这些方法不属于集合框架。

Vector 主要用在事先不知道数组的大小,或者只是需要一个可以改变大小的数组的情况。

1、构造方法

第一种构造方法创建一个默认的向量,默认大小为 10:

Vector()

第二种构造方法创建指定大小的向量。

Vector(int size)

第三种构造方法创建指定大小的向量,并且增量用 incr 指定。增量表示向量每次增加的元素数目。

Vector(int size,int incr)

第四种构造方法创建一个包含集合 c 元素的向量:

Vector(Collection c)
2、特有方法
方法 描述
addElement(E obj) 将指定的组件添加到此向量的末尾,将其大小增加1。
elementAt(int index) 返回指定索引处的组件。
elements() 返回此向量的组件的枚举。
firstElement() 返回此向量的第一个组件(索引号为 0的项目)。
5、数组转集合

Arrays.asList(),此方法可以将数组转集合,但本质还是数组,所以不能操作集合改变数组的大小的方法。

List<String> list = Arrays.asList("hello","world","java");
// 不能使用add(),remove(),clear()等方法去改变集合长度
java.lang.UnsupportedOperationException
// 可以修改元素的内容使用set()方法

(七)Set接口

1、set接口

set接口是继承自Collection的子接口,特点是元素不重复,存储无序。

与List接口不同,Set集合不能包含重复的元素。

set保证里面元素的唯一性其实是靠两个方法,**equals()hashCode()**方法

往set里面添加数据的时候一般会有隐式的操作

  • 先是判断set集合中是否有与新添加数据的hashcode值一致的数据
  • 如果有,那么将再进行第二步调用equals方法再进行一次判断
去重原理

HashSet 的底层是HashMap, hashMap的底层是哈希表(数组和链表的结合)

day17_18_java中集合的详解_第2张图片

什么是哈希表呢?

JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。

自定义对象去重:

因为Set接口的实现类HashSet中,判断重复是通过hashCode(),和equals()方法来实现的。

所以自定义对象想要在用Set去重时需要重写hashCode和equals方法(idea编译器的快捷键有对自定义类的hashcode和equals方法的生成方法)

常用方法:
  • add() - 将指定的元素添加到集合中
  • addAll() - 将指定集合的所有元素添加到集合中
  • iterator() -返回一个迭代器,该迭代器可用于顺序访问集合中的元素
  • remove() - 从集合中移除指定的元素
  • removeAll() - 从存在于另一个指定集合中的集合中删除所有元素
  • keepAll() -保留集合中所有还存在于另一个指定集合中的所有元素
  • clear() - 从集合中删除所有元素
  • size() - 返回集合的长度(元素数)
  • toArray() - 返回包含集合中所有元素的数组
  • contains() - 如果集合包含指定的元素,则返回true
  • containsAll() - 如果集合包含指定集合的所有元素,则返回true
  • hashCode() -返回哈希码值(集合中元素的地址)
2、HashSet类

HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的,无序的集合。

HashSet 允许有 null 值。

HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。

在Java中,如果我们必须随机访问元素,则通常使用HashSet。 这是因为哈希表中的元素是使用哈希码访问的。

了解HashMap:

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。是无序的

HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。

hashMap的数据结构:

HashMap的数据结构为 数组+链表

数组的特点:查询效率高,插入,删除效率低。

链表的特点:查询效率低,插入删除效率高。

在HashMap底层使用数组加链表的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。

3、LinkedHashSet类

底层数据结构:哈希表+链表

保证了唯一性,

链表保存有序(存储和取出是一致)

LinkedHashSet<String> hs = new LinkedHashSet<String>();
hs.add("hello");
hs.add("world");
hs.add("java");
hs.add("hello");

for(String str :hs){
    System.out.println(str);
}
4、TreeSet类

Java集合框架的TreeSet类提供树数据结构的功能。

特点:排序 和唯一

排序 :

  • 自然排序(就是升序)
  • 比较器排序
// 这里介绍自然排序
public static void main(String[] args) {
    TreeSet<Integer> treeSet = new TreeSet<Integer>();
    treeSet.add(66);
    treeSet.add(18);
    treeSet.add(12);
    treeSet.add(66);
    treeSet.add(77);

    for(Integer integer: treeSet){
        System.out.print(integer);
    }
}
// 输出:12 18 66 77

Map体系

day17_18_java中集合的详解_第3张图片

概述

现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map接口。

我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同,如下图。

  • Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
  • Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
  • Collection中的集合称为单列集合,Map中的集合称为双列集合。
  • 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。

Map接口中的常用方法

  1. public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  2. public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  3. public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  4. boolean containsKey(Object key) 判断集合中是否包含指定的键。
  5. public Set keySet(): 获取Map集合中所有的键,存储到Set集合中。
  6. public Set> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
注:Map不允许键重复,如果有重复的键,Map会自动将已存在的值覆盖掉
HashMap 常用方法列表如下:
方法 描述
clear() 删除 hashMap 中的所有键/值对
clone() 复制一份 hashMap
isEmpty() 判断 hashMap 是否为空
size() 计算 hashMap 中键/值对的数量
put() 将键/值对添加到 hashMap 中
putAll() 将所有键/值对添加到 hashMap 中
putIfAbsent() 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。
remove() 删除 hashMap 中指定键 key 的映射关系
containsKey() 检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue() 检查 hashMap 中是否存在指定的 value 对应的映射关系。
replace() 替换 hashMap 中是指定的 key 对应的 value。
replaceAll() 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。
get() 获取指定 key 对应对 value
forEach() 对 hashMap 中的每个映射执行指定的操作。
entrySet() 返回 hashMap 中所有映射项的集合集合视图。
keySet() 返回 hashMap 中所有 key 组成的集合视图。
values() 返回 hashMap 中存在的所有 value 值。

面试题

第一题:List a=new ArrayList() 和 ArrayList a =new ArrayList()的区别?

List list = new ArrayList(); 不能使用ArrayList特有的方法和属性

这句创建了一个 ArrayList 的对象后把上溯到了 List。此时它是一个List对象了,有些ArrayList 有但是 List 没有的属性和方法,它就不能再用了。

ArrayList list=new ArrayList();需要用到 ArrayList 独有的方法的时候使用

ArrayList创建对象则保留了ArrayList 的所有属性。

实例代码如下:

List list = new ArrayList();

ArrayList arrayList = new ArrayList();

list.trimToSize(); //错误,没有该方法。

arrayList.trimToSize(); //ArrayList里有该方法。

第二题:说一下ArrayList的扩容机制

(1)带初始容量参数的构造函数,用户可以自己定义容量

transient Object[] elementData;
private static final Object[] EMPTY_ELEMENTDATA = {};

// 在创建ArrayList时需要传递初始化容量的构造方法
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

(2)默认构造函数,使用初始容量10构造一个空列表(无参数构造)

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

(3)构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

首先获取数组的旧容量,然后计算新容量的值,计算使用位运算,将其扩容至原来的1.5倍。
得到新容量的值后,校验扩容后的容量是否大于需要的容量。如果不够,则把最小需要容量当作扩容后的新容量并确保扩容后的容量不超过数组能设置的最大大小值
最后将老数组的数据复制到新的数组中

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// 扩容方法
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// 超过最大容量的处理
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

第三题:Vector和ArrayList以及LinkedList区别和联系,以及分别的应用场景?

Vector的底层的实现其实是一个数组,是线程安全的实现类,方法都有synchronized

LinkedList的底层其实是一个双向链表,每一个对象都是一个Node节点,Node就是一个静态内部类,它是**线程不安全的,**所有的方法都没有加锁或者进行同步

这里先简单介绍一下,下面会对ArrayList的扩容机制进行分析

ArrayList是线程不安全的,如果不指定它的初始容量,那么它的初始容量是0,当第一次进行添加操作的时候它的容量将扩容为10

三种集合的使用场景

  1. Vector很少用,有其他线程安全的List集合
  2. 如果需要大量的添加和删除则可以选择LinkedList 原因是:它查询的时候需要遍历整个链表,插入和删除的时候无需移动节点
  3. 如果需要大量的查询和修改则可以选择ArrayList 原因:底层为数组,删除和插入需要移动其他元素,查询的时候根据下标来查

第四题:我们想要使用线程安全的List集合,你有什么办法?

1:可以使用Vector

2.自己重写类似于ArrayList的但是线程安全的集合

3.可以使用**Collections(工具类)**中的方法,将ArrayList变成一个线程安全的集合

4.可以使用java.util.concurrent包下的CopyOnWriteArrayList,它是线程安全的

第五题:那你说说CopyOnWriteArrayList是怎么实现线程安全的?

它是juc包下的,专门用于并发编程的,他的设计思想是:读写分离,最终一致,写时复制

它不能指定容量,初始容量是0.它底层也是一个数组,集合有多大,底层数组就有多大,不会有多余的空间

CopyOnWriteArrayList的缺点

底层是数组,删除插入的效率不高,写的时候需要复制,占用内存,浪费空间,如果集合足够大的时候容易触发GC

数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。【当执行add或remove操作没完成时,get获取的仍然是旧数组的元素】。CopyOnWriteArrayList读取时不加锁只是写入和删除时加锁

应用场景:读操作远大于写操作的时候

CopyOnWriteArrayList和Collections.synchronizedList区别

CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。

最终一致,写时复制**

它不能指定容量,初始容量是0.它底层也是一个数组,集合有多大,底层数组就有多大,不会有多余的空间

CopyOnWriteArrayList的缺点

底层是数组,删除插入的效率不高,写的时候需要复制,占用内存,浪费空间,如果集合足够大的时候容易触发GC

数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。【当执行add或remove操作没完成时,get获取的仍然是旧数组的元素】。CopyOnWriteArrayList读取时不加锁只是写入和删除时加锁

应用场景:读操作远大于写操作的时候

CopyOnWriteArrayList和Collections.synchronizedList区别

CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。

你可能感兴趣的:(java,开发语言,idea,数据结构,哈希算法,散列表,链表)