本篇文章主要是学习后的知识记录,存在不足,或许不够深入,还请谅解。
目录
- ArrayList源码解析
- ArrayList中的变量
- ArrayList构造函数
- ArrayList中的add方法
- ArrayList中的add(插入指定位置)方法
- ArrayList中的get方法
- ArrayList中的remove(int index)方法
- ArrayList中的remove(Object o)方法
- ArrayList中的clear方法
- CopyOnWriteArrayList源码解析
- CopyOnWriteArrayList变量
- CopyOnWriteArrayList的构造函数
- CopyOnWriteArrayList的两个add方法
- CopyOnWriteArrayList的两个get方法
- CopyOnWriteArrayList的remove方法
ArrayList源码解析
ArrayList中的变量
通过上图可以看到,ArrayList中总共有7个变量,下面看下每个变量的作用:
/** * 序列化 */ private static final long serialVersionUID = 8683452581122892189L; /** * 默认的初始容量 */ private static final int DEFAULT_CAPACITY = 10; /** * 用于在构造函数中,初始化一个空的数组 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 用于无参构造函数中,给一个空的数组 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 这个是真正存储元素的数组 */ transient Object[] elementData; // non-private to simplify nested class access /** * size,是用来记录arrayList中,即elementData里的元素的大小,数组中共有多少个元素 * @serial */ private int size; /** * 这个参数,是数组所允许的最大长度,要是超出了,可能会报OutOfMemoryError异常 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList构造函数
ArrayList中有三个构造函数:
/** * 指定一个具有初始容量的空数组,可以自己指定容量的大小 * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException 指定的初始容量值若是负值,会抛出这个异常 */ public ArrayList(int initialCapacity) { //容量大于0,就把elementData初始化一个具有initialCapacity大小的数组 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; //要是指定的容量值是0,就初始化一个空数组 } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; //指定的容量值小于0.抛出不合法参数异常 } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 无参构造函数,会指定一个初始容量为10的数组 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * 构造一个包含指定元素的列表集合,按照集合返回它们的顺序 * * @param c 传入的集合,会把集合中的元素放入数组中 * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); //数组中元素的长度不为0,就把c的元素copy到elementData中 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 { //要是元素个数是0个,就初始化一个空数组 this.elementData = EMPTY_ELEMENTDATA; } }
ArrayList中的add方法
/** * add方法,会把一个元素添加到数组的末尾 * * @param e 传入的元素 * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { //size是目前数组存的元素的个数,传入size+1,即需要的数组长度 //需要的数组长度,是可以再去容纳一个元素 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
//这里传入的是size+1 private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
//这个方法是判断数组是否是一个空数组,要是使用无参构造函数初始化的arrayList,那么返回值就是10 private static int calculateCapacity(Object[] elementData, int minCapacity) { //要是elementData是一个空的数组 //判断需要的数组长度和默认容量值哪个大,返回最大的那个 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } //要是elementData数组中有元素,直接返回minCapacity return minCapacity; }
//这里是,判断数组是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { //记录数组被修改的次数 modCount++; //要是需要的数组长度大于目前数组的长度,就需要扩容了(即数组的长度是否可以存入下一个元素) if (minCapacity - elementData.length > 0) grow(minCapacity); }
/** * 扩容方法 * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { //旧的数组的长度 int oldCapacity = elementData.length; //需要扩容的数组的长度,即10*1.5=15 int newCapacity = oldCapacity + (oldCapacity >> 1); //要是扩容后的数组的长度还是小于需要的最小容量,那么就把需要的最小容量给newCapacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //要是扩容后的数组长度比最大的数组容量还大,就需要控制了 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //把扩容之后的数组copy到长度为newCapacity的数组中 elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { //参数小于0.抛异常 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //判断需要的数组容量和数组最大容量哪个大, //需要的数组容量比数组最大容量还大,就返回int的最大值 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
图式:
ArrayList中的add(插入指定位置)方法
先看下 System.arraycopy:
// src 源数组 // srcPos 源数组要复制的起始位置 // dest 要赋值到的目标数组 // destPos 目标数组放置的起始位置 // length 复制的长度 System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length);
继续add方法:
/** * 将元素插入数组中指定的位置 * @param index 指定的索引值 * @param element 需要插入的元素 * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { //参数校验,判断要插入的下标是否越界 rangeCheckForAdd(index); //这个和add(E e)是一样的,判断数组是否需要扩容等 ensureCapacityInternal(size + 1); // Increments modCount!! //把插入index位置的原有元素以及该元素后面的元素,向右移动 System.arraycopy(elementData, index, elementData, index + 1, size - index); //把元素插入到index的位置 elementData[index] = element; //数组元素的个数加1 size++; }
/** * A version of rangeCheck used by add and addAll. */ private void rangeCheckForAdd(int index) { //要是插入的位置比数组中元素的个数大,或者插入的位置值小于0,就抛出下标越界异常 if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * Constructs an IndexOutOfBoundsException detail message. * Of the many possible refactorings of the error handling code, * this "outlining" performs best with both server and client VMs. */ private String outOfBoundsMsg(int index) { return "Index: "+index+", Size: "+size; }
ArrayList中的get方法
get方法相对而言就比较简单些:
/** * 根据下标获取指定位置的元素 * * @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) { //参数校验,若index比数组中元素的size还大,就抛异常 rangeCheck(index); //返回对应的元素 return elementData(index); }
/** * 在获取数组元素之前,需要进行数据校验 * 若传入的参数不在指定的数组索引范围内,就抛异常 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
ArrayList中的remove(int index)方法
/** * 移除列表中指定位置的元素,然后把移除元素后面的元素向左移动 * * @param index 需要移除元素的索引值 * @return 移除的元素 * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { //参数校验,校验index的值是否在索引所允许的范围内 rangeCheck(index); //列表的修改次数加一 modCount++; //先查出对应index位置出的元素,赋值给oldValue E oldValue = elementData(index); //把移除的元素后面的所有元素向左移动 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); //把数组最后一个索引位置数据设置为null elementData[--size] = null; // clear to let GC do its work //返回移除的元素数据 return oldValue; }
ArrayList中的remove(Object o)方法
/** * * 从列表中删除指定元素的第一个匹配项 * * @param o 需要移除的元素 * @return <tt>true</tt> if this list contained the specified element */ public boolean remove(Object o) { //若元素是null,移除第一个匹配为null的元素 if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } //若元素不为null,就移除第一个匹配到的元素 } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
/* * Private remove method that skips bounds checking and does not * return the value removed. */ 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 }
注意:由于ArrayList是线程不安全的,所以不要在遍历中去对ArrayList做修改,否则会出现错误
ArrayList中的clear方法
/** * 清除列表中的所有元素 */ public void clear() { //列表修改次数加一 modCount++; // clear to let GC do its work 清除所有元素,垃圾回收 //通过遍历把所有元素设置为Null for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
CopyOnWriteArrayList源码解析
通过上图可以看到copyOnWrite的实现方式,这种方式适用于读极多,写极少的情况,而且如果数据量巨大,在copy之后的一瞬间,内存占用增加,也会引发问题。CopyOnWriteArrayList是线程安全的。
CopyOnWriteArrayList变量
/** 可重入锁 */ final transient ReentrantLock lock = new ReentrantLock(); /** * 数组,只能通过getArray和setArray操作 */ private transient volatile Object[] array; /** * Gets the array. Non-private so as to also be accessible * from CopyOnWriteArraySet class. */ final Object[] getArray() { return array; } /** * Sets the array. */ final void setArray(Object[] a) { array = a; }
CopyOnWriteArrayList的构造函数
/** * 创建一个空的列表 */ public CopyOnWriteArrayList() { setArray(new Object[0]); } /** * 构造一个包含指定元素的列表集合,按照集合返回它们的顺序 * * @param c 传入的集合 * @throws NullPointerException if the specified collection is null */ public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); } /** * 创建包含给定数组副本的列表 * * @param toCopyIn the array (a copy of this array is used as the * internal array) * @throws NullPointerException if the specified array is null */ public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
CopyOnWriteArrayList的两个add方法
/** * 添加一个元素到列表的最后面 * * @param e 需要添加到列表中的元素 * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { //初始化锁 final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { //获取数组中的元素 Object[] elements = getArray(); //获取数组的长度 int len = elements.length; //把elements数组copy到长度为len + 1的newElements数组中,即新的数组长度增加1 Object[] newElements = Arrays.copyOf(elements, len + 1); //然后把元素加到数组的末尾 newElements[len] = e; //set数组的元素为添加之后的数组 setArray(newElements); return true; } finally { //解锁 lock.unlock(); } } /** * 向指定的索引位置加入元素,加入位置后面的元素需要向右移位 * * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { //初始化一个锁 final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { //获取数组中的元素 Object[] elements = getArray(); //获取数组的长度 int len = elements.length; //如果传入的索引值不在数组所允许的范围内,就抛异常 if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); Object[] newElements; int numMoved = len - index; //如果插入的索引大小和数组长度一样,那么直接插入到数组末尾 if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { //设置新数组的长度比之前的数组长度大一位,即可以插入一个元素 newElements = new Object[len + 1]; //先copy elements中index索引之前的元素到newElements System.arraycopy(elements, 0, newElements, 0, index); //再把elements中index之后的元素已经index中的元素copy到index右边,即右移 System.arraycopy(elements, index, newElements, index + 1, numMoved); } //把元素放入到指定的索引处 newElements[index] = element; //设置数组的引用为新的数组 setArray(newElements); } finally { //解锁 lock.unlock(); } }
向指定索引处插入元素图解:
CopyOnWriteArrayList的两个get方法
get方法比较简单,不做赘述。
private E get(Object[] a, int index) { return (E) a[index]; } /** * {@inheritDoc} * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { return get(getArray(), index); }
CopyOnWriteArrayList的remove方法
/** * 移除列表中指定索引位置的元素,并把后续的元素向左移动 * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { //初始化锁 final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { //获取数组中的元素 Object[] elements = getArray(); //获取数组的长度 int len = elements.length; //获取索引处原先的旧值 E oldValue = get(elements, index); int numMoved = len - index - 1; //如果要移除的是最后一位直接移除 if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { //新的数组,长度比旧的数组少一位 Object[] newElements = new Object[len - 1]; //同样的,//先copy elements中index索引之前的元素到newElements System.arraycopy(elements, 0, newElements, 0, index); //再把elements中index+1之后的元素向左移位 System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } //返回移除后的元素 return oldValue; } finally { //解锁 lock.unlock(); } }
在CopyOnWriteArrayList,移除的方法还有另外两个,实现的方法也都大同小异,都是先copy一份列表,然后加锁去操作,移除掉元素,然后再把数组的引用指向移除后的数组即可。
到此,arrayList和CopyOnWriteArrayList源码就结束了,上面的解释以及注释可能有错误或者不足的地方,希望指正,共同进步,多谢!