文档章节

Java容器类源码-ArrayList的最全的源码分析

Ryane
 Ryane
发布于 2016/07/25 17:31
字数 3909
阅读 252
收藏 3

写在前面

本文是针对Java 1.8的源代码进行解析的,可能会和其他版本有所出入。

一、继承和实现

  • **继承:**AbstractList

  • **实现:**List<E>, RandomAccess, Cloneable, Serializable接口

源代码

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
}

二、全局变量

1. 默认容量

private static final int DEFAULT_CAPACITY = 10;

2. 空的对象数组

private static final Object[] EMPTY_ELEMENTDATA = {};

3.默认的空数组

// 无参构造函数创建的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

4.存放数据的数组的缓存变量,不可序列化

transient Object[] elementData;

5.数组的大小

private int size;

三、构造方法

1.带有容量initialCapacity的构造方法

源码解释:

public ArrayList(int initialCapacity) {
     // 如果初始化时ArrayList大小大于0
    if (initialCapacity > 0) {
          // new一个该大小的object数组赋给elementData
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) { // 如果大小为0
          // 将空数组赋给elementData
        this.elementData = EMPTY_ELEMENTDATA;
    } else { // 小于0
          // 则抛出IllegalArgumentException异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

2.不带参数的构造方法

源码解释:

public ArrayList() {
     // 直接将空数组赋给elementData  
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

3.带参数Collection的构造方法

源码解释:

参数c为一个Collection,Collection的实现类大概有以下几种常用类型:

  • List:元素可以重复的容器
  • Set: 元素不可重复的容器
  • Queue:结构是一个队列,先进先出

这个构造方法的意思是,将一个Collection实现类的对象转换为一个ArrayList,但是c容器装的内容

必须为ArrayList装的内容的子类。例如,将一个装了String内容的HashSet转换为装了String内容的

ArrayList,使得ArrayList的大小和值数组都是HashSet的大小和值数组。具体实现如下代码,首先调

用c(Collection的具体实现类)的toArray方法,具体大家可以看各个实现类的toArray方法,但是大

概意思都是将c容器转换为object类型的数组,因为它们的返回值都是object[]。之于下面的两个判断

是当得到的elementData的类名不是Object类名的时候或者是长度为0的时候才会执行。

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 {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}  

四、方法

1.trimToSize()

**说明:**将ArrayList的容量设置为当前size的大小。首先需要明确一个概念,ArrayList的size就是ArrayList的元素个数,length是ArrayList申请的内容空间长度。ArrayList每次都会预申请多一点空间,以便添加元素的时候不需要每次都进行扩容操作,例如我们的元素个数是10个,它申请的内存空间必定会大于10,即length>size,而这个方法就是把ArrayList的内存空间设置为size,去除没有用到的null值空间。这也就是我们为什么每次在获取数据长度是都是调用list.size()而不是list.length()。

**源码解释:**首先modCount是从类 java.util.AbstractList 继承的字段,这个字段主要是为了防止在多线程操作的情况下,List发生结构性的变化,什么意思呢?就是防止一个线程正在迭代,另外一个线程进行对List进行remove操作,这样当我们迭代到最后一个元素时,很明显此时List的最后一个元素为空,那么这时modCount就会告诉迭代器,让其抛出异常 ConcurrentModificationException。

如果没有这一个变量,那么系统肯定会报异常ArrayIndexOutOfBoundsException,这样的异常显然不是应该出现的(这些运行时错误都是使用者的逻辑错误导致的,我们的JDK那么高端,不会出现使用错误,我们只抛出使用者造成的错误,而这个错误是设计者应该考虑的),为了避免出现这样的异常,定义了检查。

(引用自:郭无心,详情可以看他在知乎的回答:https://www.zhihu.com/question/24086463/answer/64717159)。

public void trimToSize() {
    modCount++;
     // 如果size小于length
    if (size < elementData.length) {
         // 重新将elementData设置大小为size
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}  

2.size()

**说明:**返回ArrayList的大小

**源码解释:**直接返回size

public int size() {
    return size;
}

3.isEmpty()

**说明:**返回是否为空

**源码解释: **直接返回判断size==0

public boolean isEmpty() {
    return size == 0;
}

4.indexOf(Object o)

**说明:**对象o在ArrayList中的下标位置,如果存在返回位置i,不存在返回-1

**源码解释:**遍历ArrayList的大小,比较o和容器内的元素,若相等,则返回位置i,若遍历完都不相等,返回-1

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;
}  

5.contains(Object o)

**说明:**是否包含对象o

**源码解释:**调用indexOf()方法得到下标,存在则下标>=0,不存在为-1,即只要比较下标和0的大小即可。

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

6.lastIndexOf(Object o)

**说明:**返回容器内出现o的最后一个位置

**源码解释:**从后向前遍历,得到第一个出现对象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;
}  

7.clone()

**说明:**返回此 ArrayList 实例的浅表副本。

源码解释:

public Object clone() {
    try {
         // 调用父类(翻看源码可见是Object类)的clone方法得到一个ArrayList副本
        ArrayList<?> v = (ArrayList<?>) super.clone();
         // 调用Arrays类的copyOf,将ArrayList的elementData数组赋值给副本的elementData数组
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
         // 返回副本v
        return v;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
 }  

8.toArray()

**说明:**ArrayList 实例转换为。

**源码解释:**直接调用Arrays类的copyOf。

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

9.toArray(T[] a)

**说明:**将ArrayList里面的元素赋值到一个数组中去

**源码解释:**如果a的长度小于ArrayList的长度,直接调用Arrays类的copyOf,返回一个比a数组长度要大的新数组,里面元素就是ArrayList里面的元素;如果a的长度比ArrayList的长度大,那么就调用System.arraycopy,将ArrayList的elementData数组赋值到a数组,然后把a数组的size位置赋值为空。 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; }

10.rangeCheck(int index)

**说明:**测试index是否越界

源码解释:

private void rangeCheck(int index) {
     // 如果下标超过ArrayList的数组长度
    if (index >= size)
         // 抛出IndexOutOfBoundsException异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}  

11.get(int index)

**说明:**获取index位置的元素

**源码解释:**先检查是否越界,然后返回ArrayList的elementData数组index位置的元素。

public E get(int index) {
     // 检查是否越界
    rangeCheck(index);
     // 返回ArrayList的elementData数组index位置的元素
    return elementData(index);
}  

12.set(int index, E element)

**说明:**设置index位置的元素值了element,返回该位置的之前的值

源码解释:

public E set(int index, E element) {
     // 检查是否越界  
    rangeCheck(index);
     // 调用elementData(index)获取到当前位置的值
    E oldValue = elementData(index);
     // 将element赋值到ArrayList的elementData数组的第index位置
    elementData[index] = element;
    return oldValue;
}  

13.ensureCapacityInternal(int minCapacity)

**说明:**得到最小扩容量

源码解释:

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
         // 获取默认的容量和传入参数的较大值
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

14.ensureExplicitCapacity(int minCapacity)

**说明:**判断是否需要扩容

源码解释:

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // 如果最小需要空间比elementData的内存空间要大,则需要扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
} 

15.grow()方法

**说明:**帮助ArrayList动态扩容的核心方法

源码解释:

// MAX_VALUE为231-1,MAX_ARRAY_SIZE 就是获取Java中int的最大限制,以防止越界  
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // 获取到ArrayList中elementData数组的内存空间长度
    int oldCapacity = elementData.length;
    // 扩容至原来的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组, 
    // 不够就将数组长度设置为需要的长度
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 判断有没超过最大限制
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
    // 并将elementData的数据复制到新的内存空间
    elementData = Arrays.copyOf(elementData, newCapacity);
}  

16.add(E e)

**说明:**添加元素e

源码解释:

public boolean add(E e) {
     // 扩容
    ensureCapacityInternal(size + 1);  
    // 将e赋值给elementData的size+1的位置。
    elementData[size++] = e;
    return true;
}  

17.add(int index, E element)

**说明:**在ArrayList的index位置,添加元素element

源码解释:

public void add(int index, E element) {
    // 判断index是否越界  
    rangeCheckForAdd(index);
     // 扩容
    ensureCapacityInternal(size + 1);  
     // 将elementData从index位置开始,复制到elementData的index+1开始的连续空间
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
     // 在elementData的index位置赋值element
    elementData[index] = element;
     // ArrayList的大小加一  
    size++;
}  

18.remove(int index)

**说明:**在ArrayList的移除index位置的元素

源码解释:

public E remove(int index) {
     // 判断是否越界  
    rangeCheck(index);
    modCount++;
     // 读取旧值  
    E oldValue = elementData(index);
     // 获取index位置开始到最后一个位置的个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
         // 将elementData数组index+1位置开始拷贝到elementData从index开始的空间
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
     // 使size-1 ,设置elementData的size位置为空,让GC来清理内存空间
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}  

19.remove(Object o)

**说明:**在ArrayList的移除对象为O的元素,跟indexOf方法思想基本一致

源码解释:

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;
}  

20.clear()

**说明:**设置全部元素为null值,并设置size为0。

**源码解释:**可见clear操作并不是从空间内删除,只是设置为null值,等待垃圾回收机制来回收而已,把size设置为0,以便我们不会浏览到null值的内存空间。

public void clear() {
    modCount++;
    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;
    size = 0;
}  

21.addAll(Collection<? extends E> c)

**说明:**将Collection c的全部元素添加到ArrayList中

源码解释:

public boolean addAll(Collection<? extends E> c) {
     // 将c转换为数组a
    Object[] a = c.toArray();
     // 获取a占的内存空间长度赋值给numNew
    int numNew = a.length;
     // 扩容至size + numNew
    ensureCapacityInternal(size + numNew);  // Increments modCount
     // 将a的第0位开始拷贝至elementData的size位开始,拷贝长度为numNew
    System.arraycopy(a, 0, elementData, size, numNew);
     // 将size增加numNew  
    size += numNew;
     // 如果c为空,返回false,c不为空,返回true
    return numNew != 0;
}  

22.addAll(int index, Collection<? extends E> c)

**说明:**从第index位开始,将c全部拷贝到ArrayList

源码解释:

public boolean addAll(int index, Collection<? extends E> c) {
     // 判断index大于size或者是小于0,如果是,则抛出IndexOutOfBoundsException异常
    rangeCheckForAdd(index);
     // 将c转换为数组a
    Object[] a = c.toArray();
    int numNew = a.length;
     // 扩容至size + numNew
    ensureCapacityInternal(size + numNew);  // Increments modCount
      // 获取需要添加的个数
    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}  

24.batchRemove(Collection<?> c, boolean complement)

**说明:**根据complement值,将ArrayList中包含c中元素的元素删除或者保留

源码解释:

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
     // 定义一个w,一个r,两个同时右移   
    int r = 0, w = 0;
    boolean modified = false;
    try {
         // r先右移
        for (; r < size; r++)
              // 如果c中不包含elementData[r]这个元素
            if (c.contains(elementData[r]) == complement)
                  // 则直接将r位置的元素赋值给w位置的元素,w自增
                elementData[w++] = elementData[r];
    } finally {
        // 防止抛出异常导致上面r的右移过程没完成
        if (r != size) {
              // 将r未右移完成的位置的元素赋值给w右边位置的元素
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
              // 修改w值增加size-r
            w += size - r;
        }
        if (w != size) {
            // 如果有被覆盖掉的元素,则将w后面的元素都赋值为null
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
              // 修改size为w
            size = w;
            modified = true;
        }
    }
    return modified;
}  

