1. ArrayList的底层是什么?

ArrayList的底层实现是一个存储了Object类型的可扩容的动态数组.

2. 动态数组与一般数组有什么区别?

与一般数组相比,动态数组(ArrayList)的容量可以动态的增长.

4. 既然是底层是数组,那么数组就会有大小限制,ArrayList默认大小是多少?

通过查看jdk源码,得知:

每次我们调用初始化一个空白的集合ArrayList,它的底层数组其实是空的.但是当我们第一次为ArrayList添加元素的时候,底层数组扩容到了10.

源码如下

DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的数组

5. ArrayList的add方法底层是怎样实现的?

当我们通过ArrayList调用add()方法添加元素的时候,其实底层一共需要调用了4个方法(如果需要扩容的话,则调用5个方法。Math.max()和Arrays.copyOf()不计入其内。)

我们使用ArrayList<Integer> arrayList = new ArrayList<>();

  • 构造的是一个空的ArrayList,此时ArrayList底层的elementData是一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA(空数组)

当我们调用add方法增加元素的时候

  • 首先是调用了ensureCapacityInternal,然后参数size+1(size代表我们集合中元素的个数)
  • 接着调用calculateCapacity(计算容量),通过判断elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA(也就是判断我们在初始化ArrayList的时候,是不是使用的是默认的构造器)
    • 如果是初始化的时候使用的是默认构造器,接着就返回DEFAULT_CAPACITY和上面我们传的参数中的最大值,而DEFAULT_CAPACITY在源码就是我们常说的初始化容量10,此时该方法返回DEFAULT_CAPACITY
    • 如果不是使用默认构造器初始化的话,则直接返回我们传入的参数(也就是我们一开始的size+1)
  • 到目前为止我们的底层数组elementData的容量仍然是0,并没有进行扩容
    • 接着调用ensureExplicitCapacity()方法,在该方法内部首先调用modCount++表明此刻在修改ArrayList.
    • 接着判断一下:如果我们传进来的参数minCapacity(size+1)大于我们当前底层数组elementData的长度(此时为0),则需要扩容,接着调用了grow()方法进行了扩容。
  • 在grow()方法中
    • 首先通过将element的容量扩容至原来的1.5倍(有时是小于1.5倍)(同时这里也是ArrayList的自动扩容机制)
    • 接着判断如果扩容后的容量仍旧小于minCapacity,就把minCapacity作为新容量,否则什么也不做
    • 接着判断如果新容量大于最大数组容量的话,继续调用hugeCapacity()进行扩容(一般调用这个的话会爬出异常)
    • 最后通过Arrays.copyOf()进行数组的扩容。

总结:在进行 add 操作时先判断下标是否越界,是否需要扩容,如果需要扩容,就复制数组,然后设置对应的下标元素值

扩容:默认扩容一半,如果扩容一半不够的话,就用目标的size作为扩容后的容量

6. ArrayList效率怎么样?

ArrayList不是线程安全的,所以效率比较高 ,但是只能用于单线程的环境中

7. ArrayList主要继承哪些类实现了哪些接口?

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

RandomAccess的意思是其拥有快速访问的能力,ArrayList可以以 O(1)[^1]的时间复杂度去根据下标访问元素。由于ArrayList底层机构是数组,所以它占据了一块连续的内存空间,其长度就是数组的大小,因此它也有数组的缺点,在空间效率不高,但是也有它的优点,就是查询速度快,时间效率较快

8. ArrayList的常量与变量有哪些?

 // 序列ID
private static final long serialVersionUID = 8683452581122892189L;

// ArrayList默认的初始容量大小
private static final int DEFAULT_CAPACITY = 10;

// 空对象数组,用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};

// 空对象数组,如果使用默认的构造函数创建,则默认对象内容是该值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存放当前数据,不参与序列化
transient Object[] elementData; // non-private to simplify nested class access

// list大小
private int size; 

当集合中的元素超出数组规定的长度时,数组就会进行扩容操作,扩容操作就是ArrayList存储操作缓慢的原因,尤其是当数据量较大的时候,每次扩容消耗的时间会越来越多

9. ArrayList是线程安全的吗?

不,ArrayList不是线程安全的,而且ArrayList允许元素为null

10. ArrayList如何实现线程安全?

  • 可以使用Collections.synchronizedList(List list) 函数返回一个线程安全的 ArrayList 集合:

List list = Collections.synchronizedList(new ArrayList());

  • 使用 concurrent 并发包下的CopyOnWriteArrayList():

List list = CopyOnWriteArrayList();

最后修改日期:2020-07-13

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。