小编教ArrayList源码解读

前言
1)本文章的JDK版本为JDK1.8,如果你使用的是其他版本,请参考你的Java源码!
2)由于作者水平有限,本文只对部分的方法进行了分析。不足之处,希望大家指出,谢谢
3)如果你对Java中的数组还没有理解,可以先学习数组及其在JVM中的存储方式,可以参考下面文章
Java中数组在内存中的存放原理讲解
java对象数组的概述和使用

1、继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2、成员变量
private static final int DEFAULT_CAPACITY = 10;
ArrayList默认的初始容量
private static final Object[] EMPTY_ELEMENTDATA = {};
默认的空Object数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
与默认初始容量对应的Object数组
transient Object[] elementData;
存放元素的数组
private int size;
动态数组的size
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
最大的数组size = 0x7FFFFFF7 = (Integer.MAX_VALUE = 0x7fffffff)-8
3、构造器
//指定初始化容量的构造器,它通过判断int值大小来初始化elementData
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//默认的
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
/类型判断,如果elementData的类型不是Object[]数组,则将其
复制为Object类型的数组,并且elementData的大小小于size时,多于的位置
*填入null
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
4、方法

4.1 public void trimToSize()

/**

  • ArrayList大小整理,在size < elementData.length 且 size != 0时,
  • elementData将会被截断为size的长度!!!!
  • elementData将会被截断为size的长度!!!!
    */
    public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
    elementData = (size == 0)
    ? EMPTY_ELEMENTDATA
    : Arrays.copyOf(elementData, size);
    }
    }

4.2 public void ensureCapacity(int minCapacity)及相关私有方法

/**

  • 增加数组的容量的判定,这个操作只有在 minCapacity>minExpand 时,
  • 才能完成操作。
  • 注意下面的三目运算符,minExpand的值取决于
  • 你是否已经在你的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);

}

/* 核心方法,动态数组的增长实现

  • 一般情况按oldCapacity的半数增长,但最大的size不能大于Integer.MAX_VALUE
  • 原数组会被copy到一个新的数组(size=newCapacity)中,多于的位置填充null
    */
    private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
    }

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
grow()流程图:

4.3 public int indexOf(Object o)

//返回指定元素第一次出现的索引,无则返回 -1
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
//如果使用 o.equals()则会抛出空指针异常(null.equals()是错的)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
//Object类的equals90直接调用“==”
if (o.equals(elementData[i]))
return i;
}
return -1;
}

4.4 public int lastIndexOf(Object o)

//返回指定元素最后一次出现的索引,无则返回 -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;
}

4.6 add方法

//(1)在尾部添加
public boolean add(E e) {
//确定是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

//(2)在指定位置添加
public void add(int index, E element) {
//判定是否索引越界
rangeCheckForAdd(index);
//确定是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!

//从指定数组的指定开始位置到指定结束位置复制一个数组,在本节末尾给出了该方法的详细解释
System.arraycopy(elementData, index, elementData, index + 1,
                 size - index);

//将index位置的原element替换为新的element
elementData[index] = element;
size++;

}

//(3)两个索引越界检查方法
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//索引越界信息
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
public static void arraycopy(Object src, int srcPos,Object dest, int destPos,int length)

src - 源数组。
srcPos - 源数组中的起始位置。
dest - 目标数组。
destPos - 目标数据中的起始位置。
length - 要复制的数组元素的数量。
从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。从 src 引用的源数组到 dest 引用的目标数组,数组组件的一个子序列被复制下来。被复制的组件的编号等于 length 参数。源数组中位置在 srcPos 到 srcPos+length-1 之间的组件被分别复制到目标数组中的 destPos 到 destPos+length-1 位置。
如果参数 src 和 dest 引用相同的数组对象,则复制的执行过程就好像首先将 srcPos 到 srcPos+length-1 位置的组件复制到一个带有 length 组件的临时数组,然后再将此临时数组的内容复制到目标数组的 destPos 到 destPos+length-1 位置一样。

这样一来,ArrayList的插入就很好理解了,先将 index 及其后面的所有元素复制一份,然后从 index+1 处到destPos+length-1;然后将 index 处的元素替换为新的元素即可!。

4.7 remove方法

在理解了add方法之后,remove方法就很好理解了。

//移除指定位置的元素
public E remove(int index) {
rangeCheck(index);

modCount++;
E oldValue = elementData(index);

//所要移除元素的位置之所以不用 index - 1,
//是因为rangCheck()方法中是判断 index>size,而不是>=
int numMoved = size - index - 1;
if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
                     numMoved);
//将最后一个元素置null,方便垃圾回收器回收
//至于为什么置null能方便GC回收,请阅读“对象生命周期”和JVM垃圾回收机制的相关书籍和文章
elementData[--size] = null; // clear to let GC do its work

return oldValue;

}

