`

【Java源码解读】ArrayList

    博客分类:
  • Java
 
阅读更多

源码来源:OpenJDK 10

 

简述

ArrayList实现了List接口;大小可变;允许有null元素;支持调整其内部用户存储数据的数组大小;与Vector比较相似,除了ArrayList不同步(多线程不安全)。

 

关于性能

  • size, isEmpty, get, set, iterator, listIterator等方法的时间复杂度都是O(1);add的时间复杂度是可变常量(amortized constant time)。(与LinkedList相比,ArrayList的常量因子更小一点)。除了这些操作,其它操作的一般都是线性复杂度O(n)。
  • 当需要向一个ArrayList中添加大量元素时,可先调用ensureCapacity来增大ArrayList内部数组的容量,以减少重新分配内存空间次数

关于线程安全/同步

  • 在多线程场景中使用ArrayList时,如果至少有一个线程会改变ArrayList实例的结构,那么就必须做好同步管理。
  • 添加或删除元素,或显式地改变ArrayList内部的数组,都是会改变ArrayList实例结构的操作。但是仅仅设置其中某个元素的值,不会改变实例结构。
  • 一般可以对拥有ArrayList实例的外部对象进行同步化访问改造,从而达到线程安全的目的。
  • 如果找不到合适的(外部)对象进行同步访问处理,可调用Collections.synchronizedList方法,将ArrayList实例包装成一个线程安全的新对象,再在新对象上执行多线程操作。这个包装操作最好在创建ArrayList实例时就执行,以免在创建和同步化包装这两个操作之间发生误用。
  • 通过ArrayList的iterator方法和listIterator方法得到的迭代器都采用fail-fast机制。也就是说,这样的iterator被创建出来后,如果ArrayList实例的内部结构被改变,iterator就会抛出异常ConcurrentModificationException(除非这个改变是iterator自身的add或remove方法操作的)。
    • 但是并不能保证不同步的并发修改一定会引起上述异常。所以可以将此异常用于辅助排障,但不能让正常处理逻辑依赖于这个fail-fast机制。

构造方法

ArrayList()

将内部数组初始化为一个大小为0数组

 

ArrayList(int initialCapacity)

如果initialCapacity大于0,则将内部数组初始化为一个大小为initialCapacity的数组

如果initialCapacity等于0,则将内部数组初始化为一个大小为0的数组

否则,抛出异常IllegalArgumentException

 

ArrayList(Collection<? extends E> c)

如果c中有元素,则将c中的元素复制到一个新数组中,并将该新数组作为ArrayList的内部数组

否则,将内部数组初始化为一个大小为0的数组

 

关键方法

newCapacity(int minCapacity)

计算内部数组扩容的目标容量

 

private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return minCapacity;
    }
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}

先取现容量的1.5倍,作为新容量

如果该新容量比现容量大

如果该新容量小于等于最大允许的容量MAX_ARRAY_SIZE,则使用该新容量

否则

如果minCapacity小于0,则抛出异常OutOfMemoryError

否则

如果minCapacity大于MAX_ARRAY_SIZE,就用Integer.MAX_VALUE作为新容量

否则,就用MAX_ARRAY_SIZE作为新容量

否则

如果内部数组是默认的0元素数组,就选DEFAULT_CAPACITY(10)和minCapacity中的较大者作为新容量

如果minCapacity小于0,则抛出异常OutOfMemoryError

否则,将minCapacity作为新容量

MAX_ARRAY_SIZE

值为Integer.MAX_VALUE-8

因为某些VM会在数组头部预留一些空间不放置用户数据,所以需要减8,以防超出VM的限制。

 

其它方法

 trimToSize

将内部数组的容量调整为当前有效元素的数量。可通过此方法来减小ArrayList实例所占用的内存。

该操作会将字段modCount增加1。modCount是父类AbstractList中的字段,它记录了实例内部结构改变的次数。该字段主要由iterator用于实现fail-fast机制。iterator会判定该字段的值是不是其期望的值;如果不是,就会抛出异常。如果AbstractList的子类需要提供fail-fast机制的iterator,则可在会改变其实例内部结构的操作中将该字段增加1;如果不需要提供这样iterator,则可以忽略该字段。

 

ensureCapacity(int minCapacity)

确保内部数组可容纳minCapacity所指定数量的元素

如果minCapacity比内部数组的长度大,且内部数组不是空数组,或minCapacity比初始容量DEFAULT_CAPACITY(10)大],则增大内部数组。具体策略在newCapacity(int minCapacity)中实现

 

public void ensureCapacity(int minCapacity) {
    if (minCapacity > elementData.length
        && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
            && minCapacity <= DEFAULT_CAPACITY)) {
        modCount++;
        grow(minCapacity);
    }
}

 

size()

返回list中元素的个数

 

isEmpty()

判断list中是否存在元素

 

contains(Object o)

判断list中是否存在指定的元素。具体实现就是判断indexOf(o)的返回值是否大于等于0

 

indexOf(Object o)

返回指定元素在list中首次出现的索引。如果不存在目标元素,则返回-1。具体实现就是先判断o是不是null:

如果是null,就挨个检查是否有元素是null,一旦找到就返回相应的索引;

如果不是null,就用o.equals方法挨个检查是否有元素相等,一旦找到就返回相应的索引。

 

lastIndexOf(Object o)

返回指定元素在list中最后一次出现的索引。如果不存在目标元素,则返回-1。具体实现与indexOf(Object o)类似。不同之处是挨个检查的方向相反。

 

clone()

创建一个浅拷贝的副本。也就是说,拷贝的是ArrayList实例对象这个壳,它内部存放的元素不会被拷贝,只是将这些元素的索引复用到了新的list。具体实现就是先执行super.clone()得到一个新的ArrayList实例;再创建一个存放了各元素(引用)的数组,作为新list实例的内部数组;再将modCount的值置为0

 

toArray()

返回一个包含各元素的数组,且这些元素的顺序与list的相同。具体做法就是创建一个list内部数组的副本。因为返回的数组对象是一个全新的对象,所以对此新数组的修改不会影响到原list

 

toArray(T[] a)

与toArray()类似,该方法是泛型形式

如果list的元素能全部放到a数组中,那么各元素(引用)会被放到a中,并返回a。否则,将会创建一个新的数组,数组大小就是当前list的size,并将元素(引用)放到该新数组中,再返回该新数组。

另外,如果a的容量比list中元素个数多,那么a[size]会被置为null。这种做法有助于判断a数组中有效元素的个数。但是需要使用者知道list中不存在null元素。

 

get(int index)

获取指定索引的元素。该方法会先检查index是否大于等于size。如果是,就会抛出异常IndexOutOfBoundsException

 

set(int index, E element)

将指定的元素放到指定的索引处(替换原元素),并返回原元素。该方法也会先检查索引是否越界

 

add(E e)

将指定的元素追加到list的末尾,并返回true。该方法会先将modCount增加1。内部数组已经存满元素,会先调用grow(size+1)进行扩容,再放置新元素。size也会增加1

 

add(int index, E element)

将指定元素插入到list的指定索引处,索引处的原元素及之后的元素都会被向后移动一格(索引值加1)。该方法会先检查索引是否有效。modCount和size都会增加1

 

remove(int index)

移除指定索引处的元素,并返回该元素,索引后的各元素都会被向前移动一格(索引值减1)。该方法会先检查索引是否有效。modCount会增加1,size会减1。此外,内部数组的末尾元素后的一个会被置为null,此做法与toArray(T[] a)类似

 

remove(Object o)

移除list中第一个与指定对象o相等的元素,并返回true(成功移除)或false(未找到相等的元素)。如果o为null,就用‘==null’检查是否相等;如果o不为null,就用o.equals方法。如果找到了相等的元素,就执行与remove(int index)相同的内部逻辑,进行移除操作

 

clear()

移除list中的所有元素。具体实现就是将内部数组的每个有效元素(引用)置为null。modCount会增加1,size会置为0

 

addAll(Collection<? extends E> c)

将指定集合中的所有元素追加到list末尾,并返回true(有新元素加入)或false(没有新元素加入)。具体实现是,先把集合c中的元素(引用)复制到新数组中,再把这些元素(引用)添加到内部数组中。在这个过程中,如果集合c没有元素,会直接返回false;如果原元素个数和新元素个数的和超过了当前内部数组的容量,就会先扩容。另外,无论c中是否有元素,modCount都会增加1。size也会更新(c中有多少个元素就增加多少)

 

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

将指定集合中的所有元素追加到指定索引处,并返回true(有新元素加入)或false(没有新元素加入)。该方法会先检查索引是否有效。具体实现与addAll(Collection<? extends E> c)类似。如果指定索引处原来有元素,则从该索引开始向后的其它元素都会被移动最终list的末尾。这与add(int index, E element)类似

 

 

removeRange(int fromIndex, int toIndex)

移除指定索引区间的元素,包括fromIndex的元素,不包括toIndex的元素。toIndex及之后的元素会被前移。该方法会先检查索引是否有效。modCount会增加1

 

removeAll(Collection<?> c)

移除指定集合c中含有的元素,并返回true(有元素被移除)或false(没有元素被移除)。注意:remove(Object o)是移除list中首个相等的元素,而此方法时移除所有相等的元素(不是首个)。因为此方法会对每个元素进行相等性检查(c.contains)。具体实现时,会先找到list中首个相等的元素,再遍历剩余的元素。在这遍历过程中,如果后续有元素是c所不包含,则把该元素(引用)赋到之前的索引处。这个“之前的索引”初始值是首个相等元素的索引,随着遍历过程中需保留元素的增加,该“索引”的值也一同增加。另外,如果在后续遍历过程中发生异常,会先hold住异常,把后方未遍历的元素直接提到前面,然后再重新抛出异常。如果后续遍历一切正常,则modCount增加的值就是被移除的元素个数,并清理现场,即调整size并将新空出来的内部数组空间值置为null

 

listIterator(int index)

获取一个从指定索引开始的ListIterator<E>。该方法会先检查索引是否有效

 

listIterator()

获取一个从0索引开始的ListIterator<E>。这是listIterator(int index)的一个特例

 

iterator()

获取一个从0索引开始的Iterator<E> 

 

subList(int fromIndex, int toIndex)

获取一个指定索引区间的子list(ArrayList.SubList),包括fromIndex处的元素,不包括toIndex处的元素。这个子list只是原list的一个视图,对子list改动会调用原list的方法

 

forEach(Consumer<? Super E> action)

对每个元素应用指定的操作。

 

spliterator()

返回一个Spliterator<E>(可分割迭代器,用于并行遍历)

 

removeIf(Predicate<? super E> filter)

移除所有符合filter的元素。具体实现与removeAll(Collection<?> c)类似,也是先找到第一个符合要求的元素,再遍历后续元素进行移除。但是因为filter具体操作是不受控制的,为了安全,会先遍历所有后续元素,并在一个数组中记录符合filter的元素的索引信息,然后再执行真正的移除操作,最后清理现场。modCount会增加1,size也会改变。

 

removeIf(Predicate<? super E> filter, int i, final int end)

移除指定索引区间内所有符合filter的元素,包括i,不包括end。这是removeIf(Predicate<? super E> filter)的一个特例

 

replaceAll(UnaryOperator<E> operator)

将所有元素替换为将operator处理过后得到的结果。modCount会增加1

 

sort(Comparator<? super E> c)

使用指定的比较器,对所有元素进行排序,排序结果反映在原内部数组

 

其它

  • 可以阅读源码,体会一下使用final局部变量的场景。
  • ArrayList中存在部分可以改进的代码。
分享到:
评论

相关推荐

    Java中的ArrayList的底层源码解读、LinkedList、Vector的区别介绍

    阅读建议:结合代码进行阅读,在阅读的时候进行代码的比对,以及做笔记,结合自己不懂的地方反复观看,源码解读较为枯燥,文档中的步骤给的都很详细,需要大家结合源码一步一步的跟着步骤进行阅读,

    java源码解读-JavaSource:Java源码解读

    "JavaSource:Java源码解读"项目旨在帮助开发者深入探索Java的内部工作原理,从而更好地运用和优化代码。在这个项目中,我们可以看到一系列针对Java源码的分析与讨论,主要聚焦于JavaSource-master这一核心部分。 ...

    java源码解读-java-src:java源码解读

    Java源码解读是Java开发人员深入理解平台工作原理和编程模型的重要途径。在这个"java-src:java源码解读"项目中,我们可以探索Java的核心库,包括JVM(Java虚拟机)、集合框架、并发机制、I/O流、网络编程等多个关键...

    java源码文档src

    `java.text`和`java.util.Locale`包提供了国际化和本地化的支持,源码解读能帮助开发者为不同地区和语言的用户提供定制服务。 总之,Java源码文档src是Java开发者不可或缺的学习资源,它揭示了Java平台的内在工作...

    ArrayList源码分析

    ArrayList是Java集合框架中的一员,它是List接口的一个实现,提供了动态数组的功能。ArrayList的主要特点是它在内存中以数组的形式存储元素,因此对于随机访问元素,它的性能非常高效。本篇文章将深入探讨ArrayList...

    java源码解读-JavaAPI:jdk源码解读分析

    本篇文章将对Java API的部分关键组件进行源码解读,帮助读者深入理解其工作原理。 1. **对象创建与内存管理**: - `Object`类:所有Java类的基类,包含了如`clone()`, `equals()`, `hashCode()`等方法。理解`...

    java源码解读-ITG-JavaBook01:Java面试高频源码解读

    《Java源码解读-ITG-JavaBook01: Java面试高频源码解读》是一部针对Java程序员面试准备的深入学习资料。在这个项目中,我们将会探索Java语言的一些核心概念和常用库的源代码,帮助开发者更好地理解Java的内部机制,...

    java面试题_源码解读(3题)

    在Java面试中,源码解读是一项重要的能力,它考察了开发者对Java语言底层实现的理解以及问题解决的能力。这里我们将深入探讨三道常见的Java面试题,它们涵盖了基础、并发和集合框架等方面,帮助你提升对Java源码的...

    本仓库记录了我的Java学习进阶之路,涵盖了Java基础、JDK源码、JVM中的重要知识,附有代码和博客讲解,旨在提供一个Java在线共享学习平台,帮助更多的Java学习入门者进阶 .zip

    作者目录Java基础Java基础学习(1)——引用Java基础学习(2)——注解Java基础学习(3)——泛型Java基础学习(4)——动态代理《Java多线程核心技术》读书笔记JDK源Java集合框架源码解读(1)——ArrayList、LinkedList和...

    Java SE 源码 Java SE 源码

    源码解读能揭示反射在动态类型语言特性中的作用。 7. **集合框架**:Java集合框架包括数组、列表、队列、映射等数据结构,如`ArrayList`、`HashMap`等。源码揭示了这些数据结构的实现细节,对于优化和定制自己的...

    java源码解读-jdk_reading:java源码日常阅读与注解

    《Java源码解读——深入理解JDK》 Java作为一门广泛应用的编程语言,其源码是许多开发者探索技术原理、提升编程技能的重要资源。"java源码解读-jdk_reading:java源码日常阅读与注解"项目,旨在帮助开发者深入理解...

    清华妹子的Java仓库(进阶学习路线)

    Java集合框架源码解读(1)——ArrayList、LinkedList和Vector Java集合框架源码解读(2)——HashMap Java集合框架源码解读(3)——LinkedHashMap Java集合框架源码解读(4)——WeakHashMap Java集合框架源码解读

    Java学习笔记(源码)

    【Java学习笔记(源码)】是一份详细记录了Java编程语言学习过程的资源集合,包含实际的源代码示例。这份笔记旨在帮助初学者和有一定经验的开发者深入理解和掌握Java语言的核心概念、语法以及常见应用。以下是笔记中...

    交易平台系统JSP源码Java源码

    总的来说,要理解和学习这个交易平台系统的JSP源码,你需要具备扎实的Java基础,理解Web开发的核心概念,熟悉JSP、Servlet和数据库交互,同时,能够解读项目结构和运行说明。通过深入研究,你可以从中学到如何构建一...

    Java相关技术总结,包括redis,MySQL,RabbitMq,面试题总结,源码解读

    源码解读是提升技术水平的重要途径。例如,深入理解HashMap和ConcurrentHashMap的实现,可以让我们更好地利用这些数据结构,避免性能瓶颈;阅读ArrayList和LinkedList的源码,有助于我们选择合适的数据结构以优化...

    Java底层知识点、源码解读,技术栈相关原理知识点、工具解读最佳实践、功能点实战,问题排查,开发技巧等

    Java作为一门广泛使用的编程语言,其底层知识点和源码解读对于深入理解并优化代码性能至关重要。本主题将探讨以下几个方面: 1. **Java虚拟机(JVM)**: JVM是Java程序运行的基础,它负责字节码的解释执行,内存...

    java毕设源码范例和详细说明(由浅入深,深度解读在资料后半部分).docx

    Java毕设源码范例和详细说明(由浅入深,深度解读在资料后半部分)是基于Java语言的毕设项目,旨在提供一个通俗易懂的Java毕设源码范例,并附上详细的代码说明。本项目的技术复杂度适中,适合初级开发者学习和理解。...

    Java rt.jar 源码分析

    2. 集合框架:深入源码可以让我们明白`ArrayList`、`HashMap`等数据结构的实现细节,包括它们的时间复杂度和空间占用,这对于优化代码性能非常有帮助。 3. 异常处理:rt.jar中包含了所有标准的异常类,如`...

    java8源码-JavaRobot:Java学习笔记,JavaLearningNote

    源码解读 String源码系列 List源码系列 ArrayList LinkedList CopyOnWriteArrayList Vector Map源码系列 HashMap LinkedHashMap ConcurrentHashMap TreeMap Hashtable Set源码系列 HashSet LinkedHashSet TreeSet ...

    java项目源码超市管理系统素材

    "java项目源码超市管理系统素材"这一标题揭示了这是一个使用Java编程语言编写的项目,其核心功能是实现超市管理系统的操作。它可能是为了教学或实践目的而设计的一个实例,提供了完整的源代码,供学习者研究、理解和...

Global site tag (gtag.js) - Google Analytics