zoukankan      html  css  js  c++  java
  • 给jdk写注释系列之jdk1.6容器(10)-Stack&Vector源码解析

      前面我们已经接触过几种数据结构了,有数组、链表、Hash表、红黑树(二叉查询树),今天再来看另外一种数据结构:栈。
         什么是栈呢,我就不找它具体的定义了,直接举个例子,栈就相当于一个很窄的木桶,我们往木桶里放东西,往外拿东西时会发现,我们最开始放的东西在最底部,最先拿出来的是刚刚放进去的。所以,栈就是这么一种先进后出First In Last Out,或者叫后进先出 的容器,它只有一个口,在这个口放入元素,也在这个口取出元素
     
      栈最主要了两个动作就是入栈和出栈操作,其实还是很容易的明白的对不对,那么我们接下来就看一下Jdk容器中的栈Stack是怎么实现的吧。
      
    1.定义
    1 public
    2 class Stack<E> extends Vector<E> {

      我们发现Stack继承了Vector,Vector又是什么东东呢,看一下。

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

      发现没有,Vector是List的一个实现类,其实Vector也是一个基于数组实现的List容器,其功能及实现代码和ArrayList基本上是一样的。那么不一样的是什么地方的,一个是数组扩容的时候,Vector是*2,ArrayList是*1.5+1;另一个就是Vector是线程安全的,而ArrayList不是,而Vector线程安全的做法是在每个方法上面加了一个synchronized关键字来保证的。但是这里说一句,Vector已经不官方的(大家公认的)不被推荐使用了,正式因为其实现线程安全方式是锁定整个方法,导致的是效率不高,那么有没有更好的提到方案呢,其实也不能说有,但是还真就有那么一个,Collections.synchronizedList(),这不是我们今天的重点不做深入探讨,回到Stack的实现上。

      

    2.Stack&Vector底层存储
     
         由于Stack是继承和基于Vector,那么简单看一下Vector的一些定义和方法源码:
     1     // 底层使用数组存储数据
     2     protected Object[] elementData;
     3     // 元素个数
     4     protected int elementCount ;
     5     // 自定义容器扩容递增大小
     6     protected int capacityIncrement ;
     7 
     8     public Vector( int initialCapacity, int capacityIncrement) {
     9         super();
    10         // 越界检查
    11         if (initialCapacity < 0)
    12             throw new IllegalArgumentException( "Illegal Capacity: " +
    13                                                initialCapacity);
    14         // 初始化数组
    15         this.elementData = new Object[initialCapacity];
    16         this.capacityIncrement = capacityIncrement;
    17     }
    18  
    19     // 使用synchronized关键字锁定方法,保证同一时间内只有一个线程可以操纵该方法
    20     public synchronized boolean add(E e) {
    21         modCount++;
    22        // 扩容检查
    23        ensureCapacityHelper( elementCount + 1);
    24         elementData[elementCount ++] = e;
    25         return true;
    26     }
    27  
    28     private void ensureCapacityHelper(int minCapacity) {
    29         // 当前元素数量
    30         int oldCapacity = elementData .length;
    31         // 是否需要扩容
    32         if (minCapacity > oldCapacity) {
    33            Object[] oldData = elementData;
    34            // 如果自定义了容器扩容递增大小,则按照capacityIncrement进行扩容,否则按两倍进行扩容(*2)
    35            int newCapacity = (capacityIncrement > 0) ?
    36               (oldCapacity + capacityIncrement) : (oldCapacity * 2);
    37            if (newCapacity < minCapacity) {
    38               newCapacity = minCapacity;
    39            }
    40            // 数组copy
    41             elementData = Arrays.copyOf( elementData, newCapacity);
    42        }
    43     }
      Vector就简单看到这里,其他方法Stack如果没有调用的话就不进行分析了,不明白的可以去看ArrayList源码解析。
     
    3.peek()——获取栈顶的对象
     1     /**
     2      * 获取栈顶的对象,但是不删除
     3      */
     4     public synchronized E peek() {
     5         // 当前容器元素个数
     6         int   len = size();
     7      
     8         // 如果没有元素,则直接抛出异常
     9         if (len == 0)
    10            throw new EmptyStackException();
    11         // 调用elementAt方法取出数组最后一个元素(最后一个元素在栈顶)
    12         return elementAt(len - 1);
    13     }
    14  
    15     /**
    16      * 根据index索引取出该位置的元素,这个方法在Vector中
    17      */
    18     public synchronized E elementAt(int index) {
    19         // 越界检查
    20         if (index >= elementCount ) {
    21            throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
    22        }
    23 
    24         // 直接通过数组下标获取元素
    25         return (E)elementData [index];
    26     }

    4.pop()——弹栈(出栈),获取栈顶的对象,并将该对象从容器中删除

     1     /**
     2      * 弹栈,获取并删除栈顶的对象
     3      */
     4     public synchronized E pop() {
     5         // 记录栈顶的对象
     6        E      obj;
     7         // 当前容器元素个数
     8         int   len = size();
     9 
    10        // 通过peek()方法获取栈顶对象
    11        obj = peek();
    12        // 调用removeElement方法删除栈顶对象
    13        removeElementAt(len - 1);
    14 
    15        // 返回栈顶对象
    16         return obj;
    17     }
    18 
    19     /**
    20      * 根据index索引删除元素
    21      */
    22     public synchronized void removeElementAt(int index) {
    23         modCount++;
    24         // 越界检查
    25         if (index >= elementCount ) {
    26            throw new ArrayIndexOutOfBoundsException(index + " >= " +
    27                                               elementCount);
    28        }
    29         else if (index < 0) {
    30            throw new ArrayIndexOutOfBoundsException(index);
    31        }
    32         // 计算数组元素要移动的个数
    33         int j = elementCount - index - 1;
    34         if (j > 0) {
    35            // 进行数组移动,中间删除了一个,所以将后面的元素往前移动(这里直接移动将index位置元素覆盖掉,就相当于删除了)
    36            System. arraycopy(elementData, index + 1, elementData, index, j);
    37        }
    38         // 容器元素个数减1
    39         elementCount--;
    40         // 将容器最后一个元素置空(因为删除了一个元素,然后index后面的元素都向前移动了,所以最后一个就没用了 )
    41         elementData[elementCount ] = null; /* to let gc do its work */
    42     }

    5.push(E item)——压栈(入栈),将对象添加进容器并返回

     1     /**
     2      * 将对象添加进容器并返回
     3      */
     4     public E push(E item) {
     5        // 调用addElement将元素添加进容器
     6        addElement(item);
     7        // 返回该元素
     8         return item;
     9     }
    10 
    11     /**
    12      * 将元素添加进容器,这个方法在Vector中
    13      */
    14     public synchronized void addElement(E obj) {
    15         modCount++;
    16        // 扩容检查
    17        ensureCapacityHelper( elementCount + 1);
    18        // 将对象放入到数组中,元素个数+1
    19         elementData[elementCount ++] = obj;
    20     }

    6.search(Object o)——返回对象在容器中的位置,栈顶为1

     1     /**
     2      * 返回对象在容器中的位置,栈顶为1
     3      */
     4     public synchronized int search(Object o) {
     5         // 从数组中查找元素,从最后一次出现
     6         int i = lastIndexOf(o);
     7 
     8         // 因为栈顶算1,所以要用size()-i计算
     9         if (i >= 0) {
    10            return size() - i;
    11        }
    12         return -1;
    13     }

    7.empty()——容器是否为空

    1     /**
    2      * 检查容器是否为空
    3      */
    4     public boolean empty() {
    5         return size() == 0;
    6     }
      到这里Stack的方法就分析完成了,由于Stack最终还是基于数组的,理解起来还是很容易的(因为有了ArrayList的基础啦)。
     
     
     
         虽然jdk中Stack的源码分析完了,但是这里有必要讨论下,不知道是否发现这里的Stack很奇怪的现象,
         (1)Stack为什么是基于数组实现的呢?
              我们都知道数组的特点:方便根据下标查询(随机访问),但是内存固定,且扩容效率较低。很容易想到Stack用链表实现最合适的。
         (2)Stack为什么是继承Vector的?
              继承也就意味着Stack继承了Vector的方法,这使得Stack有点不伦不类的感觉,既是List又是Stack。如果非要继承Vector合理的做法应该是什么:Stack不继承Vector,而只是在自身有一个Vector的引用,聚合对不对?
     
         唯一的解释呢,就是Stack是jdk1.0出来的,那个时候jdk中的容器还没有ArrayList、LinkedList等只有Vector,既然已经有了Vector且能实现Stack的功能,那么就干吧。。。
     
         既然用链表实现Stack是比较理想的,那么我们就来尝试一下吧:
     1 import java.util.LinkedList;
     2 
     3 public class LinkedStack<E> {
     4 
     5         private LinkedList<E> linked ;
     6 
     7         public LinkedStack() {
     8                this.linked = new LinkedList<E>();
     9        }
    10 
    11         public E push(E item) {
    12                this.linked .addFirst(item);
    13                return item;
    14        }
    15 
    16         public E pop() {
    17                if (this.linked.isEmpty()) {
    18                       return null;
    19               }
    20                return this.linked.removeFirst();
    21        }
    22 
    23         public E peek() {
    24                if (this.linked.isEmpty()) {
    25                       return null;
    26               }
    27                return this.linked.getFirst();
    28        }
    29        
    30         public int search(E item) {
    31                int i = this.linked.indexOf(item);
    32                return i + 1;
    33        }
    34        
    35         public boolean empty() {
    36                return this.linked.isEmpty();
    37        }
    38 }
      看完后,你说我cha,为什么这么简单,就这么点代码。因为这里使用的LinkedList实现的Stack,记得在LinkedList中说过,LinkedList实现了Deque接口使得它既可以作为栈(先进后出),又可以作为队列(先进先出)。那么什么是队列呢?Queue见吧!    
     
         Stack&Vector 完! 
     
     
    参见:
     
  • 相关阅读:
    idea 中使用 svn
    [剑指offer] 40. 数组中只出现一次的数字
    [剑指offer] 39. 平衡二叉树
    [剑指offer] 38. 二叉树的深度
    [剑指offer] 37. 数字在排序数组中出现的次数
    [剑指offer] 36. 两个链表的第一个公共结点
    [剑指offer] 35. 数组中的逆序对
    vscode在win10 / linux下的.vscode文件夹的配置 (c++/c)
    [剑指offer] 34. 第一个只出现一次的字符
    [剑指offer] 33. 丑数
  • 原文地址:https://www.cnblogs.com/tstd/p/5104099.html
Copyright © 2011-2022 走看看