前言
文章仅是笔者个人的学习笔记,存在一些只有笔者个人能看到的用词或者描述,如果有不明确的地方,欢迎留言理性讨论。
一、概述
- Arraylist不是线程安全的,但是java有提供同步创建的方法:
Collections.synchronizedList(new ArrayList(...));
,关键点看起来是RandomAccess
这个接口, - 默认初始容量是10,在添加大容易之前,可以使用
ensureCapacity
来提前确定需要的容量及扩容,避免递增式的扩容和拷贝,(从1直接到N,避免1、2、3、、、N)。 - 可以存 null
二、源码分析
- 底层使用数组:
private transient Object[] elementData;
,注意这里的transient
,如果用它声明一个实例变量,当对象存储时,它的值不需要维持(也就是不需要做序列化)。- 引申1:为什么要声明这个关键字呢?
- 这里要 联系 Arraylist 的扩容机制:数组的容量是按一定系数扩容的,比如当前容量是20,但是可能实际只有 15 个item,如果直接把整个数组来序列化,那么就浪费了 20-15=5 个元素的空间占用。
- 因此,它实际是用一个 for 循环,按实际item个数,来完成序列化的。
- 引申2:申明了之后又是怎么实现序列化的呢?
- 这个可以看arraylist做序列化时候的源码来解答:
- java通过
Serializable
实现的序列化,本质上是通过输入输出流来完成的。 - 虽然底层的数组声明了
transient
,但是通过下面这个方法可以看出来,每个Item被单独取出来,作为形参传入,从而实现序列化,所以数组本身声明了transient
也没有关系了。
- 引申1:为什么要声明这个关键字呢?
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
//这里是重点,注意是 i < size
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
- **构造方法:**没什么特别好说的,可以传入初始容量,初始collection集合,或者直接用默认的。
- **增加:**共有5个方法实现增加item。
- add(E e)
- add(int index, E element)
- 插入特定位置,需要对后续元素做整体后移处理。
- 显然这个操作是非常耗费资源的,所以arraylist不适合做插入操作。
- addAll(Collection<? extends E> c)
- addAll(int index, Collection<? extends E> c)
- set(int index, E element)
- 删除:
- remove(int index)
- 和上面的插入同理,删除特定位置的元素,后续元素整体要做前移处理
- remove(Object o)
- removeRange(int fromIndex, int toIndex)
- removeAll()
- 这个是父类实现的方法,Arraylist本身未重新实现
- remove(int index)
- 查找:
- E get(int index)
- 直接通过下标访问数组即可,这个速度无疑是非常快的。
- 所以 Arraylist 在随机访问上的效率是很高的,因为就是数组。
- E get(int index)
- 扩容:
- 实际使用的方法就是
ensureCapacity(int minCapacity)
,这里还是放下源码吧:- 可以看出,其实就是换一个更大的数组,去存 Arraylist 中的数据,这过程中无疑也涉及数组的拷贝
- 所以这个过程的开销,可以说 懂得都懂。
- 每次扩容是 1.5 倍,为啥是1.5倍呢,网上说法是这个系数大小刚刚好,不会太大,也不会太小。我只能说:(待确认
- Arraylist 也提供了数组对齐的方法,就是把数组大小和实际元素数量对齐:
trimToSize
,当然,其实就是拷贝一下。
- 实际使用的方法就是
public void ensureCapacity(int minCapacity) {
//修改计时器
modCount++;
//ArrayList容量大小
int oldCapacity = elementData.length;
/*
* 若当前需要的长度大于当前数组的长度时,进行扩容操作
*/
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
//计算新的容量大小,为当前容量的1.5倍
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
//数组拷贝,生成新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
}