ArrayList集合底层数据结构

  • List 接口的可调节大小的数组实现。
  • 特点:增删慢,查询快。

ArrayList实现的三个接口

它实现了三个接口,相同点都是空接口,作为一个标记出现。

  • Serializable标识接口

类的序列化由实现这个接口的类启动。不实现此接口的类不会使用任何状态序列化和反序列化。

序列化:将对象的数据写入到文件

反序列化:将文件中的对象的数据读取出来

  • Cloneable标识接口

只有实现这个 “可克隆” 接口,然后在类中重写 Object 中的 clone() 方法,后面通过类调用 clone 方法才能克隆成功。如果不实现这个接口,则会抛出 克隆不被支持 的异常。

  • RandomAccess标识接口

只要实现了这个接口,就能支持快速随机访问。

  1. ArrayList 实现了 RandomAccess 接口,使用 for 循环通过索引遍历。
  2. LinkedList 没有实现这个接口,默认使用 iterator 进行遍历。

通过测试,他们这样各自的实现是最高效的,所以实现 RandomAccess 接口也需要根据实际场景需求来进行。

ArrayList构造方法

构造方法描述
ArrayList()构造一个初始容量为 10 的空列表
ArrayList(int initialCapacity)构造一个指定初始容量的空列表
ArrayList(Collection<? extends E> c)构造一个包含指定集合元素的列表(参数就是一个集合)

各个方法的时间复杂度

方法时间复杂度
add(E e)添加元素到末尾,平均时间复杂度为O(1)
add(int index, E e)添加元素到指定位置,平均时间复杂度为O(n)
get(int index)获取指定索引位置的元素,时间复杂度为O(1)
remove(int index)删除指定索引位置的元素,时间复杂度为O(n)
remove(Object o)删除指定元素值的元素,时间复杂度为O(n)

Add方法源码分析

public boolean add(E e) {
    //调用方法对内部容量进行检查
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    //如果 elementData 数组为空
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //如果容量不足10,扩容至默认容量10(第一次扩容的参数,此时数组还为null)
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);    //将上面计算出的容量传递,继续检查
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;        //记录修改的次数++ (主要用于迭代器中)
    
    //当前最小容量 - 数组长度 > 0(判断是否溢出)
    if (minCapacity - elementData.length > 0)
        //将第一次计算出来的容量传给 “核心扩容方法”
        grow(minCapacity);    
}
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;                //记录旧的数组容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);        //新容量扩容1.5倍
    
    //如果新容量小于最小容量,则将最小容量的值赋给 新容量(如果是第一次调用add方法必然小于)
    if (newCapacity - minCapacity < 0)                    
        newCapacity = minCapacity;
    
    //如果newCapacity大于数组最大容量(默认是int类型最大值)
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);    //创建超大数组,Integer.MAX_VALUE
    
    //创建一个新数组,将新数组的地址赋值给elementData
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 如果容量不足10,第一次扩容至10,以后每次都是原容量的 1.5 倍。

ArrayList 频繁扩容导致添加性能下降的解决办法

创建集合的时候指定足够大的容量。

ArrayList线程安全吗?

ArrayList 不是线程安全的,例如当我们 add 操作时,elementData[size++] 这个操作并不是原子操作。

ArrayList 中 elementData 为什么使用 transient 修饰?

transient 关键字修饰的对象不会被序列化。因为 elementData 是 ArrayList 的数据域,由于 ArrayList 是基于动态数组实现的, elementData 的容量通常大于实际存储元素的容量,所以只需发送有实际值的数组元素即可。

ArrayList 和 LinkedList 的区别

数据结构:ArrayList底层是数组(内存里是连续的空间),LinkedList底层链表(内存空间不连续)。

访问效率:ArrayListLinkedList 在随机访问的时候效率要高,因为 LinkedList 线性的数据存储方式,所以需要移动指针从前往后依次查找。

增删效率:LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。LinkedList只需要改变前后的指针就可以了。

ArrayList和Vector的区别

ArrayList 和 Vector的底层都是基于数组实现的动态扩容。他们的区别在于:

线程安全:Vector 使用了 Synchronized 来实现线程同步,而 ArrayList 是线程不安全的。

性能:ArrayList 在性能方面优于 Vector。

扩容:Vector 扩容每次扩容 2 倍,而 ArrayList 扩容 1.5 倍。

ArrayList 相应的线程安全容器

ArrayList和LinkedList的区别和原理

  • 使用 Vector,方法上添加了 Synchronized 来实现线程同步,但已经不推荐了。
  • 使用集合工具类的 Collections.synchronizedList() 方法。
  • 使用 CopyOnWriteArrayList 类创建集合。

CopyOnWriteArray介绍

CopyOnWriteArray 容器是一个 写时复制 的容器。

往一个容器添加元素的时候,不直接往当前容器的 Object[] 添加,而是先将当前容器 Object[] 进行复制,复制出一个新的容器。然后向新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。

这样做的好处是,可以对 CopyOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以 CopyOnWrite 容器是一种 “读写分离” 的思想,读和写是不同的容器。

缺点是每次写入都要复制一个新的数组,会造成内存浪费,垃圾回收频繁等,适合读多写少的场景。


为什么算法这么难
16 声望3 粉丝

我想要清净一下~