初始化
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
初始化目的是为了初始化底层的elementData,但是无参构造会将elementData初始化为一个空数组,当插入,扩容会按默认值重新初始化数组,有参数构造,则会根据次参数来构造数组,这样的做法显然是为了按需分配避免浪费
插入
/** 在元素序列尾部插入 */
public boolean add(E e) {
// 1. 检测是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 2. 将新元素插入序列尾部
elementData[size++] = e;
return true;
}
/** 在元素序列 index 位置处插入 */
public void add(int index, E element) {
rangeCheckForAdd(index);
// 1. 检测是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
// 2. 将 index 及其之后的所有元素都向后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 3. 将新元素插入至 index 处
elementData[index] = element;
size++;
}
- 直接插入(默认插入到尾部)
- 检查数组是否有足够的空间
-
将新元素插入至尾部
- 指定位置插入
- 检测是不是有足够的空间
- 将index及其后面的所有元素退因为
- 将新元素插入
注意 可以看到这个操作时间复杂度为O(N),平凡的一定元素坑定为导致效率的问题,所以日常开发中最好别用特别是元素较多的时候
扩容
接下来就讲讲扩容吧
扩容的入口:
ensureCapacityInternal 就是扩容的入口,在插入前我们得检查是否需要扩容从而得到了他。
/** 扩容的入口方法 */
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/** 计算最小容量 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/** 扩容的核心方法 */
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// newCapacity = oldCapacity + oldCapacity / 2 = oldCapacity * 1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果最小容量超过 MAX_ARRAY_SIZE,则将数组容量扩容至 Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
em....比较简单就不讲了,最关键查看到put时会查看这个时候其表elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA如果是,才开始初始化数组。
删除
因为删除没有无参的方法,所以难以避免的就是造成了元素的移动。
/** 删除指定位置的元素 */
public E remove(int index) {
rangeCheck(index);
modCount++;
// 返回被删除的元素值
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
// 将 index + 1 及之后的元素向前移动一位,覆盖被删除值
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最后一个元素置空,并将 size 值减1
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
E elementData(int index) {
return (E) elementData[index];
}
/** 删除指定元素,若元素重复,则只删除下标最小的元素 */
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 遍历数组,查找要删除元素的位置
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/** 快速删除,不做边界检查,也不返回删除的元素值 */
private void fastRemove(int index) {
modCount++;
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
}
先来看根据index判断是不是要进行删除。
- 获取索引位置的元素值
- 将index+1之后的的元素向前移动一位
- 最后将最后一个元素置空,size也减去一个1
- 然后 返回被删除的值。
如果是对象
- 判断是不是传入对象是不是为空,如果是就调用fastRemove删除value为空的
-
如果不是就删除,就遍历找到最小的所以同样调用fastRemove进行删除。
缩容
因为Arraylist的变化超大,扩容后删除会产生大量空闲空间,这个时候就需要锁绒了
/** 将数组容量缩小至元素数量 */
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
遍历
快速失败机制
当遇到并发修改的时候,的迭代器会快速失败,抛出异常ConcurrentModificationException。
关于遍历时删除
这个得注意,阿里巴巴 Java 开发手册里也有所提及。这里引用一下:
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
List a = new ArrayList();
a.add("1");
a.add("2");
for (String temp : a) {
System.out.println(temp);
if("1".equals(temp)){
a.remove(temp);
}
}
}
eg代码:执行的逻辑看不出来问题,因为问题“隐藏”得比较深,我们把次打印到控制台发下只有1没有2,为啥捏
先来看看转换的另外一个代码吧:
List a = new ArrayList<>();
a.add("1");
a.add("2");
Iterator it = a.iterator();
while (it.hasNext()) {
String temp = it.next();
System.out.println("temp: " + temp);
if("1".equals(temp)){
a.remove(temp);
}
}
这个时候,我们再去分析一下 ArrayList 的迭代器源码就能找出原因。