25.removeAll(Collection<?> c)

**说明:**ArrayList移除c中的所有元素

源码解释:

public boolean removeAll(Collection<?> c) {
     // 如果c为空,则抛出空指针异常
    Objects.requireNonNull(c);
     // 调用batchRemove移除c中的元素
    return batchRemove(c, false);
}  

26.retainAll(Collection<?> c)

**说明:**和removeAll相反,仅保留c中所有的元素

源码解释:

public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
     // 调用batchRemove保留c中的元素
    return batchRemove(c, true);
}  

27.iterator()

**说明:**返回一个Iterator对象,Itr为ArrayList的一个内部类,其实现了Iterator<E>接口

public Iterator<E> iterator() {
    return new Itr();
}  

28.listIterator()

**说明:**返回一个ListIterator对象,ListItr为ArrayList的一个内部类,其实现了ListIterator<E> 接口

源码解释:

public ListIterator<E> listIterator() {
    return new ListItr(0);
}  

29.listIterator(int index)

说明:返回一个从index开始的ListIterator对象

源码解释:

public ListIterator<E> listIterator(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index);
    return new ListItr(index);
}  

30.subList(int fromIndex, int toIndex)

**说明:**根据两个参数,获取到一个子序列

源码解释:

public List<E> subList(int fromIndex, int toIndex) {
     // 检查异常
    subListRangeCheck(fromIndex, toIndex, size);
     // 调用SubList类的构造方法
    return new SubList(this, 0, fromIndex, toIndex);
}

