众所周知,ArrayList的底层是数组,作为经常使用的集合,作为一位java开发 对于此集合想必不陌生。相对于HashMap的源码,ArrayList源码相对简单。言归正传,让我们简略的走进ArrayList的源码之中。
我们从new 一个ArrayList开始,
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};transient Object[] elementData;
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
先简单说明下: transient 关键字标记的成员变量不参与序列化过程。当我们new一个ArrayList的时候,elementData指向了一个Object的数组。
当我们调用add方法的时候,
private static final int DEFAULT_CAPACITY = 10;
public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }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); }
我们来分析下此时调用ArrayList add方法的过程,此时通过上面我们可以明显看出来,首先它会先去检查此时数组的大小,ArrayList如果new出来的时候没有指定集合的大小或者指定的size小于10 那么第一次调用add方法的时候 会设置size为10 。接着如果此时集合的不够存储的话,那么集合会去扩容。从源码我们可以看出来ArrayList 扩容是在原来数组大小基础上增加1.5倍。用一个新的数组 复制旧数组数据。所以在平时代码的书写过程中,我们要特别注意下,如果使用ArrayList 的过程中,知道该申请多大的大小的数组 那么在new的过程中尽量写上去。 尽量避免数组的扩容。我们接着说,添加的时候,会指定数组下一个坐标的值为传入的参数。同时size,会+1。 我们调用arrlist.size()方法的时候,ArrayList 给出来的是真正集合的长度。如果new 的时候 给予了size为10 然后只添加了一个元素,那么此时的size还是只是1.但此时申请的数组长度是10.
下面我们来看下 ArrayList的 contains的方法。我们有时候会判断一个集合是否存在某个值。分析下源码:
public boolean contains(Object o) { return indexOf(o) >= 0; }
从源码我们可以看出来,其实集合也是遍历数组,通过equals的方法去比对,如果存在返回数组的坐标,否则返回坐标-为1. 再拿坐标跟0比较,小于0则返回false。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; }
下面看下get方法。
public E get(int index) { rangeCheck(index); return elementData(index); }
private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
其实就是通过数组坐标获取值。在获取的时候会判断下坐标是否大于数组大小 大于则抛出角标越界异常。
关于remove方法。
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; }
从源码中可以得知,public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
说明:arraycopy 参数: src-源数组。 srcPos-源数组中的起始位置。 dest-目标数组。 destPos 目标数据中开锁位置 length要复制的长度
举个列子吧 比如集合有元素 0,1,2,3,4,5 那么remove坐标2 则先调用arraycopy方法得到0,1,3,4,5,5 然后将坐标5置 为null --size,这样这个集合就remove掉了元素。
特别强调 --size 和size--是有区别的 例如:此处
elementData[--size] = null 变成 elementData[size--] = null
会抛出角标越界的异常。因为此时的size--坐标是6 。
--size 先减再赋值,size--先赋值再减
ArrayLsit还有好多方法 暂时就不说了 有兴趣的话大家可以自己去看源码。