java8:ArrayList与Vector的实现原理

本文详细分析了ArrayList的源码,介绍了其作为动态数组的实现,如何通过构造方法设置初始容量,以及关键方法如get、add、set和remove的工作原理。同时对比了与线程安全的Vector在容量增长策略上的区别。
摘要由CSDN通过智能技术生成

概述

一上来,先来看看源码中的这一段注释,我们可以从中提取到一些关键信息:
Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)
从这段注释中,我们可以得知 ArrayList 是一个动态数组,实现了 List 接口以及 list 相关的所有方法,它允许所有元素的插入,包括 null。另外,ArrayList 和 Vector 除了线程不同步之外,大致相等。

ArrayList继承关系

image.png

属性

//默认容量的大小
private static final int DEFAULT_CAPACITY = 10;
//空数组常量
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认的空数组常量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMEN TDATA = {};
//存放元素的数组,从这可以发现 ArrayList 的底层实现就是一个 Object数组
transient Object[] elementData;
//数组中包含的元素个数
private int size;
//数组的最大上限
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALU E - 8;
  ArrayList 的属性非常少,就只有这些。其中最重要的莫过于 elementData 了,ArrayList所有的方法都是建立在elementData 之上。接下来,我们就来看一下一些主要的方法吧。

方法

构造方法

   
    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }
   从构造方法中我们可以看见,默认情况下,elementData 是一个大小为 0 的空数组, 当我们指定了初始大小的时候,elementData 的初始大小就变成了我们所指定的初始大小了。

get 方法

    /**
     * Returns the element at the specified position in this list.
     *
     * @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) {
        rangeCheck(index);

        return elementData(index);
    }
   /**
     * Checks if the given index is in range.  If not, throws an appropriate
     * runtime exception.  This method does *not* check if the index is
     * negative: It is always used immediately prior to an array access,
     * which throws an ArrayIndexOutOfBoundsException if index is negative.
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    E elementData(int index) {
        return (E) elementData[index];
    }
    因为 ArrayList 是采用数组结构来存储的,所以它的 get 方法非常简单,先是判断一下有没有越界,之后就可以直接通过数组下标来获取元素了,所以 get 的时间复杂度是 O(1)。

add 方法

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //调用一个native 的复制方法,把index 位置开始的元素都往后挪一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    /**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

   private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, 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++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
   /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //注意到这一行代码
        //采用右移运算,就是原来的一般,所以是扩容1.5倍。
        //比如10的二进制是1010,右移后变成101就是5。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }
    ArrayList 的 add 方法也很好理解,在插入元素之前,它会先检查是否需要扩容,然后再把元素添加到数组中最后一个元素的后面。在 ensureCapacityInternal 方法中, 我们可以看见,如果当 elementData 为空数组时,它会使用默认的大小去扩容。所以说,通过无参构造方法来创建 ArrayList 时,它的大小其实是为 0 的,只有在使用到的时候,才会通过 grow 方法去创建一个大小为 10 的数组。

  第一个 add 方法的复杂度为 O(1),虽然有时候会涉及到扩容的操作,但是扩容的次数是非常少的,所以这一部分的时间可以忽略不计。如果使用的是带指定下标的 add 方法,则复杂度为 O(n),因为涉及到对数组中元素的移动,这一操作是非常耗时的。

**set **方法

 /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
   E elementData(int index) {
        return (E) elementData[index];
    }

set 方法的作用是把下标为 index 的元素替换成element,跟 get 非常类似,所以就不在赘述了,时间复杂度度为 O(1)。

remove 方法

 /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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);
        // 将数组元素个数减一,并将最后的元素置为null
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

remove 方法与add 带指定下标的方法非常类似,也是调用系统的arraycopy 方法来移动元素,时间复杂度为 O(n)。

grow 方法


    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }
  grow 方法是在数组进行扩容的时候用到的,从中我们可以看见,ArrayList 每次扩容都是扩 1.5 倍,然后调用 Arrays 类的 copyOf 方法,把元素重新拷贝到一个新的数组中去。

size 方法

 /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return size;
    }
   size 方法非常简单,它是直接返回 size 的值,也就是返回数组中元素的个数,时间复杂度为 O(1)。这里要注意一下,返回的并不是数组的实际大小。

indexOf 方法和 lastIndexOf

 /**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
    }
 /**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
    }
  indexOf 方法的作用是返回第一个等于给定元素的值的下标。它是通过遍历比较数组中每个元素的值来查找的,所以它的时间复杂度是 O(n)。lastIndexOf 的原理跟 indexOf 一样,而它仅仅是从后往前找起罢了。

clear方法

/**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

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

        size = 0;
    }

clear方法比较简单,将数组的所有元素都置为null,并将数组元素个数置为0

Vector

     Vector 很多方法都跟ArrayList 一样,只是多加了个 synchronized 来保证线程安全罢了。只把 Vector 与 ArrayList 的不同点提一下就可以了。

vector继承关系

image.png
Vector 比 ArrayList 多了一个属性:

 /**
     * The amount by which the capacity of the vector is automatically
     * incremented when its size becomes greater than its capacity.  If
     * the capacity increment is less than or equal to zero, the capacity
     * of the vector is doubled each time it needs to grow.
     *
     * @serial
     */
    protected int capacityIncrement;
   这个属性是在扩容的时候用到的,它表示每次扩容只扩 capacityIncrement 个空间就足够了。该属性可以通过构造方法给它赋值。先来看一下构造方法:
   /**
     * Constructs an empty vector with the specified initial capacity and
     * capacity increment.
     *
     * @param   initialCapacity     the initial capacity of the vector
     * @param   capacityIncrement   the amount by which the capacity is
     *                              increased when the vector overflows
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

    /**
     * Constructs an empty vector with the specified initial capacity and
     * with its capacity increment equal to zero.
     *
     * @param   initialCapacity   the initial capacity of the vector
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

    /**
     * Constructs an empty vector so that its internal data array
     * has size {@code 10} and its standard capacity increment is
     * zero.
     */
    public Vector() {
        this(10);
    }
    从构造方法中,我们可以看出 Vector 的默认大小也是 10,而且它在初始化的时候就已经创建了数组了,这点跟 ArrayList 不一样。再来看一下 grow 方法:
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    从 grow 方法中我们可以发现,newCapacity 默认情况下是两倍的 oldCapacity,而当指定了 capacityIncrement 的值之后,newCapacity 变成了oldCapacity+capacityIncrement。

总结

1、ArrayList 创建时的大小为 0;当加入第一个元素时,进行第一次扩容时,默认容量大小为 10。
2、ArrayList 每次扩容都以当前数组大小的 1.5 倍去扩容。
3、Vector 创建时的默认大小为 10。
4、Vector 每次扩容都以当前数组大小的 2 倍去扩容。当指定了 capacityIncrement 之后,每次扩容仅在原先基础上增加 capacityIncrement 个单位空间。
5、ArrayList 和 Vector 的 add、get、size 方法的复杂度都为 O(1),remove 方法的复杂度为 O(n)。
6、ArrayList 是非线程安全的,Vector 是线程安全的。

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值