Java集合 | ArrayList源码分析(JDK 1.8)

1.基本图示

《Java集合 | ArrayList源码分析(JDK 1.8)》

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 

2. 基本介绍

  • 底层使用数组,同时是动态数组
  • 由于数组是 Object 类型的,因此可以添加元素 null,可以添加相同元素
  • ArrayList 中的操作是线程不安全的
  • 实现了 RandomAccess 接口,即可以使用随机访问,通过索引访问效率更高

3.基本参数

数组初始容量为 10

private static final int DEFAULT_CAPACITY = 10; 

默认的空数组

private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 

用于存放集合的数组,不是放入10条数据,该数组长度就为10,它会自动扩容以达到最适合的一个容量。因为是 Object 类,因此允许为 null

transient Object[] elementData; 

数组中当前元素的长度

private int size; 

4.初始化方法

如果在初始化时候不指定参数,则会初始化一个空数组

public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 

如果在初始化的时候指定初始容量,如果初始容量大于0,则初始化 elementData 数组,数组容量即为 initialCapacity;如果 initialCapacity 是 0,初始化空数组;如果小于 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); } } 

如果传入一个集合类

public ArrayList(Collection<? extends E> c) { //将集合转为数组对象后赋值给 elementData 数组 elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray 如果不是 Object 类 if (elementData.getClass() != Object[].class) // 使用 Arrays.copyOf 创建一个新的数组 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 如果长度等于 0,初始化空数组 this.elementData = EMPTY_ELEMENTDATA; } } 

5.添加元素和数组扩容

如果直接添加元素,会先调整一次数组的容量,注意参数是当前已存入数组的数据长度+1

public boolean add(E e) { // 调用调整数组容量的方法,参数为(当前数组容量+1) ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } // 在指定位置插入一个值 public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // 在数组插入的位置,所有元素向后移一位  System.arraycopy(elementData, index, elementData, index + 1, size - index); // 在指定的位置插入值 elementData[index] = element; size++; } // 插入一个集合 public boolean addAll(Collection<? extends E> c) { // 将集合转为数组对象后赋值给 Object 数组 a Object[] a = c.toArray(); int numNew = a.length; // 确保当前 elementData 数组的容量充足 ensureCapacityInternal(size + numNew); // 将集合数组直接放到 elementData 数组的后面 System.arraycopy(a, 0, elementData, size, numNew); size += numNew; // 数组不为 0 才返回 true return numNew != 0; } // 在指定的位置插入一个集合 public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); //原 elementData 数组需要指定移动的 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; } 

ensureCapacityInternal 方法里面嵌套了两层方法,首先调用 calculateCapacity 方法计算数组所需要的最小容量 minCapacity,然后再调用 ensureExplicitCapacity 方法来判断是否需要对当前数组进行扩容。注意,minCapacity 是当前已存入的数据长度+1

elementData 数组不是插入多少数据就长度为多少,而是根据插入的数据长度来自动扩充数组长度的(后面会讲到)

private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { // 如果 elementData 数组为空(构造函数不传入参数的情况),会先返回10,即最小容量为10(也是 ArrayList 的默认长度) if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } // 如果不为空,直接返回当前已存入的数据长度+1 return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 如果已经存入数组的长度+1 比 elementData数组的长度 大了,那么就会对 elementData 数组进行扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } 

如果不传入任何参数,那么 ArrayList 的默认长度便是 10,即 elementData 数组的长度为 10,此时当插入第 11 个值时,会对 elementData 数组长度进行 1.5 倍扩容变成 15,以此类推,插入第 16 个值时,elementData 长度变为 15*1.5=22…

private void grow(int minCapacity) { int oldCapacity = elementData.length; // 将 elementData 数组的长度扩大为原来的 1.5 倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果扩容后的elementData数组长度还是比当前已存数组长度要小,则还是使用原来的值 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 返回一个扩容后或者容量改变后的 elementData 数组 elementData = Arrays.copyOf(elementData, newCapacity); } 

6.删除元素

删除元素和增加元素基本是一样的,只是不用扩容罢了

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 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; } // 快速删除的方法,和之前删除方法一致 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 } // 全部清空的方法 public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; } 

7.修改、获取元素

直接获取指定数组下标的元素

public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } public E get(int index) { rangeCheck(index); return elementData(index); } @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } 

8.其他方法

retainAll 用于检测两个集合是否存在相同的数据,即交集,因为无论是否有交集,该方法都会返回 true(虽然很奇怪,但确实是这样),我们可以用另外一种方式来判断,具体可以去看:https://www.cnblogs.com/lyajs/p/5737410.html

public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); } private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; // r 用于遍历 elementData 数组, w 用于存放更新后(把交集数据从后往前移动)的最后一个位置 int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) // r 从前往后遍历,如果 r 指向的位置的值在 c 中有,则往前覆盖;如果没有,则 r 继续往后遍历 if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { //当出现异常(如 r 的位置超过了 elementData 的容量) if (r != size) { // 直接将后面的移到前面 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } // 如果 w!=size 说明进行了覆盖操作,此时 w 前面的都是交集,把 w 后面的数据全部删除 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; } 
// 调用 indexOf 方法来判断
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;
}

9.迭代方式

(1) 迭代器方式
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } 
(2) 随机访问,通过索引值去遍历
for(int i=0; i<list.size(); i++) { System.out.println(list.get(i)); } 
(3) 通过 for 循环
for (String str: list) { System.out.println(str); } 

其中,随机访问效率最高,由于 ArrayList 继承自 RandomAccess, 而且它的迭代器都是基于 ArrayList 的方法和数组直接操作,所以遍历时 get 的效率要 >= 迭代器

10.参考

https://blog.csdn.net/u011240877/article/details/52853989
http://www.cnblogs.com/skywang12345/p/3308556.html#a4
https://blog.csdn.net/wbin233/article/details/70195112

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/babycan5/article/details/82533377
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