[集合类]源码解析5(ArrayList类、Vector类和Stack类)

上一篇:[集合类]源码解析4(List接口和AbstractList抽象类)

1. 概述

前面我们按照接口为线索,分析了和集合类相关的一些接口以及他们的实现类,下面我们将针对具体的类来分析其具体实现。我们将了解到容器的存储实现、构造方法、常用方法。

Vector的实现和ArrayList基本相同,二者的区别是,Vector是线程安全的,其写方法都是syncronized修饰的。

Stack是栈的实现,继承了Vector,所以也是线程安全的。Stack的JavaDoc中有这样一段话:

在这里插入图片描述

// 翻译参考:
// Deque接口及其实现提供了一组更完整、更一致的后进先出堆栈操作,应该优先使用这些操作。例如:
// Deque<Integer> stack = new ArrayDeque<Integer>();

当然,我们在Deque的文档中也找到了相关说明:

在这里插入图片描述

// 翻译参考:
// Deques也可以作为后进先出的堆叠。这个接口应该优先用于遗留堆栈类。
// 当deque用作堆栈时,元素从deque的开头被推入和弹出。
// 如下表所示,堆栈方法与Deque方法完全等价:

所以,当没有并发要求时,我们应该优先使用Deque实现栈。

2. ArrayList

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

RandomAccess, Cloneable, Serializable接口我们在前面已经分析过了,传送门: [集合类] 源码解析1(Iterable、RandomAccess、Serializable、Cloneable)

迭代器也分析过了,传送门:[集合类] 源码解析2(Iterator的实现)

List接口和AbstractList抽象类也分析过了,传送门:[集合类]源码解析4(List接口和AbstractList抽象类)

1)属性

private static final long serialVersionUID = 8683452581122892189L;

// 默认初始化大小
private static final int DEFAULT_CAPACITY = 10;

// 空数组,供空实例使用
private static final Object[] EMPTY_ELEMENTDATA = {};

// 与EMPTY_ELEMENTDATA区分开,以了解添加第一个元素时的膨胀程度
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储元素的数组。ArrayList容量是这个数组的长度。
// elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList在添加第一个元素时会扩充为DEFAULT_CAPACITY
transient Object[] elementData; // non-private to simplify nested class access

// ArrayList的size,包含的元素数量
private int size;

可以看到elementData被transient关键字修饰了,这个关键字的意思是不会被序列化,诶?但是ArrayList还实现了Serializable接口,这是怎么回事?

由于ArrayList是动态扩容的,所以数组中可能存在没有存储元素的单元,如果采用外部的序列化实现的话,就会序列化整个数组,浪费时间和空间。我们后面会看到 ArrayList 有 writeObject 和 readObject 两个方法,实际上就是序列化和反序列化的方法。

2)构造方法

ArrayList有三个构造方法,第一个传入初始大小,当 ArrayList 新增元素时,如果空间不足,会进行动态扩容,会导致整个数组进行一次内存复制。因此,在初始化 ArrayList 时,可以预估数组大小,这样可以减少扩容次数,提高系统性能。第二个是空参构造,数组默认引用DEFAULTCAPACITY_EMPTY_ELEMENTDATA。第三个传入集合,通过toArray方法转化为数组,引用赋给数组,如果集合长度为0,数组引用EMPTY_ELEMENTDATA。

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() {
  	// 默认空数组,和EMPTY_ELEMENTDATA区分
  	// 在3)常用方法(5)关键方法中还会提到
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    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 {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3)常用方法

(1)添加方法

扩容请查看(5)关键方法,我们之前迭代器文章我们提到modCount是避免进行并发修改的,按理说在添加方法中应该modCount++,但是我们并没有看到相关语句,将在(5)关键方法中讲到。

public void add(int index, E element) {
    rangeCheckForAdd(index);
		// 确保空间足够
    ensureCapacityInternal(size + 1);  // Increments modCount!!
  	// 将index后面元素后移
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

public boolean addAll(Collection<? extends E> c) {
  	// 转换成数组
  	Object[] a = c.toArray();
  	int numNew = a.length;
  	// 确保空间足够
  	ensureCapacityInternal(size + numNew);  // Increments modCount
  	// 追加到末尾
  	System.arraycopy(a, 0, elementData, size, numNew);
  	size += numNew;
  	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);  // Increments modCount
		// 要移动的元素数量
  	int numMoved = size - index;
  	if (numMoved > 0)
      	// index后面元素向后移动numNew距离
    		System.arraycopy(elementData, index, elementData, index + numNew,
                     numMoved);
		// 添加元素
  	System.arraycopy(a, 0, elementData, index, numNew);
  	size += numNew;
  	return numNew != 0;
}

(2)删除方法
public E remove(int index) {
  	// 保证index合法
    rangeCheck(index);

    modCount++;
  	// 返回旧值
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
      	// 将index后面元素前移一个单元
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
  	// 最后一个空间置空
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

// 遍历,调用fastRemove方法
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;
}

// 相比remove空参方法,省去了index检查,返回值,
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
}

// 删除c中存在的元素
public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}
// 删除c中不存在的
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

