Java-集合类源码List篇(三)

前言

  前面分析了ArrayList和LinkedList的实现,分别是基于数组和双向链表的List实现。但看之前那张图,还有两个实现类,一个是Vector,另一个是Stack,接下里一起走进它们的源码世界吧!

4. Vector

Vector跟ArrayList比较相似,继承实现的类或者接口也都是一样的,都是继承自AbstractList,同时底层也是基于数组来实现的。

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

4.1 Vector成员变量

/**
     * 数组来保存集合中的元素
     */
    protected Object[] elementData;

    /**
     * 集合size
     */
    protected int elementCount;

    /**
     * 增长步长
     */
    protected int capacityIncrement;

Vector既然是基于数组来实现的,那么肯定有一个数组的成员变量elementData,返回集合中当前元素的个数elementCount。与ArrayList不同的是增加了一个增长步长,我们知道在ArrayList中,每次增长是上一次的容量的1.5倍,而Vector中则是如果指定了capacityIncrement且该值为正整数,则每次需要扩容时的容量增长为该数值。

4.2 Vector访问元素

 /**
     * 添加元素
     */
    public synchronized boolean add(E e) {
        modCount++;
        //判断是否需要扩容
        ensureCapacityHelper(elementCount + 1);
        //将元素添加至指定位置
        elementData[elementCount++] = e;
        return true;
    }

看到上述方法是否很熟悉,没错,它其实跟ArrayList的添加元素方法基本差不多。唯一不同的就是该方法加了synchronized关键字,该关键字的意思就是访问该方法时需要获取到对象锁的线程方能访问。这就是Vector的线程安全访问机制,通过为每个方法添加synchronized关键字的方法达到在多线程对集合类进行操作时,能够线程安全访问。其它的基本与ArrayList无异。

5. Stack

 看Vector的成员变量的时候,发现都是protected修饰的,所以是可以被子类直接访问的。

public
class Stack<E> extends Vector<E> 

的确在Stack中是没有另外成员变量的,与Vector相比只不过增加了对栈操作的方法,所以Stack是基于数组的线程安全的栈实现集合。

 

Stack是一个基于LILO(后进先出)的栈的实现,它初始化的时候也是一个空的栈。可以看下他的构造函数:

 /**
     * Creates an empty Stack.
     */
    public Stack() {
    }

5.1 Stack的成员方法

public E push(E item);

添加元素(入栈)

public synchronized E pop();

移除元素(出栈)

public synchronized E peek();

返回栈顶元素

public synchronized int search(Object o);

查找元素

看到上述的成员方法,就知道它的方法同Vector一样都是同步的,所以它也是一种线程安全的实现。上面5个方法中,push是没有synchronized修饰的,而其他四个方法都是被synchronized修饰,这个是为什么呢?我们来看下push的源码实现:

   public E push(E item) {
        //调用Vector中的实现
        addElement(item);

        return item;
    }

    /**
     * Vector中的addElement实现
     * @param obj
     */
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

所以我们知道,push()方法其实调用的还是父类Vector的实现,所以就没有加synchronized修饰也能达到同步的效果啦!

 

再来看下search()方法实现:

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

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

返回对象在栈中的位置,下标从1开始,栈顶元素为1。 

其中数组的最后一位元素即为栈顶元素,search方法返回的是离栈顶最近的item与栈顶之间的距离!

在JDK中,有如下语句:

* <p>A more complete and consistent set of LIFO stack operations is
 * provided by the {@link Deque} interface and its implementations, which
 * should be used in preference to this class.  For example:
 * <pre>   {@code
 *   Deque<Integer> stack = new ArrayDeque<Integer>();}</pre>
 *

如果想要实现一个LIFO的基于数组栈实现的话,JDK中提供了ArrayDeque是应该优先考虑的。事实上ArrayDeque同LinkedList一样,都是可以作为LIFO(后进先出)的栈实现,同时也是可以作为FIFO(先进先出)的队列实现。那么就有个问题了:

为什么同样基于数组实现,Stack只能实现栈而ArrayDeque却可以作为栈和队列的实现呢?

要想实现FIFO的队列实现,那么必须满足能够在队列首部出队列,尾部进队列,就是要两端能够操作。

假定有以上数组,那么如果基于栈实现的话,则只需要维护该数组的尾端,出栈移除最大索引元素,size--,入栈则只需要判断size+1与length的大小,看是否需要扩容。如果要做到队首进行出队列,队尾入队列的操作,则每次出队列后,为保持数组空间的连续性都需要将全部元素往前移动一位,那么效率上则不是一种很理想的实现。

 

事实上,ArrayDeque是虽然也是采用基于数组实现的,而它的则增加了逻辑上的循环,通过增加head和tail使其变成了“循环数组”。所以在ArrayDeque中尾部指向索引tail不一定比首部索引head更大。至于具体实现就到后面Deque的接口实现类部分再详述啦!

 

小结

本篇中,主要是分析了基于的数组的集合实现类Vector和Stack,下面小结一下:

Stack与LinkedList用作堆栈时异同:


1.实现机制:Stack是基于数组实现的,LinkedList是基于链表实现的。

2.线程安全:Stack是在方法上进行了同步访问,LinkedList则没有,若需要同步,可通过Collections的同步方法实现。

Vector与ArrayList区别:

1.线程安全:Vector是在方法上进行了同步访问,ArrayList则没有,若需要同步,可通过Collections的同步方法实现。

2.成员变量:Vector增加了一个capacityIncrement成员属性,在扩容时该变量指定情况则直接增加相应步长容量。ArrayList则是增加约1.5倍容量。

 

经过三篇的篇幅,List接口的下的常用集合我们已经分析完啦!

posted @ 2017-07-21 18:06  骑着单车的程序猿  阅读(238)  评论(0编辑  收藏  举报