1.ArrayList数据结构
- 底层数据结构就是一个元素的类型为Object类型的数组
ArrayList的特性:
- ArrayList是否允许空:是
- ArrayList是否允许重复数据:是
- ArrayList是否有序:是
- ArrayList是否线程安全:否
2.ArrayList线程安全性
添加元素时的步骤:
- 先在object [size] 的位置上存放需所添加的元素
- 将 size 的值增加1
假设在多线程的情况下操作ArrayList,1号线程添加元素(只进行了步骤1,即把该元素放在了object [size] 的位置上),因为网络或其他原因没有进行步骤2.
2号线程添加元素,因为1号线程size值并没有发生改变,所以2号线程所添加的元素同样放在了object [size] 的位置上。
问题修复后,1号线程和2号线程继续运行,size值发生了两次+1,即线程不安全。
解决办法:
- 使用synchronized关键字
- 用Collections类中的静态方法synchronizedList()对ArrayList进行调用
3.ArrayList成员变量
// 序列版本号
private static final long serialVersionUID = 8683452581122892189L;
// 默认容量大小(当ArrayList的构造方法中没有显示指出ArrayList的数组长度时使用默认容量)
private static final int DEFAULT_CAPACITY = 10;
// 空数组(当ArrayList的构造方法中显示指出ArrayList的数组长度为0时,类内部将EMPTY_ELEMENTDATA 这个空对象数组赋给elemetData数组。)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 用于保存ArrayList中数据的数组(transient,意味着在序列化的时候此字段是不会被序列化的)
private transient Object[] elementData;
// ArrayList中所包含元素的个数,size是按照调用add、remove方法的次数进行自增或者自减的,所以add了一个null进入ArrayList,size也会加1
private int size;
4.ArrayList的add()方法
// 将指定的元素添加到ArrayList的尾部
//1.判断是否需要扩容
//2.然后将元素添加到elementData数组的指定位置
//3.最后将集合中实际的元素个数加1
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
指定位置的新增元素:
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
核心即在 System.arraycopy ,复制数组然后向指定位置后移一位。若数据大的情况下复制数组会导致效率低下
5.ArrayList删除元素
删除方式:
- 按照下标删除
- 按照元素删除,这会删除ArrayList中与指定要删除的元素匹配的第一个元素
两种方法所调用的代码相同:
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
简单理解:
把指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一个位置
最后一个位置的元素指定为null,这样让gc可以去回收它
6.ArrayList扩容机制
ArrayList的扩容主要发生在向ArrayList集合中添加元素的时候
/**
步骤一:
确保添加元素成功的最小集合容量minCapacity的值
参数:size+1。如果集合添加元素成功后,集合中的实际元素个数
先判断elementData是否为默认的空数组,如果是,minCapacity为minCapacity与集合默认容量大小中的较大值
*/
ensureCapacityInternal(size + 1); // Increments modCount!!
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
}
ensureExplicitCapacity(minCapacity);
}
/**
步骤二:
确定集合为了确保添加元素成功是否需要对现有的元素数组进行扩容
首先将结构性修改计数器加一
然后判断minCapacity与当前元素数组的长度的大小,如果minCapacity比当前元素数组的长度的大小大的时候需要扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
// 结构性修改加1
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
步骤三:
对现有的元素数组进行扩容
参数:minCapacity。集合为了确保添加元素成功的最小容量
首先将原元素数组的长度增大1.5倍
然后对扩容后的容量与minCapacity进行比较
---新容量小于minCapacity,则将新容量设为minCapacity
---新容量大于minCapacity,则指定新容量
最后将旧数组拷贝到扩容后的新数组中
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // 旧容量
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
newCapacity = hugeCapacity(minCapacity); // 指定新容量
// 拷贝扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
7.ArrayList优缺点
- ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
- 删除/插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
- ArrayList比较适合顺序添加、随机访问的场景