很多小伙伴都知道Arraylist和Linkedlist相比,查询快,增删慢,这是为什么呢?它底层又是怎样的结构呢?请随我一起从源码分析(如有错误请大佬指出,本人是几个月小白)
简介
java容器集合可分为key值存储(Collection)和key-value存储(Map)两类,如下图继承链中ArrayList则是继承AbstractList类,实现了Collection接口
特点
对于ArrayList主要特点:
基于Object数组实现的List集合
不像数组一样长度固定,ArrayList可以实现动态扩容
有序,可重复
线程非安全,效率高
基于数组,查询快,但删除慢
元素可以为null
接下来源码分析(JDK1.8)
继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
可见ArrayList支持泛型,且继承AbstractList类,实现了List,RandomAccess, Cloneable, java.io.Serializable接口,除开List,其他三个仅仅标识ArrayList可以具有随机访问,克隆,序列化功能
例如Cloneable仅仅是一个接口里面没有内容,仅为标识作用,其他两个一样
public interface Cloneable {
}
成员变量
private static final long serialVersionUID = 8683452581122892189L;
// 默认初始容量为10
private static final int DEFAULT_CAPACITY = 10;
// 初始一个空的Object数组
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* (源码)Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* (有道翻译)用于默认大小的空实例的共享空数组实例。我们将其与空的ELEMENTDATA区分开来,
* 以便知道添加第一个元素时应该膨胀多少。
* 后面默认构造函数将用到DEFAULTCAPACITY_EMPTY_ELEMENTDATA为elementData初始化
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* 底层数组
* transient关键字:大致上是,在实现Serilizable接口后
* 将不需要序列化的属性前添加关键字transient,
* 序列化对象的时候,这个属性就不会序列化到指定的目的地中。
*/
transient Object[] elementData; // 非私有简化嵌套类访问
//ArrayList的大小(包含的元素数量),默认为0。
private int size;
构造方法三个
/**
* 构造一个指定初始容量的Arraylist
*/
public ArrayList(int initialCapacity) {
//如果大于0则初始化elementData数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//如果初始容量为0,则创建空数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
//小于0则抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 构造一个初始容量为10的空列表。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
* (有道翻译)构造一个包含指定集合元素的列表,按照集合的迭代器返回元素的顺序。
*
* @param c将其元素放置到列表中的集合
* @throws 如果指定的集合为空,则抛出NullPointerException
*/
public ArrayList(Collection<? extends E> c) {
//将c转换为数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//c.toArray可能(不正确)不返回Object[](参见6260652)
//如果不正确,所以下面有判断
if (elementData.getClass() != Object[].class)
//使用Arrays.copyOf将elementData拷贝成Object数组
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 长度为0则替换为空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
常用的增删改查
add方法
add方法主要有2个
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//先调用ensureCapacityInternal判断是否需要扩容
ensureCapacityInternal(size + 1); // 增加 modCount!!
//将元素e存入elementData数组并且size++
elementData[size++] = e;
return true;
}
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//这里因为是在指定索引位置添加元素,则需要调用rangeCheckForAdd检查索引位置
rangeCheckForAdd(index);
//再确保容量足够
ensureCapacityInternal(size + 1); // Increments modCount!!
//调优arraycopy方法,将index插入元素的位置的后面元素依次后移,所以插入元素是非常耗时
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将element元素放入index索引处
elementData[index] = element;
size++;
}
ensureCapacityInternal
//上面两个都调用了ensureCapacityInternal和rangeCheckForAdd方法
/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
//参数为minCapability=size+1,
private void ensureCapacityInternal(int minCapacity) {
//判断elementData数组是否为空数组,空的话则返回默认为10的长度和size+1的最大值,
//之前size初始化默认为0,如果这是第一次则扩容为10,因为10>1;
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//modCount+1;
modCount++;
// 这里判断需不需要扩容,minCapacity 为10,而现在length为0,还没添加元素,
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//要分配的数组的最大大小。一些虚拟机在数组中保留一些头字。
//尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
//这里设置 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// 记载length
int oldCapacity = elementData.length;
//设置新容量为oldCapacity 的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//下面就是判断扩容后的长度和之前的长度,这里<0意思第一次没有元素newCapacity 为0;
//则将默认的10赋予新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果大于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//则调用hugeCapacity方法赋予newCapacity 值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//minCapacity通常接近于size,所以这是一个胜利
//最后调用Arrays.copyOf得到新的容量为newCapacity的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//返回如果大于最大允许的长度,则返回 Integer.MAX_VALUE 尽量将最大的容量返回
//否则返回 MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
rangeCheckForAdd
//判断是否越界
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
get方法set方法
//这个比较简单,直接调用rangeCheck检查索引,然后返回
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public E set(int index, E element) {
//一样检查索引
rangeCheck(index);
//保存之前index上的元素
E oldValue = elementData(index);
//覆盖并将之前的元素返回
elementData[index] = element;
return oldValue;
}
remove方法
//首先检查索引,然后modcount++,这里modcount也许有人不是很了解,这里只大致说一下
//modCount 顾名思义就是修改次数,在迭代器中有一个属性expectedModCount;
//在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。
//在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,不相等则代表其他线程对这容器进行了修改,这里还有个Fail-Fast 机制,没了解的同学建议了解一下
public E remove(int index) {
rangeCheck(index);
modCount++;
//记录删除的元素
E oldValue = elementData(index);
//记录将移动的元素个数
int numMoved = size - index - 1;
//依次向前移动,这里又可以证明Arraylist删除非常的麻烦
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//最后一个元素只为空,这样该元素就没被引用,而数组元素是放值的堆中,会被GC管理回收
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
//通过传入一个元素,判断时候有则删除,且返回true,否则返回false
public boolean remove(Object o) {
//o==null,循环遍历,有则调用fastRemove
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 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
}
//清空数组,全部只为空
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
其他常用方法
如下代码很好理解不再多说
public boolean isEmpty() {
return size == 0;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
总结
可以从上面的源码看出,ArrayList底层是一个Object类型的数组,可以存储基本类型和引用类型,元素可以为空,因为是基于数组,具有索引,所以查询效率高,但是面对增删改时候,会依次移动元素导致效率下降,浪费时间
为什么非线程安全:上面在做add增加的时候,会进行扩容,在最后的copyOf方法的时候会返回elementData对象,如果有多个线程则会返回多个elementData对象,这时候后来的线程返回的elementData对象将会把之前的线程elementData对象覆盖,这里想要线程安全可以使用下面三个类
java.util.Collections.SynchronizedList
vector
java.util.concurrent.CopyOnWriteArrayList
好了,以上是个人总结的部分源码,有兴趣的小伙伴建议还是打开电脑看看源码,谢谢观看。