【知识总结】ArrayList

ArrayList集合底层数据结构

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

ArrayList实现的三个接口

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

  • Serializable标识接口

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

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

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

  • Cloneable标识接口

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

  • RandomAccess标识接口

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

  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 容器是一种 “读写分离” 的思想,读和写是不一样的容器。

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

相关文章
相关标签/搜索