五、内部类

(1)private class Itr implements Iterator<E>
(2)private class ListItr extends Itr implements ListIterator<E>
(3)private class SubList extends AbstractList<E> implements RandomAccess
(4)static final class ArrayListSpliterator<E> implements Spliterator<E>

ArrayList有四个内部类,

其中的Itr是实现了Iterator接口,同时重写了里面的hasNext(),next(),remove()等方法;

其中的ListItr继承Itr,实现了ListIterator接口,同时重写了hasPrevious(),nextIndex(), previousIndex(),previous(),set(E e),add(E e)等方法,所以这也可以看出了Iterator和ListIterator的区别,就是ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。具体可以参考http://blog.csdn.net/a597926661/article/details/7679765。

其中的SubList继承AbstractList,实现了RandmAccess接口,类内部实现了对子序列的增删改查等方法,但它同时也充分利用了内部类的优点,就是共享ArrayList的全局变量,例如检查器变量modCount,数组elementData等,所以SubList进行的增删改查操作都是对ArrayList的数组进行的,并没有创建新的数组。

最后一个比较个人比较少接触,大家需要自行度娘。

End

笔者技术真的是一般般,写这个为了加深理解的同时给害怕看源代码的朋友一点鼓励,所以笔者在写的过程中有查阅很多资料来努力减少错误,但是如有错漏之处,希望大神们指出,我会第一时间修改,以免误人子弟,也希望和笔者一样基础不够好的朋友不要畏惧看源码,源码看起来并不会很难,而且多看源代码会对Java更深刻的理解。

