Java容器二.ArrayList源码学习-JDK8

2018-06-08  本文已影响0人  stoneyang94

按照从构造方法->常用API(增、删、改、查)的顺序来阅读源码,并做简要分析。

一. 定义

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

介绍

优势

二 .属性

底层用数组来保存数据

private transient Object[] elementData;
private int size;

基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明数组默认不会被序列化。这是 ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容

三. 构造函数

1)无参构造函数--ArrayList()

Constructs an empty list with an initial capacity of ten.

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added.

private static final int DEFAULT_CAPACITY = 10;

此时我们创建的ArrayList对象中的elementData中的长度是1,size是0,当进行第一次add的时候,elementData将会变成默认的长度:10.(具体过程看下文的add())

2)带容量大小的构造函数--ArrayList(int initialCapacity)

传入参数如果是大于等于0,则使用用户的参数初始化,如果用户传入的参数小于0,则抛出异常

Constructs an empty list with the specified initial capacity.

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);
    }
}

3)带Collection对象的构造函数--ArrayList(Collection<? extends E> c)

构造一个包含指定元素的list,这些元素的是按照Collection的迭代器返回的顺序排列的

  1. 将collection对象转换成数组,然后将数组的地址的赋给elementData
  2. 更新size的值,同时判断size的大小
    2.1 如果是size等于0,直接将空对象EMPTY_ELEMENTDATA的地址赋给elementData
    2.2 如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容copy到elementData中

Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    //因为size代表的是集合元素数量,所以通过别的集合来构造ArrayList时,要给size赋值
    if ((size = elementData.length) != 0) {
        //这里是当c.toArray出错,没有返回Object[]时
        //利用Arrays.copyOf 来复制集合c中的元素到elementData数组中
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
private static final Object[] EMPTY_ELEMENTDATA = {};

Arrays.copyOf()方法

   public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
 
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

可以看到,他是在方法内部,新建了一个相同类型的数组,然后调用系统方法

System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));

original - 源数组。
0 - 源数组中的起始位置。
copy - 目标数组。
0 - 目标数据中的起始位置。
Math.min(original.length, newLength) - 要复制的数组元素的数量。

四. 增加---add addAll

添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,使得新容量为旧容量的 1.5 倍oldCapacity + (oldCapacity >> 1)
扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。

添加一个元素(在末尾)--add(E e)

add(E e)

Appends the specified element to the end of this list.

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;//在数组末尾追加一个元素,并修改size
    return true;
}

ensureCapacityInternal(int minCapacity)

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

calculateCapacity(Object[] elementData, int minCapacity)

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //利用 == 可以判断数组是否是用默认构造函数初始化的
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private static final int DEFAULT_CAPACITY = 10;

ensureExplicitCapacity()

经过前面的一顿操作,minCapacity 是

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; //如果确定要扩容,会修改modCount 
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

grow()

Increases the capacity to ensure that it can hold at least the number of elements specified by the minimum capacity argument

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 旧容量的1.5倍
  //如果还不够 ,那么就用 能容纳的最小的数量。(add后的容量)
    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);
}

在指定位置添加一个元素--add(int index, E element)

Inserts the specified element at the specified position in this list.

  1. 确保有足够的容量之后
  2. 使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位
  3. 将新的数据内容存放到数组的指定位置(index)上
public void add(int index, E element) {
    rangeCheckForAdd(index);
    // 进行扩容检查
    ensureCapacityInternal(size + 1);  // Increments modCount!!
   // 对数组进行复制处理,目的就是空出index的位置插入element,并将index后的元素位移一个位置
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
   // 将指定的index位置赋值为element
    elementData[index] = element;
    size++;
}

关于arraycopy()

public static void (Object src,int srcPos,
                    Object dest,int destPos,
                    int length)
参数 意义
src 源数组
srcPos 源数组要复制的起始位置
dest 目的数组
destPos 目的数组放置的起始位置
length 复制的长度

其中rangeCheckForAdd是为了防止越界:

private void rangeCheckForAdd(int index) {
     if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

增加一个集合--addAll(Collection<? extends E> c)

Appends all of the elements in the specified collection to the end of this list, in the order that they are returned by the specified collection's Iterator.

 public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();//将集合转换为数组
    int numNew = a.length;
    //扩容检查
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //将c添加至list的数据尾部
    System.arraycopy(a, 0, elementData, size, numNew);
    //更新当前容器大小
    size += numNew;
    return numNew != 0;
}

在指定位置,增加一个集合元素--addAll(int index, Collection<? extends E> c)

Inserts all of the elements in the specified collection into this list, starting at the specified position.

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //计算需要移动的长度(index之后的元素个数)
    int numMoved = size - index;
    //将数组index后的元素向右移动numNum个位置,即空出第index到index+numNum的位置
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
    //将要插入的集合元素复制到数组空出的位置中
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

关于增 的总结:
add、addAll。
先判断是否越界,是否需要扩容。
如果扩容, 就复制数组。
然后设置对应下标元素值。

值得注意的是:
1 如果需要扩容的话,默认扩容一半。如果扩容一半不够,就用目标的size作为扩容后的容量。
2 在扩容成功后,会修改modCount

五. 删除---remove

根据索引位置删除元素--remove(int index)

Removes the element at the specified position in this list.

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);// 供返回
    int numMoved = size - index - 1;// 计算数组要复制的数量
   // 数组复制,就是将index之后的元素往前移动一个位置
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 因为删除了一个元素,然后index后面的元素都向前移动了,所以最后一个就没用了
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

根据元素内容删除,只删除匹配的第一个--remove(Object o)

Removes the first occurrence of the specified element from this list, if it is present. If the list does not contain the element, it is unchanged.

循环遍历所有对象,得到对象所在索引位置,然后调用fastRemove方法,执行remove操作

    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;
    }

fastRemove()

Private remove method that skips bounds checking and does not return the value removed.

定位到需要remove的元素索引,先将index后面的元素往前面移动一位(调用System.arraycooy实现),然后将最后一个元素置空。

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
}

数组越界检查

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}

六. 查找---get

查找指定位置上的元素

Returns the element at the specified position in this list.

public E get( int index) {
    RangeCheck(index);
    return (E) elementData [index];
}

七. 更新---set

将指定位置的元素更新为新元素

Replaces the element at the specified position in this list with the specified element.

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);// 记录要更新位置的元素,供返回使用
    elementData[index] = element;
    return oldValue;
}

八. 判断

包含--contains(Object o)

调用indexOf方法,遍历数组中的每一个元素作对比,如果找到对于的元素,则返回true,没有找到则返回false

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;
}

空--isEmpty()

public boolean isEmpty() {
    return size == 0;
}

九. ArrayList遍历方式

ArrayList支持3种遍历方式

  1. 通过迭代器遍历。即通过Iterator去遍历。
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}
  1. 随机访问,通过索引值去遍历。
    由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}
  1. for循环遍历。如下:
Integer value = null;
for (Integer integ:list) {
    value = integ;
}

十. ArrayList和 Vector 的区别

 int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                 capacityIncrement : oldCapacity);

十一. 小回顾

参考文章
Java集合干货系列-(一)ArrayList源码解析
Java官方文档
ArrayList源码解析(JDK8)

上一篇 下一篇

猜你喜欢

热点阅读