// 上面两个方法都通过该方法实现
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0; // r为遍历索引 w为结果索引
    boolean modified = false;
    try {
        for (; r < size; r++)
          	// complement为false,保存c中不存在的元素
          	// complement为true,保存c中存在的元素
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
      	// 存在并发修改,
        if (r != size) {
          	// 如果存在并发删除,则size - r为负数
          	// System.arraycopy文档中有说明,会抛出IndexOutOfBoundsException运行时异常
          	// 如果存在并发添加,则将添加的元素追加到w索引后面。
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
      	// 成功删除了元素,将后面空间置空
        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;
}


public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

(3)修改方法
// 修改index索引的值,返回旧值
public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
(4)查询方法
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

// 这种处理方法我们见过很多次了,针对o是否为空,分别处理
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;
}
// 参考indexOf,区别在于遍历是倒序的
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;
}
(5)关键方法

在添加方法中都调用了ensureCapacityInternal来保证数组空间大小,下面我们分析一下。

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

// 首先判断elementData数组是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
// 也就是调用空参的构造方法,第一次添加元素的时候
// 如果是的话,返回Math.max(默认数组大小10, 参数minCapacity)
// 否则返回参数minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
  	// 原来modCount在这里+1,难怪在添加方法中找不到
    modCount++;

    // overflow-conscious code
  	// 如果空间不够了,就要扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 真正的扩容函数
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
  	// 扩容 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
  	// 还不够的话,扩容到参数minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
  	// 如果超过了最大数组大小,根据参数minCapacity的大小需要,进行设置
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
  	// elementData引用到新数组,旧数组等待GC,所以我们要尽量避免扩容操作
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}
(6)其他方法
// 缩减数组大小到元素数量
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
          ? EMPTY_ELEMENTDATA
          : Arrays.copyOf(elementData, size);
    }
}

// 重写了AbstractCollection的方法,效率更高
public Object[] toArray() {
  	// 返回副本
    return Arrays.copyOf(elementData, size);
}

public <T> T[] toArray(T[] a) {
  	// a的空间不够,新拷贝一个数组返回
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
  	// a的空间足够,将elementData复制给a,返回a
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

4)System.arraycopy

通过上面的源码阅读,我们可以发现,ArrayList由于使用数组存储,很多地方都使用了System.arraycopy方法,(Arrays.copyOf方法内部也是使用了System.arraycopy)。由于System.arraycopy是本地方法,下面我们阅读一下他的文档。

由于博主英文水平有限,为避免歧义,先给出英文文档,有能力的同学可以自己阅读。下面将根据我的理解进行翻译,有错误的地方,望不吝赐教。

在这里插入图片描述

从指定的src数组(srcPos索引开始)复制到dest数组的指定位置(destPos索引)。数组的子序列从src引用的源数组复制到dest引用的目标数组,复制的元素数量等于length参数。元素从源数组的srcPos到srcPos + length - 1位置按顺序复制到目标数组的destPos 到 destPos + length - 1位置。

如果src和dest参数引用的是同一个数组对象,就好像首先复制srcPos到srcPos + length - 1位置的元素到一个容量为length的临时数组,然后临时数组的内容再复制到destPos 到 destPos + length - 1位置的目标数组。

如果 dest 为空,抛出 NullPointerException 异常

如果 src 为空, 抛出 NullPointerException 异常,并且不修改目标数组

另外,如果下列任何情况成立,则抛出ArrayStoreException,并且不修改目标数组:

src参数引用的对象不是数组。
dest参数引用的对象不是数组。
src参数和dest参数引用的是元素类型不同的原始类型的数组。
src参数引用的是一个具有基本元素类型元素的数组,dest参数引用的是一个具有包装类型元素的数组。
src参数引用一个具有包装类型元素的数组,dest参数引用一个具有基本类型元素的数组。

另外,如果下列任何情况成立,则抛出IndexOutOfBoundsException,并且不修改目标数组:

srcPos参数是负的。
destPos参数是负的。
length参数是负的。
srcPos+length大于src.length,即源数组的长度。
destPos+length大于destination.length,即目标数组的长度。

另外,如果从srcPos到srcPos+length-1位置的源数组的任一元素不能通过赋值转换为目标数组元素的类型,则抛出ArrayStoreException。在这种情况下,令k为小于length的最小非负整数,如果src[srcPos+k]不能转换为目标数组元素的类型,当抛出异常时,位置srcPos到srcPos+k-1的源数组元素已经复制到目标数组destPos到destPos+k-1位置,目标数组的其他位置将不会被修改。(由于已经详细列出了这些限制,本段实际上只适用于两个数组都具有引用类型元素的情况。)

3. Vector

通过查看源码我们可以发现,Vector方法和ArrayList基本相同,不过在修改方法上,都使用synchronized修饰。Vector是ArrayList的同步版本,所以这里就不再赘述了。

4. Stack

开头我们已经说过,官方文档推荐用Deque实现栈,这里我们只是简单看一下Stack类的源码。

// 短短不到50行,非常简单,对列表操作进行限制,就是栈了。
// 需要注意的是 三个同步方法的synchronized是为了保证size()获取的共享变量的可见性
public class Stack<E> extends Vector<E> {

    public Stack() {
    }

    public E push(E item) {
        addElement(item);

        return item;
    }
		
    public synchronized E pop() {
        E       obj;
        int     len = size();

        obj = peek();
        removeElementAt(len - 1);

        return obj;
    }

    public synchronized E peek() {
        int     len = size();

        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }

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

    public synchronized int search(Object o) {
        int i = lastIndexOf(o);

        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }

    private static final long serialVersionUID = 1224463164541339165L;
}

下一篇:[集合类]源码解析6(Queue接口、AbstractQueue抽象类、Deque接口)

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值