ArrayList底层原理
ArrayList集合底层数据结构
ArrayList集合介绍
实现List接口,底层可调整大小的数组实现
数组
:一旦初始化长度就不可以改变
数组结构介绍
增删慢
:每次删除元素,都需要更改数组长度、拷贝以及移动元素的位置
查询快
:由于数组在内存中是一块连续空间,因此可以根据地址+索引
的方式快速获取对应位置上的元素
ArrayList继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
实现Serializable标记型接口
序列化:将对象转化为字节数组的过程
反序列化:将字节数组转化为对象的过程
实现Cloneable标记型接口
克隆:将ArrayList集合的数组clone到另一个集合
实际底层调用的是Object中的clone()
方法,而clone是一个native方法
Object o=list.clone();
System.out.println(o==list);//地址不一样,深度克隆
使用了Arrays.copyOf()
方法进行复制
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
浅拷贝
:基本数据类型可以达到完全复制,引用数据类型拷贝的是栈上的地址值,所以修改引用类型的数据,会改变原来的数据
深拷贝
:基本数据类型和引用类型都可以完全复制,引用对象在堆中创建新的对象,对原数据没有任何影响
深拷贝的实现方式:
实现Cloneable
接口并重写clone()
方法
让其clone()
方法通过序列化和反序列化的方式来生成一个原对象的深拷贝副本
实现RandomAccess标记型接口
ArrayList支持随机访问
通过get(i)即可获得相应内存中存放的值。原因是因为ArrayList存放的内容在内存中是连续的,数组直接用[ ]访问,相当于直接操作内存地址,所以随机访问的效率较高。
普通的for循环是随机访问的,所以遍历ArrayList使用普通for循环比增强for循环和迭代器的效率高。
而LinkedList是一个双向链表,链表只能顺序访问,不支持随机访问,LinkedList中的get方法是按照顺序从列表的一端开始检查,直到找到要找的地址。所以遍历LinkedList使用增强for循环和迭代器的效率高,使用普通for循环会每次都从头开始遍历,效率较差。
AbstractList抽象类
AbstractList 虽然是抽象类,但其内部只有一个抽象方法 get():
abstract public E get(int index);
从字面上看这是获取的方法,子类必须实现它,一般是作为获取元素的用途,除此之外,如果子类要操作元素,还需要重写 add(), set(), remove() 方法,因为 AbstractList 虽然定义了这几个方法,但默认是不支持的,
ArrayList源码分析
相关变量
//初始容量为10
private static final int DEFAULT_CAPACITY = 10;
//长度为0的空数组
private static final Object[] EMPTY_ELEMENTDATA = {
};
//默认为长度为0的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
//集合真正存元素的数组
transient Object[] elementData; // non-private to simplify nested class access
//集合的长度
private int size;
构造方法
Constructor | 描述 |
---|---|
ArrayList() | 构造一个初始容量为十的空列表 |
ArrayList(int initialCapacity) | 构造具有指定初始容量的空列表 |
ArrayList(Collection <? extends E>c) | 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序 |
空参:ArrayList()
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
实际上空参的数组长度为0
有参:ArrayList(int initialCapacity)
public ArrayList(int initialCapacity) {
//手动初始化
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果长度为0,使用final修饰的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
如果在使用ArrayList集合,知道长度就尽量给定长度,减少内存的消耗,因为数组每次扩容,都会复制一份新的数组,会极大的消耗内存
有参:ArrayList(Collection <? extends E>c)
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//如果数组长度为0,使用的是默认的空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
进入到Arrays.copyOf
方法
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new