© 著作权归作者所有

共有 人打赏支持
Ryane
粉丝 39
博文 22
码字总数 55318
作品 0
程序员
【目录导航】JAVA零基础进阶之路

【JAVA零基础入门系列】(已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day6 Java字符串 Day7 Java输入与输出...

MFrank
06/21
0
0
【集合类型的并发】Collections.synchronizedList

1 :关注要点,为什么在有synchroniezed方法的同时会出现 Collections.synchronizedList 2 :知识背景: 您可能需要了解java Synchronized方法的加锁的各种机制,包括如何上锁,锁对象 3 : ...

止静
2014/08/22
0
2
一份关于 Java、Kotlin 与 Android 的学习笔记

JavaKotlinAndroidLearn 这是一份关于 Java 、Kotlin 、Android 的学习笔记,既包含对基础知识点的介绍,也包含对一些重要知识点的源码解析,笔记的大纲如下所示: Java 重拾Java(0)-基础知...

叶应是叶
08/08
0
0
Android--面试中遇到的问题总结(三)

《Android 开发工程师面试指南 LearningNotes 》,作者是陶程,由梁观全贡献部分。大家可以去知乎关注这两位用心的少年。这份指南包含了大部分Android开发的基础、进阶知识,不仅可以帮助准备...

sealin
2017/02/22
0
0
带你了解源码中的 ThreadLocal

这次想来讲讲 ThreadLocal 这个很神奇的东西,最开始接触到这个是看了主席的《开发艺术探索》,后来是在研究 ViewRootImpl 中又碰到一次,而且还发现 Android 中一个小彩蛋,就越发觉得这个东...

请叫我大苏
07/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Boot 入门 - 进阶篇(4)- REST访问(RestTemplate)

经常需要发送一个GET/POST请求到其他系统(REST API),通过JDK自带的HttpURLConnection、Apache HttpClient、Netty 4、OkHTTP 2/3都可以实现。 HttpClient的使用:http://rensanning.iteye.c...

onedotdot
26分钟前
2
0
Wi-Fi也有版本号了!

据Solidot消息,行业组织 Wi-Fi 联盟宣布当前的版本 Wi-Fi 802.11ac 重命名为 Wi-Fi 5,而下一个版本 802.11ax 重命名为 Wi-Fi 6,之前的版本 802.11n 改名为 Wi-Fi 4。 Wi-Fi 标准之前使用单...

linux-tao
27分钟前
3
0
项目经验不丰富、技术不突出的程序员怎么打动面试官?

前言 相信不少的程序员都有过类似的困惑:如果我没有大型的项目经历,也不能靠技术征服面试官,那我要怎么才能给面试官留下一个好印象呢? 按照本人的面试经验来说,面试主要看几点:项目经验...

Mamba1
38分钟前
2
0
MyBatis 源码分析----MyBatis 整体架构概要说明

MyBatis整体架构 MyBatis的整体架构分为三层1:基础支持层,2:核心处理层,3:接口层 1:基础支持层: 1-1反射模块: 该模块对Java 原生的反射进行了良好的封装,提供了更加简洁易用的API ,...

西瓜1994
43分钟前
7
0
如何让 J2Cache 在多种编程语言环境中使用

现在的系统是越来越复杂了,不仅仅是功能复杂,系统结构也非常复杂,而且经常在一个系统里包含几种不同语言编写的子系统。例如用 JavaScript 做前端开发、用 Java/PHP 等等做后端,C/C++/Go ...

红薯
45分钟前
46
1

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部