本源代码来自JDK1.8 与1.7、1.6 略有不同
初始大小为10
/** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {};
当初始化容量为0时,就构造这样一个空的Objcet类型数组。
/** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {};
根据注释,这个大概意思就是构造一个空的对象数组,用来与EMPTY_ELEMENTDATA 这个数组进行对比,来确定当第一次向ArrayList中添加数据时,应该如果进行扩容,就是增加多大的容量。暂时还不太好理解这个,往后看就懂了
/** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList中 用来存储数组的对象数组 这个对象是transient 的,即不可被序列化的。
由此可见,ArrayList底层实际是在维护一个对象数组
/** * 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 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access
用于标识当前ArrayList的真实大小
/** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;
ArrayList 共有三个构造方法
根据这个方法的注释,默认的构造方法构造的是构造了一个容量为十的List,但是从源代码来看,实际上在这个地方只是构造了一个空的对象数组,那么为什么说是十呢,在后来就能看懂了。
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
根据指定的容量构造一个空的对象数组。如果容量为负数,抛出异常。应该注意的是,当指定容量为0时,构造时使用的就是前面的全局变量,即为一个空的对象数组。
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
这里首先用到了Collection.toArray()方法进行数组转换。
在这里有一个确认对象类型的检验,如果集合中的对象不是Objec类型,利用Array类的静态方法进行数组的拷贝,这里给出的注释是在调用toArray()方法时可能存在问题,这里的6260652应该BUG的Id号
当转换的数组为空时,ArryaList并没有使用转换的空结果,而是依然使用自己构造的空数组。也许Java程序员认为外来的都是不可信任的吧(个人理解)
<span style="font-size:18px;">public ArrayList(Collection<? extends E> c) {</span> elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
这里把Add方法单独拿出来,这是因为这个Add不仅仅是一个方法,它是一个流程。如果把这个流程理解了,那么整个ArrayList也就懂了。前面遗留的几个不懂得地方在这都有解答。
Add方法有两个
可以看到这个方法永远返回true,所以不应该通过add的返回值来判断是否添加成功。换句话说,Java的大神们在告诉你,我的方法永远都不会出错,你要相信我
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }这个方法一共只有三行,其中第一行的才是关键。这里面是在确定ArrayList的容量能否再多加一个。我们看到ArrayList是个无底洞对吧?想放多少放多,实际上都是源于这句话。而这句话的方法在做什么呢?下面给出源代码
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
从这个地方就可以解答了前面的疑问了,如果使用默认的构造方法,那么elementData 就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当首次插入对象时,取最大值的时候一定是10,再经过后面的扩容,实际上就相当于构造了一个长度为10的对象数组。
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }ensureExplicitCapacity 这个方法第一行先不管是干嘛的,看后面的代码,很简单吧! 就是判断需要的容量和现在的用来存储的对象的数组(elementData)那个更大,如果现在已有的不够大了,那就扩容呗! grow(minCapacity)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
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); }
private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
这段代码进行了扩容,可以看到扩容通过newCapacity = oldCapacity + (oldCapacity >> 1);这段代码实现,实际上就是将新的容量变为原来容量的1.5倍。
但是需要对扩容后的容量进行检验。共有两种可能,
1得到的容量比需要的容量小,为什么会有这种情况呢,溢出了呗。
2得到的容量超过了规定的最大容量,进入hugeCapacity中,如果需要的容量小于0,抛出内存溢出异常,如果需要的容量比规定的最大容量大,那么最大容量只能是 Integer.MAX_VALUE。
最后将elementData 通过Array的复制拷贝方法进行了扩容。
由此就完成了整个添加新的元素的过程。从这个过程可以看出,ArrayList也并不是无限大的,它指定了一个最大容量 是Integer.MAX_VALUE - 8,实际最大只能是Integer.MAX_VALUE这个值了。
这个方法是在指定的位置插入元素 element
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }第一行没什么好说的,肯定是检验index是否合法
private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }第二行与前面add方法一致,检验容量并扩容
第三行是arraycopy的方法,在这里面实现的就是将原来的数组从index位置开始,每个元素向后移一位。这是一个本地方法,看不到源代码啦
第四行比较简单啦,就是把要插入的元素放在他应该出现的位置上。
从指定的位置删除元素。实现原理就是把index后面的数据都向前移位。O(n)
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; }
private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }(1)第一行就是检验index的返回,有人会问为什么不检验负数的情况。本人亲自试验了一下,如果index是负数的话,第一行是可以正确执行的,但是在elementData(index)这样地方就会抛出java.lang.ArrayIndexOutOfBoundsException: -1即 数组越界异常, 其实个人觉得,如果没有第一句 检验,仍然会抛出数组越界异常。只不过多了第一行的话,抛出的异常就是java.lang.IndexOutOfBoundsException: Index: 2, Size: 2,而且会打印出错误信息。又有哪个Java程序员不喜欢看到更详细的异常信息呢?
(2) 计算移动量,如果要删除的元素不是最后一位,就要进行移动。
(3)将移动后的数组末尾赋值null。这里有一句注释,让GC去回收。这是一种释放内存的方式。但是我们不能依赖这种方法, 因为我们永远不知道GC如何能去回收它。
删除指定的元素,仅删除符合条件的第一个元素,如果不存在,返回false 即删除失败
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; }
在这if else语句块中,都用了fastRemove(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 }
在集合中,删除与Collection中元素相等的元素。
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); }
接下来就是batchRemove(c, false) 这个方法了。源码里虽然没有给出注释,但从第一个if就可以看出,这个方法是用于返回与给定c相同或不同的元素,而且是全部。
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; }下面来分析一下这段代码。首先对集合中的对象数组进行了一次复制,然后将数组中的元素依次与collection进行对比。try{}块的结果就是得到了一个数组, 数组前w位元素或与collection相同(complement=true的时候)或不同(complement=false)。
下面来看finally,先来看第二个if{}代码块,它的作用就是把数组中w以后的元素全部变为null值,让gc来回收。
现在回到finally的第一个if中,看条件(r != size),似乎永远不会满足这个条件吧。上面的for循环一直r++啊,可是别忘了,c.contains(elementData[r])这句话是有可能抛出异常的,如果一旦类型不匹配,就会抛出异常 进入finally中。
这个方法,如果没有删除任何数据,那么将会返回false。
现在我们再来看return batchRemove(c, false);这行的含义吧。它指定了第二个参数为false,所以在batchRemove()保留下来的都是与collection中不同的元素。相同的元素都被删除了。这样就达到了removeAll(Collection<?> c)的目的。
迭代器可谓是操作ArrayList的神器,在ArrayList内部通过内部类的形式实现了Iterator接口,完成对ArrayList的操作。先让我们来内部一 Itr的源码。
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
这个类并不长,让我们来分析一下
int cursor; 我们知道iterator是始终向前走的,就是这个游标始终在++的原因
int lastRe 标识了最后一个返回的元素的索引位置,-1代表这个元素不存在
int expectedModCount = modCount; 这个非常重要,它用来校验在使用iterator期间,是否存在非Iterator的操作对ArrayList进行了修改。
这个方法很简单,但是可以看到在这个内部类中,几乎所有方法都在调用它。它所做的工作就是校验在使用iterator期间,是否存在非Iterator的操作对ArrayList进行了修改。
在ArrayList中,有很多操作都修改了一个变量,modCount。每次进行操作,modCount都在++。前面一直不理解这个有什么用,在这看到了它的用意。Iterator的游标特性决定了它对ArrayList中元素在这一时刻的位置很敏感,如果当前游标在index位置,而有其他操作在index-1的位置上插入了一个元素,那么调用iterator的next()方法,返回的还是当前这个元素,这样就乱了。为了避免这个情况发生,需要在这个期间把ArrayList“锁住“。它并没有实现真正的锁,所以采用了这个校验的方式。
返回当前游标位置的元素,这里面第一个if判断的条件很有意思。因此游标前移,当移动到一个不存在数据的地方,它抛出了异常。而并没有返回null。这就是我们为什么在使用iterator的时候不能用(null==iterator.next())来判断的原因。而是在要每次循环开始的时候判断iterator.hasNext()。
删除lastRe 所标识位置的元素。我们可以把它理解为当前元素。在try前面有一个校验,保证元素没有被改动过,要不就删错了。
在try{}语句块中,首先删除了lastRe标识的元素,然后让游标指向了这个位置。我们知道在删除元素以后,这个位置有了新的元素,这样再次调用next()的时候不会出现空指针异常,更不会跳过一个元素。
最后expectedModCount = modCount;这句相当于释放了锁。也是在表示,在我Iterator的地盘,只有我能够去修改mod,别人动了就不行!
set将指定位置的元素替换
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }get 获取指定位置的的元素
public E get(int index) { rangeCheck(index); return elementData(index); }
private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
这三个方法功能很相近
第一个是查看ArrayList中是否含有指定元素
第二个是返回指定元素的第一个出现的索引位置
第三个返回指定元素的最后一个出现的索引位置
这是一组依赖的方法,实际上执行的是同一段代码。
public boolean contains(Object o) { return indexOf(o) >= 0; }
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 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; }
拷贝,重写了Objec类的拷贝方法,ArrayList的拷贝是浅拷贝。
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) 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(e); } }
排序方法,这个方法用到的是Array类的静态排序方法
public void sort(Comparator<? super E> c) { final int expectedModCount = modCount; Arrays.sort((E[]) elementData, 0, size, c); if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } modCount++; }
上面介绍了ArrayList中常用的方法,第一次较为认真地读源代码,确实发现了不少秘密。
(1) ArrayList是用Object数组来实现了,所谓的动态扩容,实际上就是在不断地产生新的数组,然后进行复制。
(2)在使用ArrayList插入大量元素时,应该有选择的申请一个容量,而不是使用默认的容量。扩容也是会消耗时间的
(3) ArrayList不是线程安全的,它的操作中没有使用同步方法。如果想使用线程安全的,可以用Vector,也可以使用Collections.synchronizedList来创建线程安全的list。