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

1.基本图示

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

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/82533377