ArrayList源码阅读和手写ArrayList
1.1 继承与实现
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 继承了
AbstractList
抽象类,实现List
接口,序列化和可克隆的接口。
1.2 数组类型
- 底层有动态数组实现,所谓的动态即数组的容量可扩容或者缩容。初始容量为10;
/**
* 初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
*存放元素的数组
*/
transient Object[] elementData;
/**
* The size of the ArrayList (the number of elements it contains).
*数组大小
* @serial
*/
private int size;
-
transient
:-
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
-
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
-
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
-
-
那么ArrayList是如何序列化的呢? 序列化是重写了
writeObject()
这个方法,保证只序列化elementData中有数据的那部分。反序列化是重写了readObject()
. 那么elementDate到底有没有被序列化呢? 答案是 :序列化了,不过只序列化了 有数据的那一部分,从而节省了资源。
来看看源代码吧
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// 写出元素计数以及任何隐藏的内容
//程序走到这里时 给expectedModCount 来达到记录的作用
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// 这里就可以看到 只写出了有数据的部分。即序列化这部分
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
//当检测到不相等时 说明发生了并发错误,有其他程序对数据进行了修改或者插入就抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
- 这里顺便看一下
ConcurrentModificationException()
以及modCount
这个东西。 - 什么时候抛出ConcurrentModificationException?
-
在迭代Array List或者HashMap时,尝试对集合做一些修改操作(例如删除元素),可能会抛出java.util.ConcurrentModificationException的异常。
-
一个线程通常不允许修改集合,而另一个线程正在遍历它。 一般来说,在这种情况下,迭代的结果是未定义的。 某些迭代器实现(包括由JRE提供的所有通用集合实现的实现)可能会选择在检测到此行为时抛出此异常。 这样做的迭代器被称为故障快速迭代器,因为它们快速而干净地失败(fast-fail)机制,而是在未来未确定的时间冒着任意的非确定性行为。
-
此异常并不总是表示对象已被不同的线程同时修改。 如果单个线程发出违反对象合同的方法调用序列,则该对象可能会抛出此异常。 例如,如果线程在使用故障快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。
-
故障快速行为无法保证,因为一般来说,在不同步并发修改的情况下,无法做出任何硬性保证。 失败快速的操作尽可能地抛出ConcurrentModificationException 。
-
- 看看代码迭代时修改是在干啥?
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
for (String s : list) {
//当我迭代到这里时 作出删除操作就会抛出:ConcurrentModificationException
//这个在java核心技术里面有讲到。
if (s.equals("B")) {
list.remove(s);
}
}
- 产生异常的原因
- ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素……)时都会modCount++
而foreach的背后实现原理其实就是Iterator(关于Iterator可以看Java Design Pattern: Iterator),等同于注释部分代码。在这里,迭代ArrayList的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,但如果接下来如果集合进行修改modCount改变,就会造成expectedModCount!=modCount,此时就会抛出java.util.ConcurrentModificationException异常。- 来看看迭代器的源代码
- ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素……)时都会modCount++
/*
*AbstarctList的内部类,用于迭代
*/
private class Itr implements Iterator<E> {
int cursor = 0; //将要访问的元素的索引
int lastRet = -1; //上一个访问元素的索引
int expectedModCount = modCount;//expectedModCount为预期修改值,初始化等于modCount(AbstractList类中的一个成员变量)
//判断是否还有下一个元素
public boolean hasNext() {
return cursor != size();
}
//取出下一个元素
public E next() {
checkForComodification(); //关键的一行代码,判断expectedModCount和modCount是否相等
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
1.3构造函数
- ArrayList有三个构造函数
- 如果在构造函数传入了 参数,来指定容量,就分类讨论: 如果容量大于0:那么就直接new一个Obejct数组。如果等于0,那么数组是空数组。
private static final Object[] EMPTY_ELEMENTDATA = {};
,这与我们构造空参形成的数组是不同的。当我们不传入参数时,数组是默认的空数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
- 为什么这么设计呢? 因为在源码中,当我们添加一个元素,我们会调用
calculateCapacity
来计算数组的容量。计算的方式就是先判断数组是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,如果是,就取·Math.max(DEFAULT_CAPACITY, minCapacity);
否则就直接返回, minCapacity
- 如果在构造函数传入了 参数,来指定容量,就分类讨论: 如果容量大于0:那么就直接new一个Obejct数组。如果等于0,那么数组是空数组。
/**
*制定初始容量
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//这个容量其实就是用来给elementData这个数组来申请空间时的大小
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//当这个容量为0时,那么这个数组就是个空数组咯
this.elementData = EMPTY_ELEMENTDATA;
} else {
//如果小于0 则抛出异常咯
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
*不传入参数 则使用默认的空间大小
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
*/
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;
}
}
1.4 增删改查
- 接下来就是增加数据,修改数据,删除数据,查询数据了,作为一个后端程序员,百分之九十的时间都是在操作数据,而对数据的操作离不开增删改查。
1.4.1 增加数据
- ArrayList里面提供了5中增加数据的操作
add(E e):
在尾部添加元素,如果不扩容 时间复杂度是O(1)级别的,
/**
* 将元素e插入到列表的尾部
*
* @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(size + 1); // Increments modCount!!
//插入到末尾
elementData[size++] = e;
return true;
}
add(int index, E element) :
这个方法是非常耗费时间的。因为会进行元素的大量移动平均时间复杂度是O(n)级别的。如果你的数据需要经常的插入删除的话 最好选用LinkedList,他的插入和删除 操作当确定位置之后 时间复杂度是O(1)级别的,只需要改变相应节点的指针指向即可达到删除和插入操作,而无需移动大量的元素。
/**
*将元素插入到指定的位置
* @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) {
//检测index是否合法
rangeCheckForAdd(index);
//扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将数组 index+1到size-index的元素后移
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//指定位置赋值
elementData[index] = element;
//数组大小加一
size++;
}
addAll(Collection<? extends E> c)
:按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。
/**
* 将指定集合中的所有元素追加到
* 此列表,按指定的集合的"数据器"。 此操作的行为是
* 如果操作时修改了指定的集合,则未定义
* 正在进行中。 (这意味着此调用的行为是
* 未定义,如果指定的集合是此列表,这
* 列表是非空的。
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
//先将集合c转换成数组。
Object[] a = c.toArray();
//数组长度
int numNew = a.length;
//对数组进行扩容 大小就是原来的大小加上新的数组大小
ensureCapacityInternal(size + numNew);
//复制移动 最耗费时间的操作
System.arraycopy(a, 0, elementData, size, numNew);
//扩容后 数组大小肯定增加啦
size += numNew;
return numNew != 0;
}
addAll(int index, Collection<?> extends E> c)
:从index开始将集合c的元素插入进列表中。具体操作差不多了,无非就是多了一个index是否合理 ,然后将index后面的元素移动,移动多少呢?既然别人要插进来,那么就给他挪动个刚刚那么多的大小啦!直接看源码
public boolean addAll(int index, Collection<? extends E> c) {
//检查index是否合理
rangeCheckForAdd(index);
//集合c转换为数组
Object[] a = c.toArray();
//要插入的数组长度
int numNew = a.length;
//扩容一下
ensureCapacityInternal(size + numNew); // Increments modCount
//要移动的元素数量
int numMoved = size - index;
if (numMoved > 0)
//继续消耗我们的大把青春去copy
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//添加数组
System.arraycopy(a, 0, elementData, index, numNew);
//size增加一下
size += numNew;
return numNew != 0;
}
set(int index, E element)
: 就是在指定的index处设置值,很简单操作
public E set(int index, E e) {
//检查越界
rangeCheck(index);
checkForComodification();
//得到老值
E oldValue = ArrayList.this.elementData(offset + index);
//设置新值
ArrayList.this.elementData[offset + index] = e;
//返回原来值
return oldValue;
}
1.4.2 删除元素
- ArrayList提供了
remove(int index):
移除指定索引
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
remove(Object o)
:移除指定元素
public boolean remove(Object o) {
//因为ArrayList中允许存在null,所以需要进行null判断
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;
}
removeRange(int fromIndex, int toIndex):
移除从fromIndex到toindex这一段的元素
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System
.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// Let gc do its work
int newSize = size - (toIndex - fromIndex);
while (size != newSize)
elementData[--size] = null;
}
removeAll() :
移除所有元素 四个方法进行元素的删除。
1.4.3查找操作
get(int index)
:得到对应索引的元素,非常的实现。
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
1.4.4扩容操作
ensureCapacity(int minCapacity):
我们在前面经常看到这样的一个方法ensureCapacity()
:这个就是扩容的方法,在每次增加操作时,我们不但要判断索引是否合理还要判断当前数组容量是否足够,当不足的时候,我们就会进行扩容操作。来直接看看源代码
/**
* 增加此 ArrayList 实例的容量,如果
* 必要,以确保它至少可以容纳元素的数量
* 由最小容量参数指定。
* 列表是非空的。
* @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);
}
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 不同的java版本源代码可能会有一些不同,同学们在学习的时候可以多查查资料,或者直接把源代码的注释翻译过来。看源代码是有非常多的好处,最最基本的就是代码规范,看看别人怎么命令的,还有就是,很多步骤都是被封装成对应的方法,不但实现了代码的复用,还是得代码更加的简洁。
自己手写简单版的Array List源代码
public class ArrayList<E> {
private E[] data ;
private int size;
//支持泛型
/**
*构造函数,传入数组的容量 capacity 构造Array
* @param capacity 容量
*/
public ArrayList(int capacity){
data=(E[]) new Object[capacity];
size=0;
}
/**
* capacity: 容量
* 无参数的构造函数,默认数组的容量capacity=10;
*/
public ArrayList(){
this(10);
}
/**
* 得到数组大小
* @return size
*/
public int getSize(){
return size;
}
/**
* 得到数组容量
* @return data.length
*/
public int getCapacity(){
return data.length;
}
/**
* 返回数组是否为空
* @return boolean
*/
public boolean isEmpty(){
return size==0;
}
public void addLast(E e){
// if (size==data.length)
// throw new IllegalArgumentException("AddLast failed.Array is full");
// data[size]=e;
// size++;
add(size,e);
}
public void addFirst(E e){
add(0,e);
}
/**
* 在 index插入元素e
* @param index 索引
* @param e 元素
*/
public void add(int index,E e){
if (size==data.length)
resize(2*data.length);
if (index<0|| index>size)
throw new IllegalArgumentException("AddLast failed.Require index >=0 and index<=size");
//元素后移
for (int i=size-1;i>=index;i--){
data[i+1]=data[i];
}
data[index]=e;
size++;
}
/**
*
*/
public E get(int index){
if (index<0||index>=size)
throw new IllegalArgumentException("Get failed.index is ");
return data[index];
}
public void set(int index,E e){
if (index<0||index>=size)
throw new IllegalArgumentException("Get failed.index is ");
data[index]=e;
}
/**
* 数组是否包含了某个元素
* @param e 元素
* @return
*/
public boolean contains(E e){
for (int i=0;i<size;i++){
if (data[i].equals(e))
return true;
}
return false;
}
/**
* 找到某个元素 返回元素的下标
* @param e 元素
* @return
*/
public int find(E e){
for (int i=0;i<size;i++){
if (data[i].equals(e))
return i;
}
return -1;
}
/**
* 删除下标元素 并返回这个元素
* @param index 数组下标
* @return
*/
public E remove(int index){
if (index<0||index>=size)
throw new IllegalArgumentException("Get failed.index is ");
E ret=data[index];
for (int i=index+1;i<size;i++)
data[i - 1] = data[i];
size--;
data[size]=null;
//缩容
if(size==data.length/4&&data.length!=0)
resize(data.length/2);
return ret;
}
public E removeFirst(){
return remove(0);
}
public E removeLast(){
return remove(size-1);
}
//删除指定元素
public void removeElement(E e){
int index=find(e);
if (index!=-1)
remove(index);
}
private void resize(int newCapacity){
E[] newData=(E[]) new Object[newCapacity];
for (int i=0;i<size;i++)
newData[i]=data[i];
data=newData;
}
}