ArrayList
- 特点
- ArrayList 底层是一个数组,具有数组的优点:查询速度快,增加删除慢
- ArrayList 默认的初始容量是10,随着元素的不断添加,该数组会扩充为原来数组长度的1.5倍
- 适合频繁进行查询时使用
- 数组长度不可变但是集合长度可变
首先分析其构造函数
private static final Object[] EMPTY_ELEMENTDATA = {};
// 初始化定义一个默认Object的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**被transient关键字定义的对象不能被序列化 **/
transient Object[] elementData;
// 空参构造函数(最常用)
public ArrayList() {
//使用默认数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定数组长度构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//如果传入数组长度大于0创建一个指定长度的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//如果传入数组长度等于0使用上述空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {// 否则抛出异常非法参数
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//传入一个集合
public ArrayList(Collection<? extends E> c) {
//把传入的集合变成一个数组赋给当前数组
elementData = c.toArray();
/**判断数组中是否有内容,在这里可能会疑惑,上一行代码已经给elementData赋值,
为何还要判断重新拷贝赋值,这是jdk1.8防止出现一个bug而做的一个判断jdk1.9已经修复**/
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
//拷贝一份
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 若为空则还是赋值一个空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
其次分析它的方法实现
1. add方法插入数据一
public boolean add(E e) {
// 检测是否需要扩容数组,size是元素个数
ensureCapacityInternal(size + 1);
//把新增加的数组加到数组末尾
elementData[size++] = e;
//成功添加
return true;
}
/**
*下面解释一下上述方法
*/
private void ensureCapacityInternal(int minCapacity) {
//继续调用calculateCapacity
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//第一次执行添加时,elementData数组为空数组,返回DEFAULT_CAPACITY默认值10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//其后在执行添加操作时返回的就是minCapacity
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//这算是一个计数器记录数组修改次数
modCount++;
// 如上述,若第一次添加返minCapacity的值为10,elementData.length=0,进行第一次扩容
if (minCapacity - elementData.length > 0)
//执行扩容,扩容后数组长度为15(原数组长度的1.5倍),再次执行扩容时需要minCapacity值为16,minCapacity的值是数组元素加一
grow(minCapacity);//此方法在后面会详细提到
}
2. add方法插入数据二
//指定数组下标添加
public void add(int index, E element) {
//检测当前数组下标是否越界(不合法)
rangeCheckForAdd(index);
//同上检测是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//把index之后的元素向后移动一位,这是个本地方法(看不到源码)
//例:移动前数组[1,2,3,4,5,0,0,0] index=2,移动后数组[1,2,0,3,4,5,0,0]
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将元素插入index下标下
//例: 移动后数组[1,2,0,3,4,5,0,0] 插入element=2 插入后数组:[1,2,2,3,4,5,0,0]
elementData[index] = element;
//元素个数加一
size++;
}
这里分析一下它是如何实现扩容操作的
private void grow(int minCapacity) {
//获取当前数组容量
int oldCapacity = elementData.length;
//扩容数组容量=当前数组容量+当前数组容量/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//扩容完肯定是大于0的
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//数组长度的最大值是Integer.MAX_VALUE - 8,不能比这个大
if (newCapacity - MAX_ARRAY_SIZE > 0)
//已经达到数组储存极限
newCapacity = hugeCapacity(minCapacity);
// 将当前容量数组数据拷贝到新的数组中,这也是ArrayList添加操作慢的原因,需要拷贝整个数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
3. remove方法删除数据一
//指定下标删除
public E remove(int index) {
//判断index是否合法是否越界
rangeCheck(index);
//记录加一
modCount++;
//取出index处的元素
E oldValue = elementData(index);
//删除元素需要后面所有元素向前移动填补删除的空缺,这里获取每个元素移动几个位置
int numMoved = size - index - 1;
if (numMoved > 0)
//向前移动注意赋值,正好覆盖了要删除的元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//数组个数减减,把最后一个数据清空(注意数组从0开始,size是从1开始,所以是先减操作)
elementData[--size] = null;
//返回已经删除的元素
return oldValue;
}
4. remove方法删除数据二
//指定元素删除
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;
}
}
//若没有该元素直接返回false
return false;
}
/**
* 用到方法解析
*/
private void fastRemove(int index) {
//首先还是记录操作次数
modCount++;
//进行删除同上,不需要检测index是否越界问题,因为上述for循环已有条件限制
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
}
5. 遍历元素迭代器遍历
遍历有很多种方法,增强for遍历(底层也是调用迭代器遍历)
- 迭代器如何遍历
public static void main(String[] args) {
//无参构造
ArrayList<Object> lists = new ArrayList<>();
lists.add(1);
lists.add(2);
lists.add(3);
lists.add(4);
lists.add(5);
lists.add(6);
//返回的是iterator的一个实现类Itr
Iterator<Object> iterator = lists.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
}
//源码分析
public Iterator<E> iterator() {//调用此方法返回一个实例
return new Itr();
}
//初始化的三个参数
int cursor; // 记录当前操作的元素下标
int lastRet = -1; // 记录结束时元素索引为-1
int expectedModCount = modCount; //之前一直提的数组更改次数
public boolean hasNext() {//当cursor 与size相等时证明遍历已经完成跳出循环
return cursor != size;
}
public E next() {
checkForComodification();//这里可能会抛出一个异常下文讲解
int i = cursor;//把全局变量赋值局部变量
if (i >= size)//因为有循环终止条件所以i小于size,反之报异常
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)//因为有循环终止条件所以i小于size,反之报异常
throw new ConcurrentModificationException();
cursor = i + 1;//全局变量cursor 加一
return (E) elementData[lastRet = i];//返回数组下标的数据
}
5. 遍历元素迭代器遍历时执行添加或者删除操作会报异常原因分析
//上述提到以下方法,每执行一次next()方法都会先调用checkForComodification()方法
checkForComodification();
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
/**这里解释一下,因为每一次操作增加或者删除元素时都会改变modCount的值,
而在创建迭代器的时候出初始化数据int expectedModCount = modCount
,一旦发现不相等则报处异常**/
//若需要遍历时删除元素,使用下面方法
while (iterator.hasNext()){
Integer next = (Integer) iterator.next();
if (next==2){//通过iterator的remove方法删除,看下面注释
iterator.remove();
}
System.out.println(next);
}
System.out.println(Arrays.toString(lists.toArray()));
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);//这里其实还是调用了数组的删除方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;//在这里保证了这两个值的一致性
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
总结
ArrayList其实就是维护了一个数组,通过编码,实现了名义上的长度可变数组,
这是它的最大优势,借此通过查看源码也可以学到很多知识