集合框架源码学习——ArrayList
来源:互联网 发布:制作编程语言 编辑:程序博客网 时间:2024/06/15 13:49
本文所用jdk版本是jdk1.7.0_79版本。
一.ArrayList类的定义
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
上面这一段代码是ArrayList类的定义,从定义上我们可以看出,ArrayList 继承了AbstractList抽象类,实现了List接口,Cloneable接口,Seriallizable接口。在这里我要说一点的就是,AbstractList继承了AbstractCollection抽象类,同时也实现了List接口,所以,我不太清楚如果ArrayList如果不实现List接口,会造成什么影响,这里我猜想应该不会造成什么影响,再一次实现List接口,可能是因为为了使意义更加清楚吧。关于AbstractList抽象类与List接口中又有哪些方法,感兴趣的话可以翻源码去看一下,这里就过多做介绍了。
二.ArrayList类的成员变量
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to * DEFAULT_CAPACITY when the first element is added. */ private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;
上面4个是ArrayList的成员变量,从JDK注释我们可以事先了解一下他们的意义。
1.DEFAULT_CAPACITY是默认初始容量,这个默认初始容量的意思就是,ArrayList在创建对象时,默认的容量为10,如果容量不够,后面会有容量的扩展,关于扩展的方法,后面会有。
2.EMPTY_ELEMENTDATA是定义无参实例时,默认的参数集合。
3.elementData,用于存储ArrayList元素的缓冲数组。ArrayList的容量就是elementData数组的长度。任何空的同时elementData与EMPTY_ELEMENTDATA相等的ArrayList当第一个元素添加后,容量都将会扩展至默认容量。这里的emementData被transient修饰,该关键字的意思是该成员变量不用初始化。
4.size,设置ArrayList中包含的元素的数量。
三.ArrayList构造器
/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }上述代码中可以看到,ArrayList共有三个构造器。
1.ArrayList第一个构造器中,传入一个int类型的参数,用于实例化ArrayList时,初始化elementData数组的长度。
2.ArrayList第二个构造器是一个无参构造器,仅执行一次elementData = EMPTY_ELEMENTDATA
3.ArrayList第三个构造器,传入了一个Collection类型的集合,用于实例化ArrayList时,将参数中的集合加入到实例化的ArrayList中。该构造器中,向几个成员变量进行了赋值,符合了几个成员变量存在的意义。
四.方法详解
4.1 trimToSize()方法
/** * Trims the capacity of this <tt>ArrayList</tt> instance to be the * list's current size. An application can use this operation to minimize * the storage of an <tt>ArrayList</tt> instance. */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = Arrays.copyOf(elementData, size); } }该方法是用来将ArrayList的容量削减至与长度相等,从源代码中可以看到,所谓削减是将elementData的数组按照实际size的长度重新复制。看到这里的小伙伴相信绝大多数人应该可以理解了吧,其实ArrayList本质上就是用数组存储元素的,而且其值最终就是存储在elementData数组中的。所谓容量,就是指数组的最大长度,所谓size就是指数组中存储的元素的个数。
该方法中还有一个modCount++,这里,modCount变量是继承自AbstractList,我特意到源码中搜了一下,AbstractCollection抽象类与List接口中均没有该参数,可见是AbstractList抽象类中扩展的变量。具体作用可以看注释。
4.2 ensureCapacity(int minCapacity) 方法
/** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != EMPTY_ELEMENTDATA) // any size if real element table ? 0 // larger than default for empty table. It's already supposed to be // at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }如有必要,增加此ArrayList实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
源代码中,首先定义一个minExpand,如果ArrayList实例不是空的,则赋值为0,如果是空的,则赋值为默认容量大小。再判断传入参数是否大于minExpand,如果大于,则执行ensureExplicitCapacity(minCapacity)方法。根据源码可知,该方法是一个私有方法,我们可以看一下它的源代码:
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
该源代码中,首先判断minCapacity是否大于elementData.length,如果大于,则执行grow(minCapacity)方法。这也是一个私有方法,我们再深入其中来看一下:
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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); }
增加容量,以确保它可以容纳参数所指定的元素的个数。通过我们该方法,终于看到了该功能的底层是究竟如何处理的。
(1)首先定义一个oldCapacity存储原有的元素长度。(2)定义新的容量是旧容量的1.5倍。(3)判断外部传入的参数是否大于新的容量,如果大于,则外部传入的参数就为新的容量,如果不大于,则容量就扩展原容量的1.5倍。(4)判断新的容量是否超过MAX_ARRAY_SIZE(数组最大长度),如果超过,则通过hugeCapacity重新赋值。关于这个方法,做的什么事情,相信小伙伴很容易就能看懂。
总的来说,该方法的的作用是:在不考虑到达最大极限的时候。一般容量扩展最小是原容量的1.5倍,在1.5被仍不满足时,才会直接扩展至参数传入的容量值。
4.2 size()方法
public int size() { return size; }该方法就是返回size的值,即ArrayList中的元素的个数。
4.3 isEmpty()方法
public boolean isEmpty() { return size == 0; }该方法就是判断ArrayList是否为空。
4.4 contans(Object o)方法
public boolean contains(Object o) { return indexOf(o) >= 0; }该方法返回boolean类型,方法体中执行了一个判断indexOf(Object o) >= 0 的方法。下面看一下indexOf方法中的源码:
/** * Returns the index of the first occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ 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; }该方法中,可以看出,是循环执行检索,将 o 同 elementData数组循环比较,如果有相等的,则返回该索引。如果没有,则返回-1。它的上一层即可判断出ArrayList是否包含 o 参数的值。
4.5 lastIndexOf(Object o)方法
/** * Returns the index of the last occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the highest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ 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; }返回此ArrayList中最后出现 o元素的索引,如果没有,则返回-1。
源码中,可以看出来这里是以倒序的形式进行查询,与indexOf功能类似,只是查询顺序颠倒。
4.6 clone()方法
/** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The * elements themselves are not copied.) * * @return a clone of this <tt>ArrayList</tt> instance */ public Object clone() { try { @SuppressWarnings("unchecked") ArrayList<E> v = (ArrayList<E>) 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(); } }这里从单词意思和源码都可以看出,它其实是做了一次复制,将原ArrayList赋值一份,并且赋值出来的是一份将modCount归0的ArrayList。
4.7 toArray() 方法
/** * Returns an array containing all of the elements in this list * in proper sequence (from first to last element). * * <p>The returned array will be "safe" in that no references to it are * maintained by this list. (In other words, this method must allocate * a new array). The caller is thus free to modify the returned array. * * <p>This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this list in * proper sequence */ public Object[] toArray() { return Arrays.copyOf(elementData, size); }该方法是将ArrayList转化成数组。
4.8 toArray(T[] a)方法
@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; }该方法与上述类似,增加了一个泛型。
4.9 get(int index) 方法
/** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { rangeCheck(index); return elementData(index); }代码中可以看出,该方法返回index位置的元素。在返回之前执行了一次判断,如果index >=size,则抛出IndexOutOfBoundsException,这里没有做index为负的判断,而是在获取数组值的时候,抛出ArrayIndexOutOfBoundsException,个人感觉这里也可以同时做index < 0的判断。
4.10 set(index, E element)方法
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }该方法是将ArrayList的index索引位的值,替换成element元素,并且返回老的元素。
4.11 add(E e)方法
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
该方法是向ArrayList中添加一个元素。
从源码中可以看出,在添加之前,首先执行了一次ensureCapacityInternal(size + 1) 方法,我们在前面介绍过类似的一个方法,而该方法的内部也调用了上面介绍过的那个方法,在这里就不再详细描述了,总之是在添加元素之前,判断是否需要扩展容量,如果有必要的话,就扩展一次容量。
该方法我们可以看出,在添加元素的时候,代码内部自动帮我们判断和扩展容量,不需要我们人工去做。
4.12 (1) remove(int index)方法
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); 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 return oldValue; }
该方法是用于删除索引位置的元素。
首先判断index值是否合法,否则抛出IndexOutOfBoundsException;然后执行删除,size减去1,最后返回删除的元素。
这里我们可以看到它删除的过程,实际上是将index+1索引位以及后面的所有元素,向前覆盖一位,再将最后一位置为null。这种方式比较低效,所以如果List需要不断地执行插入删除等操作,不建议使用ArrayList。
4.12(2) remove(Object o)
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; }从代码中可以看出,该方法是执行删除o元素第一次出现的位置,如果删除成功,则返回true。删除方法为faseRemove(int index),并没有调用上面的remove(int index)方法,我们可以看一下该方法的源码:
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 }与上面的remove相比,少了个校验,少了个返回删除的元素。因为调用此方法之前,已经执行了校验,不用重复校验,并且删除的元素就是参数o元素,所以也不用返回。所以fastRemove比remove少了两个环节,节省了一定的消耗。
4.13 clear() 方法
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }代码中可以看出,该方法执行一次循环,将elementData数组循环置为null,将size赋值为0。
4.14(1) addAll(Collection<? extends E> c)方法
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; }该方法是将参数传入的集合一次性全部加入到此ArrayList中。
代码中可以看到,首先将参数中的集合c转为数组,必要的话扩展一次容量,再将参数转过的数组赋值到elementData中。
4.14(2)addAll(int index, Collection<? extends E> c)方法
public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; 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; }该方法中比上一个方法,多了一个index参数,我们可以从代码上去理解。
首先执行一次index校验;再将集合c转化为数组a;下面是如果有必要,执行一次扩容;下面就是关键的执行插入的两点:(1),将index后面的元素向后挪,挪的位置就是c的长度;(2)再将转化的数组a,赋值到第一步腾出来的位置。
这里可以看出,该方法执行的插入,也是十分低效的,因为数组本身的原因,所以,这里也可以得出上面的结论,如果执行插入和删除是在List中间做的话,建议不要用ArrayList。
4.15 removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) { return batchRemove(c, false); }该方法体中只执行了一次batchRemove(c,false)方法,可以看一下该方法的源码,做了哪些事情:
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; } if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }上面的方法逻辑有点复杂,我们来一点一点剖析。
(1)首先执行一次final Object[] elementData = this.elementData;这里就是定义一个局部变量,不在ArrayList本身上修改,如果出现异常则不会影响到原数据。
(2)执行一次for循环,这里的作用就是循环执行contains方法,将elementData中所有不能在c中找到的元素从0的位置开始重新赋值给elementData。这样能保证的是,所有不需要删除的元素,都被移到了最前面,w就是不需要删除的元素的个数。
(3)接下来将elementData中,w以后的元素置为null,size重新赋值成w。
分析到这里,相信大家已经知道整个方法的作用了,就是将ArrayList中包含了c集合中的元素删除掉,并且我们可以看出来,该方法也是非常低效的,因为删除元素的同时,对于保留的所有元素执行了大量的重新赋值。
由于时间问题,暂时先写一半,后续将在晚一些时间补上
- 集合框架源码学习——ArrayList
- 透过源码学习集合框架之ArrayList
- 集合框架—ArrayList集合
- java核心之集合框架——ArrayList源码分析
- java集合框架03——ArrayList和源码分析
- java集合框架03——ArrayList和源码分析
- Java 集合框架源码分析(一)——ArrayList
- Java 集合框架源码分析(一)——ArrayList
- java集合框架02——ArrayList和源码分析
- [转载]java集合框架——ArrayList和源码分析
- Java集合框架(2)—ArrayList源码分析
- 【深入学习java集合框架】ArrayList源码学习
- java集合框架学习—ArrayList的实现原理
- 集合学习--ArrayList 源码初探
- 集合源码学习(三):ArrayList
- Java集合——ArrayList源码详解
- Java集合—ArrayList的源码解读
- 集合框架——ArrayList练习
- 状态压缩DP总结【POJ3254】【POJ1185】【POJ3311】【HDU3001】【POJ2288】【ZOJ4257】【POJ2411】【HDU3681】
- 任务一
- Eclipse简单分包65535问题
- list -- set方法——替换列表中指定位置的元素
- 第十二章 构建API
- 集合框架源码学习——ArrayList
- Web.xml配置详解之context-param
- linux下安装nodejs的 webpack的问题解决
- HDU1540-Tunnel Warfare(线段树区间合并)
- Winidows下版本控制Git + TortoiseGit安装使用
- Android系统休眠---踩坑的路
- 乐驾 -- 5
- intellj idea 如何设置类头注释和方法注释
- iOS中改变键盘的高度 监听键盘