JDK源码分析之集合02ArrayList
一、前言
有了前一篇对集合类的概述,我们知道ArrayList是属于Collection类系中的一个具体实现类,其特点是长度可以动态改变,集合内部使用数组保存元素。下面我们对源码进行分析。
二、ArrayList源代码分析
2.1 类的继承关系
|
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{} |
说明:可以看出ArrayList类实现了List、RandomAccess、Cloneable和Serializable接口。我们可以看出ArrayList是List类系中的具体类。RandomAccess用于支持快速随机访问,Cloneable用于支持深拷贝。同时还继承了AbstractList抽象类,使Arraylist可以选择性的实现List接口中的方法。在定义的时候使用了泛型来支持类型动态加载。
2.2类的属性
|
/** * The array buffer into which the elements of the ArrayList are stored. The * capacity of the ArrayList is the length of this array buffer. */ // Object数组,用于保存ArrayList的元素,此数组的长度为ArrayList的容量 private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). */ // ArrayList的长度,即此ArrayList元素的个数 private int size; //定义数组最大的size private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; |
说明:ArrayList中定义了一个Object的数组用于保存元素,故归根结底对于ArrayList的所有操作其实都是对数组的操作,因此在随机访问时效率高,而和改变数组大小相关的操作如插入删除操作效率低。
2.3构造方法
|
/** * Constructs an empty list with the specified initial capacity. */ ,抛出异常信息 public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); // 定义一个指定大小的Object数组 this.elementData = new Object[initialCapacity]; } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { // 调用上面的带参构造函数 this(10); } /** * Constructs a list containing the elements of the specified collection, in * the order they are returned by the collection's iterator. */ // 创建一个ArrayList,其中包含参数集合中的所有元素,顺序为迭代器返回的顺序 public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) // 当调用toArray函数返回的类型不为Object数组使,使用Arrays.copyOf()函数完成拷贝 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } |
说明:ArrayList支持三种构造方式:默认大小、指定大小和指定元素。其中使用指定元素或指定大小构造时,所得到的ArrayList对象可以正常使用其所有函数完成增删改等操作。
2.4核心函数分析
、contains(Object o)函数
|
/** * Returns the index of the first occurrence of the specified element in * this list, or -1 if this list does not contain the element. */ // 返回特定元素(包括null)第一次出现的index,如果不存在则返回-1,比较相等时用的是equals方法 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; } public boolean contains(Object o) { 时说明存在 return indexOf(o) >= 0; } |
说明:使用containes或者indexOf方法时需要重写equals方法。
、添加元素的函数
|
/** * Appends the specified element to the end of this list. */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! //扩充数组后将元素添加到数组中 elementData[size++] = e; return true; } |
在添加元素时,首先要确保保存元素的数组能够再添加一个元素。在add函数内部首先调用了ensureCapacityInternal函数;函数代码如下:
|
private void ensureCapacityInternal(int minCapacity) { modCount++; // overflow-conscious code //如果最小需要容量大于数组的长度时,需要对数组长度扩充 if (minCapacity - elementData.length > 0) grow(minCapacity); } |
此函数比较存放元素所需空间和数组的长度,如果长度不够,则调用grow函数扩充数组容量,参数为元素个数。函数代码如下:
|
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); } |
grow函数按照一定的规则获取新的数组长度,并将元素复制到新的数组中,其中如果新的数组长度大于支持的最大值时,会调用hugeCapacity函数获取获取整数的最大值作为数组长度。其中函数源代码如下:
|
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } |
添加元素大致的流程是:调用add方法----------->扩充数组-------------->添加元素
将元素添加到指定index中:
|
public void add(int index, E element) { 或者大于数组长度都将抛出异常 rangeCheckForAdd(index); // 扩充容量 ensureCapacityInternal(size + 1); // Increments modCount!! //将index索引后面的元素全部向后移动一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } |
添加多个元素到集合的末尾
|
public boolean addAll(Collection<? extends E> c) { // 获取集合对应的数组 Object[] a = c.toArray(); // 获取数组长度 int numNew = a.length; // 扩充容量 ensureCapacityInternal(size + numNew); // Increments modCount // 将集合元素添加到数组的末尾 System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } |
添加多个元素到指定index
|
public boolean addAll(int index, Collection<? extends E> c) { // 检查index是否合法 rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; // 扩充数组 ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; // 移动index以后的元素 if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //将元素添加到到数组中 System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; } |
说明:当在中间插入元素时,要对数组元素进行重排序,因此效率要低。
、删除元素函数
删除指定index的元素
|
E elementData(int index) { return (E) elementData[index]; } public E remove(int index) { // 检查index的合法性 rangeCheck(index); modCount++; // 保存要删除的元素 E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved); // 将最后一个元素设置为null elementData[--size] = null; // Let gc do its work return oldValue; } |
删除操作实质上是用后面的元素覆盖掉前面要删除的元素,最后将末尾元素设置为null的操作
删除指定第一次出现的元素
|
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; // Let gc do its work } public boolean remove(Object o) { if (o == null) { //可以remove一个null元素 for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) //以来equals方法,所以元素需要重写equals方法 if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } |
删除操作依赖于equals方法,并且可以删除null;
删除指定集合中的所有元素,该方法依赖containes方法比较元素,因此元素同样需要重新定义equals方法
|
private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. //如果有异常发生,将发生异常后面的元素直接拷贝到数组中 if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } //如果新数组元素个数小于之前数组的长度,将w之后的元素全部设置为null if (w != size) { for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; } // 删除若干个元素 public boolean removeAll(Collection<?> c) { // 调用batchRemove函数 return batchRemove(c, false); } |
收获:在对数组进行增删改查等操作时,应该首先检查index的合法性
删除所有不在参数集合中的元素
|
public boolean retainAll(Collection<?> c) { return batchRemove(c, true); } |
三、总结
ArrayList对于元素的操作底层实现全部是基于对数组的操作实现的,因此具有随机访问效率高,但是插入删除效率低的特点。
JDK源码分析之集合02ArrayList的更多相关文章
- JDK源码分析之集合03LinkedList
一.前言 LinkedList是双向列表,实现方式是使用链表的方式保存元素:除了第一个和最后一个元素外,每一个节点都包含一个指向前一个和指向后一个节点的指针和元素的值.其特点是插入删除效率高,而随机访 ...
- 【JDK】JDK源码分析-Vector
概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...
- 【JDK】JDK源码分析-ArrayList
概述 ArrayList 是 List 接口的一个实现类,也是 Java 中最常用的容器实现类之一,可以把它理解为「可变数组」. 我们知道,Java 中的数组初始化时需要指定长度,而且指定后不能改变. ...
- 【JDK】JDK源码分析-List, Iterator, ListIterator
List 是最常用的容器之一.之前提到过,分析源码时,优先分析接口的源码,因此这里先从 List 接口分析.List 方法列表如下: 由于上文「JDK源码分析-Collection」已对 Collec ...
- 【JDK】JDK源码分析-HashMap(2)
前文「JDK源码分析-HashMap(1)」分析了 HashMap 的内部结构和主要方法的实现原理.但是,面试中通常还会问到很多其他的问题,本文简要分析下常见的一些问题. 这里再贴一下 HashMap ...
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...
- JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable
JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...
- JDK源码分析(三)—— LinkedList
参考文档 JDK源码分析(4)之 LinkedList 相关
- JDK源码分析(一)—— String
dir 参考文档 JDK源码分析(1)之 String 相关
随机推荐
- php require和include区别
require的使用方法如:require("myfile.php"),这个语句通常放在PHP脚本程序的最前面.PHP程序在执行前,就会先读入require()语句所引入的文件,使 ...
- makefile 分析 -- 内置变量及自动变量
makefile 分析1 -p 选项,可以打印出make过程中的数据库, 下面研究一下内置的变量和规则. -n 选项, 只运行,不执行, -d 选项,相当于--debug=a, b(basic), ...
- 最全的Android源码目录结构详解
Android 2.1|-- Makefile|-- bionic (bionic C库)|-- bootable (启动引 ...
- ZooKeeper典型应用场景(转)
ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...
- iphone dev 入门实例2:Pass Data Between View Controllers using segue
Assigning View Controller Class In the first tutorial, we simply create a view controller that serve ...
- C++命名空间 namespace的作用和使用解析
一. 为什么需要命名空间(问题提出) 命名空间是ANSIC++引入的可以由用户命名的作用域,用来处理程序中 常见的同名冲突. 在 C语言中定义了3个层次的作用域,即文件(编译单元).函数和复合语句.C ...
- POJ 1611 The Suspects(并查集,简单)
为什么ACM的题意都这么难懂,就不能说的直白点吗?还能不能好好的一起刷题了? 题意:你需要建一个n的并查集,有m个集合,最后要输出包含0的那个集合的元素的个数. 这是简单并查集应用,所以直接看代码吧! ...
- eclipse的shell相关插件
1.Easy Shell a. 功能 可以在Eclipse IDE里选中一个文件或目录,利用Easy Sehll直接跳转到Sehll窗口,很方便 b. 安装 Help - Install New So ...
- CQL操作
http://docs.datastax.com/en/cql/3.1/pdf/cql31.pdf CQL是Cassandra Query Language的缩写,目前作为Cassandra默认并且主 ...
- Linux系统时间设置(转载)
Linux时钟分为系统时钟(System Clock)和硬件(Real Time Clock,简称RTC)时钟.系统时钟是指当前Linux Kernel中的时钟,而硬件时钟则是主板上由电池供电的时钟, ...