【知识总结】ArrayList

ArrayList集合底层数据结构

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

ArrayList实现的三个接口

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

【知识总结】ArrayList_第1张图片

  • Serializable标识接口

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

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

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

  • Cloneable标识接口

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

  • RandomAccess标识接口

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

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

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

ArrayList构造方法

构造方法 描述
ArrayList() 构造一个初始容量为 10 的空列表
ArrayList(int initialCapacity) 构造一个指定初始容量的空列表
ArrayList(Collection 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 容器是一种 “读写分离” 的思想,读和写是不同的容器。

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

你可能感兴趣的:(java集合)