目录
1. ArrayList底层实现
底层是通过数组实现的,所以查找元素快,但增删元素比较慢。ArrayList的容
量是动态的,在每次添加元素的时候都会校验索引是否超出容器的长度,如果超出,
就把当前数组的元素复制一份到新数组中,并且新数组的长度是原数组的1.5倍。
2. 核心属性
/**
* 默认数组初始长度
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 在调用有参构造器时,存储对象指向该空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 默认长度的空数组,在调用无参构造器时,存储对象指向该空数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 实际存储数据的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 实际添加的元素个数
*/
private int size;
3. 构造方法
/**
*无参构造器,elementData指向空数组(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 有参构造器,当传入初始长度时,会创建一个该长度的Object数组给 elementData
* 如果长度为0,则elementData 指向空数组(EMPTY_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);
}
}
/**
*
* 传入一个集合元素,并将该集合元素转为数组,给elementData
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) { //如果数组长度不为0
if (elementData.getClass() != Object[].class) //如果不是Object[],就将原数组的元素复制一份到新的Object[]
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA; //数组长度为0时
}
}
4. 常用方法
4.1 add(E)
public boolean add(E e) {
//第1次添加数据:ensureCapacityInternal(0 + 1);
//第2次添加数据:ensureCapacityInternal(1 + 1);
//第11次添加数据:ensureCapacityInternal(10 + 1);
ensureCapacityInternal(size + 1);
elementData[size++] = e;
//第1次添加完成后 elementData={1, , , , , , , , , }
//第2次添加完成后 elementData={1,2, , , , , , , , }
//第11次添加完成后 elementData={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, , , , }
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//第1次添加数据:ensureExplicitCapatity(calculateCapacity({},1)); => ensureExplicitCapatity(10)
//第2次添加数据:ensureExplicitCapatity(calculateCapacity({1, , , , , , , , , },2)); => ensureExplicitCapatity(2)
//第11次添加数据:ensureExplicitCapatity(calculateCapacity({1,2,3,4,5,6,7,8,9,10 },11)); => ensureExplicitCapatity(11)
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//第1次添加数据:{} == {} 成立,return Math.max(10, 1); 返回10
//第2次添加数据:{1, , , , , , , , , } == {} 不成立,return 2;
//第11次添加数据:return 11;
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//第1次添加数据:10 - 0 > 0 触发grow(10)
//第2次添加数据:2 - 10 < 0,不触grow
//第11次添加数据:11 - 10 > 0 触发grow(11)
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length; //记录当前数组长度
int newCapacity = oldCapacity + (oldCapacity >> 1); //新数组长度 = 当前数组长度+当前数组长度右移1位,结果为原长度的1.5倍
if (newCapacity - minCapacity < 0) //如果新数组长度小于入参
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //新数组长度大于数组最大长度
newCapacity = hugeCapacity(minCapacity);
// 将原数组复制一份,长度为newCapacity,并返回新数组
//第1次添加数据:Arrays.copyOf({}, 10) => new Object[10];
//第11次添加数据:Arrays.copyOf(elementData, 15) => {1,2,3,4,5,6,7,8,9,10, , , , , }
elementData = Arrays.copyOf(elementData, newCapacity);
}
4.2 add(index, E)
public void add(int index, E element) {
rangeCheckForAdd(index); //判断索引是否超出已有元素的长度
ensureCapacityInternal(size + 1); // 判断是否需要扩容
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
假设elementData = {1,2,3,4,5,6, , , , }
实际元素个数:size = 6;
需要插入的位置:index = 2,即数组元素3所在的位置
需要移动的元素数量:numMoved = size - index = 4,即3,4,5,6
执行 System.arraycopy(elementData, index, elementData, index + 1, size - index);
结果:
elementData = {1,2, ,3,4,5,6, , , }
4.3 remove(index)
public E remove(int index) {
rangeCheck(index); //判断索引是否超出元素个数
modCount++;
E oldValue = elementData(index); //获取要删除的元素
int numMoved = size - index - 1; //计算从被删除的元素后面还有几个元素
if (numMoved > 0)
//将源数组(第一个参数)的第 index + 1 位置的 numMoved个元素,移动至目标数组(第三个参数)的 第index 位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
假设elementData = {1,2,3,4,5,6, , , , }
实际元素个数:size=6
需要移除元素的索引:index = 2
需要移动的元素个数:numMoved = 6 - 2 - 1 = 3
执行System.arraycopy(elementData, index+1, elementData, index, numMoved);
结果:
执行前的elementData = {1,2,3,4,5,6, , , , };
执行后的elementData = {1,2,4,5,6,6, , , , };
elementData[--size] = null;
最终elementData = {1,2,4,5,6, , , , , };
4.4 get(index)
public E get(int index) {
rangeCheck(index); //检查index是否超出实际元素的长度
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index]; //从数组的对应位置返回元素
}
5. FailFast机制
相关属性
protected transient int modCount = 0;
modCount是ArrayList 的父类AbstractList<E> 中的属性,当执行添加、
删除操作时,该属性会自增(set(index, E)方法并不会导致自增)。当使用Iter
ator 迭代时,会把modCount赋值给迭代器中的expectedModCount属性。
当多个线程共享一个list 时,每次调用next() 都会检查modCount和
expectedModCount的值是否相等,如果不相等,则表明有线程对list执行了添加、
删除操作。
快速失败机制,java集合类为了应对并发访问,在集合迭代过程中,内部结构
发生变化的一种防护措施。该错误检查机制为这种可能发生的错误抛出
java.util.ConcurrentModificationException
也就是说在使用迭代器进行迭代时,是不允许对集合进行修改操作的。
总结
ArrayList通过Object[] 存放数据,size是数组中实际元素的个数。所有的
操作都是围绕着数组的扩容、赋值来进行的。
添加元素时,会对当前数组的长度进行判断,当满足扩容条件时,就会把当前
数组的元素复制一份到新的数组中,并且长度是原来的1.5倍。
在指定index添加元素,其实就是先把该位置及后面的元素都往后挪一位,然
后再给这个index赋值。
删除指定index的元素,其实就是把该index后面的元素都往前挪一位,然后把
最后一位设为null。
快速失败机制在并发访问共享的集合时提供了检查机制,但如果不使用迭代器
该机制也无法正常生效(使用foreach 可以生效,因为foreach本质是对迭代器的
调用,下面给出使用foreach的反编译代码)。
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: ThreadRead.java
package org;
import java.util.Iterator;
import java.util.List;
public class ThreadRead extends Thread
{
private List list;
public ThreadRead(List list)
{
this.list = list;
}
public void run()
{
//通过反编译工具可以看到,实际上foreach调用的是Iterator
for (Iterator iterator = list.iterator(); iterator.hasNext();)
{
Object obj = iterator.next();
try
{
Thread.sleep(5L);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
目前整理的就这么多,后期有新的理解会回来补充。
水平有限,欢迎各位吐槽评论,谢谢。