源码来源: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中存在部分可以改进的代码。
相关推荐
阅读建议:结合代码进行阅读,在阅读的时候进行代码的比对,以及做笔记,结合自己不懂的地方反复观看,源码解读较为枯燥,文档中的步骤给的都很详细,需要大家结合源码一步一步的跟着步骤进行阅读,
"JavaSource:Java源码解读"项目旨在帮助开发者深入探索Java的内部工作原理,从而更好地运用和优化代码。在这个项目中,我们可以看到一系列针对Java源码的分析与讨论,主要聚焦于JavaSource-master这一核心部分。 ...
Java源码解读是Java开发人员深入理解平台工作原理和编程模型的重要途径。在这个"java-src:java源码解读"项目中,我们可以探索Java的核心库,包括JVM(Java虚拟机)、集合框架、并发机制、I/O流、网络编程等多个关键...
`java.text`和`java.util.Locale`包提供了国际化和本地化的支持,源码解读能帮助开发者为不同地区和语言的用户提供定制服务。 总之,Java源码文档src是Java开发者不可或缺的学习资源,它揭示了Java平台的内在工作...
ArrayList是Java集合框架中的一员,它是List接口的一个实现,提供了动态数组的功能。ArrayList的主要特点是它在内存中以数组的形式存储元素,因此对于随机访问元素,它的性能非常高效。本篇文章将深入探讨ArrayList...
本篇文章将对Java API的部分关键组件进行源码解读,帮助读者深入理解其工作原理。 1. **对象创建与内存管理**: - `Object`类:所有Java类的基类,包含了如`clone()`, `equals()`, `hashCode()`等方法。理解`...
《Java源码解读-ITG-JavaBook01: Java面试高频源码解读》是一部针对Java程序员面试准备的深入学习资料。在这个项目中,我们将会探索Java语言的一些核心概念和常用库的源代码,帮助开发者更好地理解Java的内部机制,...
在Java面试中,源码解读是一项重要的能力,它考察了开发者对Java语言底层实现的理解以及问题解决的能力。这里我们将深入探讨三道常见的Java面试题,它们涵盖了基础、并发和集合框架等方面,帮助你提升对Java源码的...
源码解读能揭示反射在动态类型语言特性中的作用。 7. **集合框架**:Java集合框架包括数组、列表、队列、映射等数据结构,如`ArrayList`、`HashMap`等。源码揭示了这些数据结构的实现细节,对于优化和定制自己的...
《Java源码解读——深入理解JDK》 Java作为一门广泛应用的编程语言,其源码是许多开发者探索技术原理、提升编程技能的重要资源。"java源码解读-jdk_reading:java源码日常阅读与注解"项目,旨在帮助开发者深入理解...
Java集合框架源码解读(1)——ArrayList、LinkedList和Vector Java集合框架源码解读(2)——HashMap Java集合框架源码解读(3)——LinkedHashMap Java集合框架源码解读(4)——WeakHashMap Java集合框架源码解读
【Java学习笔记(源码)】是一份详细记录了Java编程语言学习过程的资源集合,包含实际的源代码示例。这份笔记旨在帮助初学者和有一定经验的开发者深入理解和掌握Java语言的核心概念、语法以及常见应用。以下是笔记中...
总的来说,要理解和学习这个交易平台系统的JSP源码,你需要具备扎实的Java基础,理解Web开发的核心概念,熟悉JSP、Servlet和数据库交互,同时,能够解读项目结构和运行说明。通过深入研究,你可以从中学到如何构建一...
源码解读是提升技术水平的重要途径。例如,深入理解HashMap和ConcurrentHashMap的实现,可以让我们更好地利用这些数据结构,避免性能瓶颈;阅读ArrayList和LinkedList的源码,有助于我们选择合适的数据结构以优化...
Java作为一门广泛使用的编程语言,其底层知识点和源码解读对于深入理解并优化代码性能至关重要。本主题将探讨以下几个方面: 1. **Java虚拟机(JVM)**: JVM是Java程序运行的基础,它负责字节码的解释执行,内存...
Java毕设源码范例和详细说明(由浅入深,深度解读在资料后半部分)是基于Java语言的毕设项目,旨在提供一个通俗易懂的Java毕设源码范例,并附上详细的代码说明。本项目的技术复杂度适中,适合初级开发者学习和理解。...
2. 集合框架:深入源码可以让我们明白`ArrayList`、`HashMap`等数据结构的实现细节,包括它们的时间复杂度和空间占用,这对于优化代码性能非常有帮助。 3. 异常处理:rt.jar中包含了所有标准的异常类,如`...
源码解读 String源码系列 List源码系列 ArrayList LinkedList CopyOnWriteArrayList Vector Map源码系列 HashMap LinkedHashMap ConcurrentHashMap TreeMap Hashtable Set源码系列 HashSet LinkedHashSet TreeSet ...
"java项目源码超市管理系统素材"这一标题揭示了这是一个使用Java编程语言编写的项目,其核心功能是实现超市管理系统的操作。它可能是为了教学或实践目的而设计的一个实例,提供了完整的源代码,供学习者研究、理解和...
- `ArrayList`, `LinkedList`, `HashSet`, `HashMap`等集合类的使用,展示了Java集合框架的强大功能,包括动态扩容、迭代器、泛型等。 7. **多线程**: - `Thread`类的子类化,`Runnable`接口的实现,线程同步...