文章目录
ArrayList 简介
ArrayList 概述
ArrayList 集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引访问集合中的指定元素。另外,List集合中还有一个特点就是元素有序,即元素的存入顺序和取出顺序是一致的。
ArrayList 底层是一个 Object[] 数组,每一个类对象都有一个capcacity 属性,表示数组的长度,当向ArrayList 添加元素时,capcacity 属性会自动增加。
ArrayList 数据结构
底层数据结构是一个数组,元素的类型是Object类型,即可以存放所有类型的数据;
private static final Object[] EMPTY_ELEMENTDATA = {};
ArrayList 的数据结构如下:
ArrayList 源码分析
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 集成 AbstractList , 主要是 集合公共的方法
- Cloneable 表示可克隆
- Serializable : 可序列化
类中的属性
//默认数组大小
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;
说明:类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的。
构造方法
无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是一个空的数据,是个空的Object[];
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
默认代销为10;
有参构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {\
// 默认 initialCapacity 为10
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
指定 elementData数组的大小,不允许初始化大小小于0,否则抛出异常。
ArrayList(Collection<? extends E>)型构造函数
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//转换为数组
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
核心方法
add 方法
- add(E)
默认直接在末尾添加元素
public boolean add(E e) {
// size 是数组元素的个数,因为这时要添加一个元素,所以先判断size + 1 这个大小数组是否容得下
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal 确定内部容量的方法
private void ensureCapacityInternal(int minCapacity) {
//如果 elementData 是空元素,size + 1 为1
// 那么 minCapacity 的最大值 就是10,
// 这时的空数据还没有进行初始化
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 确定实际的容量,判断elementData 是否够用
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) {
//modCount 记录这集合的修改次数,也就是每次add或者remove它的值都会加1,那么由什么用呢?
// 至于作用,我们在下文中进行分析
modCount++;
// overflow-conscious code
// 第一种情况: elementData 是空数组,minCapacity = 10
//第二种情况,elementData 不是空数据,add 的时候 minCapacity= size+1,
// 也就是elementData 增加后的个数,拿它和 elementData的length 是否够用,如果
// 不够用,则需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
grow 能够扩展数据的大小
private void grow(int minCapacity) {
// overflow-conscious code
// 扩容前的实际大小
int oldCapacity = elementData.length;
// newCapacity 为 oldCapacity 的 1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//这个对应的是 elementData是空数组情况下
// oldCapacity 为0,那么newCapacaity 也为0
// minCapacity 为 10 ,所以相减小于0
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//这时新的容量就为10
// 如果newCapacity 大于最大容量
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
if (newCapacity - MAX_ARRAY_SIZE > 0)
//调用hugeCapacity方法
newCapacity = hugeCapacity(minCapacity);
// 新的容量大小确认后,copy原来的数据到扩容后的数据
elementData = Arrays.copyOf(elementData, newCapacity);
}
- oldCapacity + (oldCapacity >> 1): 比如原始容量为13,当新添加一个元素时,依据前面的计算方法,得出13的二进制数为1101,随后右移一位操作后得到的二进制为 119,即十进制数为6.最终扩容的计算结果为 oldCapacity + (oldCapacity >> 1) = 13+6 = 19。使用位运算主要是基于计算效率的考虑。
- 当ArrayList 使用无参狗子啊方法时,默认的大小为10。也就是说在第一次add的时候,分配为10的容量,后续的每次扩容都会调用Arrays.copyOf 方法,创建新数组再复制,可以想象的是,假如需要将 100 个元素放置在 ArrayList中,采用默认构造方法,则需要被扩容13次才可以完成存储。反之,无果在初始化时便指定了容量new ArrayList(1000,那么在初始化ArrayList对象的时候就直接分配1000个存储空间,从而避免被动扩容和数组复制的额外开销。最后,进一步向,如果这个值达到更大的量级,却没有注意初始的容量分配问题,那么无形中造成的性能损耗是非常大的,甚至导致OOM的风险。
hugeCapacity
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果 minCapacity 大于 Max_ARRAY_SIZE,那么就返回Integer.MAX_VALUE
// 否则返回 MAX_ARAY_SIZE = Integer.MAX_VALUE - 8
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
当我们调用add方法时,实际上的函数调用如下
2) add(int,E) 在特定位置添加元素,也就是插入元素
public void add(int index, E element) {
// 判断index 是否越界
rangeCheckForAdd(index);
// 扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//插入元素后,将index之后的元素都后移一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 在index 处插入目标元素
elementData[index] = element;
size++;// size + 1
}
rangeCheckForAdd(index)
private void rangeCheckForAdd(int index) {
// 插入的元素不能大于size 或者 小于 0,否则抛异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
System.arraycopy(…):
就是将elementData在插入位置后的所有元素往后面移一位。
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:
- Object src : 原数组
- int srcPos : 从元数据的起始位置开始
- Object dest : 目标数组
- int destPos : 目标数组的开始起始位置
- int length : 要copy的数组的长度
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
举例如下:
List<String> list = new ArrayList<>(10);
list.add("1");
list.add("2");
list.add("3");
list.add("5");
此时elementData 为
此时我们在 第一个位置插入 4
list.add(1,“6”);
System.arraycopy(elementData, 1, elementData, 2, 4);
然后把6插入到1的位置
remove 方法
- remove(int)
通过删除指定位置上的元素
public E remove(int index) {
// 检查 index 是否合法
rangeCheck(index);
modCount++;
// 获取index位置的元素,保存到oldValue上
E oldValue = elementData(index);
// 计算要移动的位数
int numMoved = size - index - 1;
if (numMoved > 0)
// 移动元素,将指定位置index后的元素都要往前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最后一个元素置空,让GC自动回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
2) remove(Object)
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;
}
遍历所有的对象,得到对象的索引位置,然后调用 fastRemove方法,执行remove操作
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
}
定位到需要remove 元素的索引,先将index后面的元素往前移动一位,然后将最后的元素置空。
其他方法比较简单,就不一罗列了
总结
- ArrayList 可以存放null元素。
- ArrayList 可以自动扩展,当ArrayList 容量不足以容纳全部元素的时候,ArrayList 会重新设置容量。新的容量=“(原始容量x3)/2 + 1”;如果设置后的新容量还不够,则直接把新容量设置为传入的参数。
- arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
- arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
- 集合初始化时,需要指定集合初始值大小。如果暂时无法确定集合大小,那么指定相应的默认值,这也要求我们记得各种集合的默认值大小,ArayList默认大小为10。