ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
特点:查询效率高,增删效率低,线程不安全。
查看源码,我们可以看出ArrayList底层使用Object数组来存储元素数据。所有的方法,都围绕这个核心的Object数组来开展。
对ArrayList的操作,其实就是对数组的操作,下面我们来分析ArrayList的底层实现:
- 私有属性:
ArrayList类只定义了两个私有属性:
public class ArrayList {
// 存放元素的数组
private Object elementData;
// 存放元素的个数
private int size;
}
很容易理解,elementData存储ArrayList内的元素,size表示它包含的元素的数量。
- 构造方法:
ArrayList提供的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表。
public class ArrayList {
// ...省略私有属性...
// 无参构造方法,默认容量是10。
public ArrayList() {
this(10);
}
// 带容量大小的构造函数
public ArrayList(int initialCapacity) {
// 如果参数非法,抛出异常
if(initialCapacity < 0) {
throw new IllegalArgumentException("initialCapacity: 不能为负数 "+
initialCapacity);
}
// 创建指定容量的数组
this.elementData = new Object[initialCapacity];
}
}
- 添加元素方法:
我们知道,数组长度是有限的,而ArrayList是可以存放任意数量的对象,长度不受限制,那么它是怎么实现的呢?本质上就是通过定义新的更大的数组,将旧数组中的内容拷贝到新数组,来实现扩容。 ArrayList的Object数组初始化长度为10,如果我们存储满了这个数组,需要存储第11个对象,就会定义新的长度更大的数组,并将原数组内容和新的元素一起加入到新数组中,源码如下:
public class ArrayList {
// ...省略私有属性和构造方法...
// 追加一个元素方法
public boolean add(Object element) {
// 1.检查数组是否需要扩容
ensureCapacityInternal(size + 1);
// 2.在数组末尾追加一个元素
elementData[size] = element;
// 3.数组元素个数累加
size++;
return true;
}
// 数组扩容方法
private void ensureCapacityInternal(int minCapacity) {
// 1.获取数组长度
int oldCapacity = elementData.length;
// 2.需求数组长度大于数组的原长度时,则证明需要扩容
if(minCapacity > oldCapacity) {
// 3.把数组空间扩展为原来的1.5倍(可自定义)
int newCapacity = oldCapacity + oldCapacity >> 1;
// 4.如果扩为1.5倍还不满足需求,直接扩为需求值
if(newCapacity < minCapacity)
newCapacity = minCapacity;
// 5.定义一个更大的新数组(数组长度)
Object[] newElementData = new Object[newCapacity];
// 6.将旧数组中的内容拷贝到新数组
System.arraycopy(elementData, 0, newElementData, 0, size);
// 7.使原数组指向新数组,完成对elementData数组的扩容
elementData = newElementData;
}
}
}
- 获取元素方法:
ArrayList的get方法就比较简单了,先做index检查,然后执行访问操作。
public class ArrayList {
// ...省略私有属性和构造方法...
// 根据索引获取元素
public Object get(int index) {
// 1.检查所有是否合法
rangeCheck(index);
// 2.根据索引获取元素
return elementData[index];
}
// 检查所有是否合法
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("移除索引越界");
}
}
- 修改元素方法:
ArrayList的set方法和获取元素方法比较类似,先做index检查,然后执行修改操作。
public class ArrayList {
// ...省略私有属性和构造方法...
// 根据索引替换数组中的元素
public Object set(int index, Object element) {
// 1.检查所有是否合法
rangeCheck(index);
// 2.获取被替换的元素
Object oldValue = elementData[index];
// 3.替换元素
elementData[index] = element;
// 4.返回替换元素的值
return oldValue;
}
}
- 插入元素方法:
将元素插入到列表中指定的位置,先检查传入的索引是否合法,然后判断数组是否需要扩容,接着将指定位置以及后续的元素向后移动一位,最后再插入元素。
public class ArrayList {
// ...省略私有属性和构造方法...
// 指定位置插入一个元素
public void add(int index, Object element) {
// 1.检查索引位置是否合法
rangeCheckForAdd(index);
// 2.检查是否需要扩容
ensureCapacityInternal(size + 1);
// 3.将指定位置以及后续的元素向后移动一位
System.arraycopy(elementData, index,elementData, index + 1, size - index);
// 4.指定位置插入元素
elementData[index] = element;
// 5.数组元素个数累加
size++;
}
// 检查索引位置是否合法
private void rangeCheckForAdd(int index) {
if(index < 0 || index > this.size)
throw new IndexOutOfBoundsException("插入索引越界");
}
}
- 移除元素方法:
根据索引来移除元素,首先先判断索引是否合法,然后将指定位置以及后续的元素向前移动一位,最后把数组中最后一个元素设置为null。
根据元素来移除元素,首先找到该元素在数组中所在的索引,如果没有找到则证明移除失败,如果找到则进行根据索引来移除元素的操作。
public class ArrayList {
// ...省略私有属性和构造方法...
// 根据索引移除数组元素
public Object remove(int index) {
// 1.检查索引位置是否合法
rangeCheck(index);
// 2.获取索引所在的元素
Object oldValue = elementData[index];
// 3.删除输出元素
fastRemove(index);
// 4.返回被删除的元素
return oldValue;
}
// 根据元素移除数组元素
public boolean remove(Object o) {
// 因为集合中可以存放null,所以判断之前需判断元素是否为null
if(o == null) {
for (int index = 0; index < size; index++)
// 判断元素是否为null
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) {
// 1.将指定位置后续的元素向前移动一位
System.arraycopy(elementData,index+1,elementData,index,size-index-1);
// 2.把数组最后一个元素设置为null,并数组元素个数减一
elementData[--size] = null;
}
}
ps:如需最新的免费文档资料和教学视频,请添加QQ群(627407545)领取。