//注意for循环,是移除所有满足条件的obj
public boolean remove(Object o) {
//空判断!集合框架中有大量空判断,自己在编程过程中也要注意空判断,
//以免抛出“空指针异常”
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//不进行索引越界检查的快速remove
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

//跳过边界检查的私有移除方法,并且不返回删除的值。
//相比 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
}

4.8 两种toArray

public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a‘s runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
此方法担当基于数组的 API 和基于 collection 的 API 之间的桥梁。

4.9 public Object clone()

//返回这个ArrayList实例的一个浅副本。(元素本身没有被复制。)
//它的modCount将会置0
//todo 不懂对象克隆的原理
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn‘t happen, since we are Cloneable
throw new InternalError(e);
}
}
4.10 其他增删查改操作类似
5、迭代器内部类实现
未完待续...

6、总结
适用范围
和刚学习使用它时一样、ArrayList有其特殊的应用场景,与LinkedList相对应。其优点是随机读取,缺点是插入元素时需要移动大量元素,效率不太高。
关于 null 判断
在写这篇文章前,自己也试着在 LeetCode 上自己实现链表和数组,但是老报空指针异常。估计也是因为没有进行 null判断 引起的。
对象置null,方便 GC 回收垃圾

原文地址:http://blog.51cto.com/13954634/2171759

时间: 2024-10-08 05:31:20

小编教ArrayList源码解读的相关文章

ArrayList 源码解读

ArrayList 源码解读     基于JDk 1.7.0_80 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable ArrayList的底层是使用数组实现的,因为数组的容量是固定的,要实现可变容量List,所以一定存在着容量检测,数组复制等方法. 对象属性 /** * 默认大小 */ pr

Java之ArrayList源码解读(JDK 1.8)

java.util.ArrayList 详细注释了ArrayList的实现,基于JDK 1.8 . 迭代器SubList部分未详细解释,会放到其他源码解读里面.此处重点关注ArrayList本身实现. 没有采用标准的注释,并适当调整了代码的缩进以方便介绍 import java.util.AbstractList; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.

ArrayList源码解读(jdk1.8)

概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayList有个整体认识,再学习它的源码,最后再通过例子来学习如何使用它.内容包括:第1部分 ArrayList简介第2部分 ArrayList数据结构第3部分 ArrayList源码解析(基于JDK1.8)第4部分 ArrayList遍历方式 第1部分 ArrayList介绍

深入理解JAVA集合系列四:ArrayList源码解读

在开始本章内容之前,这里先简单介绍下List的相关内容. List的简单介绍 有序的collection,用户可以对列表中每个元素的插入位置进行精确的控制.用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素.列表通常允许重复的元素,且允许null元素的存放. ArrayList的简单介绍 JDK中这样定义ArrayList:List接口的大小可变数据的实现. 主要有以下特点: 1.有序 2.线程不安全 3.元素可以重复 4.可以存放null值 顾名思义,取名ArrayLis

ArrayList源码解读

在端午节这个节日里,有一个特殊的任务,我带着你一起揭开"ArrayList"的真面目.从成员变量.构造函数.主要方法三部分,对ArrayList有进一步的认识,希望能够帮助你. 一.成员变量 //默认容量 private static final int DEFAULT_CAPACITY = 10; //空数组,当调用无参数构造函数的时候默认给个空数组 private static final Object[] EMPTY_ELEMENTDATA = {}; //真正保存数据的数组 p

ArrayList源码解读(部分)

前言:他山之石,可以攻玉 (1) fastRemove(int i),内部私有方法 private void fastRemove(int index) { //ArrayList内大量使用了此变量,用来验证ArrayList对象结构是否被修改 modCount++; int numMoved = size - index - 1; //index后的元素的个数 if (numMoved > 0) //0<=index<size-1的情况 System.arraycopy(element

jdk1.8.0_45源码解读——ArrayList的实现

jdk1.8.0_45源码解读——ArrayList的实现 一.ArrayList概述 ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小. 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向ArrayList中不断添加元素,其容量也自动增长.自动增长会带来数据向新数组的重新拷贝,因此,如果可预

jdk源码解读之ArrayList

直接上源码: 构造函数:     /**      * Constructs an empty list with an initial capacity of ten.      */     public ArrayList() {     this(10);     } 其实arrayList的本质是一个数据,只不过这个数组的大小可以变化.我们先来看下arraylist的数组是怎么定义的     /**      * Constructs an empty list with the sp

【源码阅读】Java集合 - ArrayList深度源码解读

Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. [email protected] JDK版本 JDK 1.8.0_110 概述总结 ArrayList底层是通过数组实现的:其中capacity表示底层数组的长度,而ArrayList长度由size表示: ArrayList允许存放null元素,也可以查找null所在的index, 比如indexOf(), lastIndex