JDK版本:1.8

我们进入ArrayList类查看类结构如下:

类的结构已经明确,我们以下内容就分为两个部分,介绍构造函数和方法
构造函数
我们先看这个空构造
List<String> list = new ArrayList<String>();
//定义一个空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//定义数组中的元素 transient 表明该变量不支持序列化
transient Object[] elementData;
/**
* Constructs an empty list with an initial capacity of ten.
上面的英文翻译个人认为不对,上来的时候DEFAULTCAPACITY_EMPTY_ELEMENTDATA只是一个空数组,并不涉及到数组有多大
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList内部是由数组实现,elementData这个object类型的数组即为其存储的数据结构
再看这个有参构造
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);
}
}
传入参数initialCapacity大于0时就将elementData初始化为一个initialCapacity大小的数组
传入参数initialCapacity等于0时和无参时保持一致
传入参数initialCapacity小于零时抛出IllegalArgumentException异常
构造函数先看到这里...比较简单
常用方法
size()
//数组中包含的元素多少
private int size;
public int size() {
return size;
}
当然size的数量肯定是在add或者remove时动态修改的
add()
/**
* 向数组的末尾添加一个元素
*/
public boolean add(E e) {
//检测是否需要并扩容 说白了就是添加一个元素之前 看看容量够不够
//添加新元素是比对容器是不是够大 你看它用size+1 多线程就会出现问题
// size的值在其它线程中可变
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//判断元素是否是首次添加
/**
* 默认扩容大小.
*/
private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
//判断elementData 中是否包含数据 也意为是否为首次添加
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//首次初始化数组大小时,默认给10 找不到为啥要比一下 首次添加元素 minCapacity肯定为1
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//此处的minCapacity为目标容量
ensureExplicitCapacity(minCapacity);
}
首次添加元素嘛,首次的话也不会直接初始化容量为1的数组 而是直接来个大小为10的数组
//数组的修改次数
protected transient int modCount = 0;
//判断容器本身容量还够不够
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 容器本身容量和目标容量做对比 判断是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
目标容量和本身容量作比较,如果本身容量不够了 就扩容
/**
* 扩容
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// 本人容量 首次为0
int oldCapacity = elementData.length;
// 扩容原则是 老容量的一半 7的一半是3
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 扩容完后的值能不能够用
if (newCapacity - minCapacity < 0)
// 不够用 直接用目标容量
newCapacity = minCapacity;
//最后的容量是不是比 最大整形还大
if (newCapacity - MAX_ARRAY_SIZE > 0)
//如果太大最高也就给最大整型-8的值 适配其他虚拟机 为啥要-8 其他vm整型头信息可能会有别的用
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
整个过程如下:
你想找一个盆儿盛水
你妈先不管你要多少水 先给你个盆儿 这个盆儿肯定是咱平时能盛水用的
你要往盆儿里加水
麻麻得看盆儿有没有地儿装,如果不够了就再找个比现在盆儿1.5倍的盆儿(如果还不够就再找个肯定够的盆儿)
看看是不是装的水太多了,如果太多 最多给你一个缸
addAll()
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
//日常扩容 详见上面
ensureCapacityInternal(size + numNew); // Increments modCount
//原数组 a,开始索引0,目标数组elementData,开始位置size(本来就比索引多1),numNew全挪
System.arraycopy(a, 0, elementData, size, numNew);
//新老数组的合
size += numNew;
return numNew != 0;
}
两个数组叠在一起的感觉
我有两个棍插在洞里依次排列 | |
你有三个棍插在洞里依次排列 | | |
你想和我混成一排
System.arraycopy(a, 0, elementData, size, numNew);
a为你的那排棍
0为你想从你棍的第几个开始 和我的混到一起
elementData为我那排棍
size 为接纳你棍的起始位置
numNew为 为你预备出来几个洞
remove()
//数组的修改次数
protected transient int modCount = 0;
public boolean remove(Object o) {
//判断是否list.remove(null) 遍历数组 寻找为null的值
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 remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
//被修改的次数加1
modCount++;
//size -1 看成转换为索引 总索引-被删除的值所在的索引 为剩余元素条目数
int numMoved = size - index - 1;
//如果剩余条目数大于0 则证明删完后需要挪后面剩下的元素
if (numMoved > 0)
//参数分别为:原数组、需要挪动的启示位置、挪动后的数组、需要挪到的位置、挪几个
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//挪完后 少了个元素 最后一个肯定变成了空
elementData[--size] = null; // clear to let GC do its work
}
有五个孔, 圆木棍插在 孔中,依次排列了5个
| | | | |
此时想删掉第二个,因此 找到第二个并拔出来,将第二个后面的木棍依次向前面挪
肯定剩了最后一个孔,拿土埋上
以上是数组的一些常用方法,其中的方方面面都是实际生活中的缩影
备注:ArrayList非线程安全 如果想线程安全 请使用Collections.synchronizedList(new ArrayLsit())包装一下