ArrayList集合底层数据结构
List
接口的可调节大小的数组实现。- 特点:增删慢,查询快。
ArrayList实现的三个接口
它实现了三个接口,相同点都是空接口,作为一个标记出现。
Serializable标识接口
类的序列化由实现这个接口的类启动。不实现此接口的类不会使用任何状态序列化和反序列化。
序列化:将对象的数据写入到文件
反序列化:将文件中的对象的数据读取出来
Cloneable标识接口
只有实现这个 “可克隆” 接口,然后在类中重写 Object 中的 clone()
方法,后面通过类调用 clone 方法才能克隆成功。如果不实现这个接口,则会抛出 克隆不被支持 的异常。
RandomAccess标识接口
只要实现了这个接口,就能支持快速随机访问。
- ArrayList 实现了 RandomAccess 接口,使用 for 循环通过索引遍历。
- 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底层链表(内存空间不连续)。
访问效率:ArrayList
比LinkedList
在随机访问的时候效率要高,因为 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 容器是一种 “读写分离” 的思想,读和写是不同的容器。
缺点是每次写入都要复制一个新的数组,会造成内存浪费,垃圾回收频繁等,适合读多写少的场景。