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

3.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     }

4.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     }

5.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     }

6.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 完!

参见:

给jdk写注释系列之jdk1.6容器(1)-ArrayList源码解析

给jdk写注释系列之jdk1.6容器(2)-LinkedList源码解析

时间: 2024-12-09 10:32:44

给jdk写注释系列之jdk1.6容器(10)-Stack&Vector源码解析的相关文章

给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析

原文出自吞噬天地,链接整理自ImportNew 给jdk写注释系列之jdk1.6容器(2):LinkedList源码解析 给jdk写注释系列之jdk1.6容器(3):Iterator设计模式 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析 给jdk写注释系列之jdk1.6容器(5)-LinkedHashMap源码解析 给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&Map迭代器 给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析 工作中

给jdk写注释系列之jdk1.6容器(13)-总结篇之Java集合与数据结构

是的,这篇blogs是一个总结篇,最开始的时候我提到过,对于java容器或集合的学习也可以看做是对数据结构的学习与应用.在前面我们分析了很多的java容器,也接触了好多种常用的数据结构,今天我们就来总结下这些内容. 下面我们以数据结构的维度来总结下,在Java集合的实现过程中,底层到底使用了哪些常用的数据结构中,他们分别又有什么特点.      1. 数组(Array) 结构说明:在程序设计中,为了处理方便, 把具有相同类型的若干变量按有序的形式组织起来.这些按序排列的同类数据元素的集合称为数组

给jdk写注释系列之jdk1.6容器(12)-PriorityQueue源码解析

PriorityQueue是一种什么样的容器呢?看过前面的几个jdk容器分析的话,看到Queue这个单词你一定会,哦~这是一种队列.是的,PriorityQueue是一种队列,但是它又是一种什么样的队列呢?它具有着什么样的特点呢?它的底层实现方式又是怎么样的呢?我们一起来看一下. PriorityQueue其实是一个优先队列,什么是优先队列呢?这和我们前面讲的先进先出(First In First Out )的队列的区别在于,优先队列每次出队的元素都是优先级最高的元素.那么怎么确定哪一个元素的优

给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&amp;Map迭代器

今天的主角是HashSet,Set是什么东东,当然也是一种java容器了. 现在再看到Hash心底里有没有会心一笑呢,这里不再赘述hash的概念原理等一大堆东西了(不懂得需要先回去看下HashMap了),需要在啰嗦一句的是hash表是基于快速存取的角度设计的,也是一种典型的空间换时间的做法(这个在分析HashMap中都有讲过).那么今天的HashSet它又是怎么一回事的,他的存在又是为了解决什么问题呢? 先来看下Set的特点:Set元素无顺序,且元素不可以重复. .想到了什么?无顺序,由于散列的

给jdk写注释系列之jdk1.6容器(2)-LinkedList源码解析

LinkedList是基于链表结构的一种List,在分析LinkedList源码前有必要对链表结构进行说明. 1.链表的概念 链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链表又分为单向链表和双向链表,而单向/双向链表又可以分为循环链表和非循环链表,下面简单就这四种链表进行图解说明.           1.1.单向链表 单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null.      1. 2.单向循环链表           单向循环

给jdk写注释系列之jdk1.6容器(1)-ArrayList

工作中经常听到别人讲“容器”,各种各样的容器,话说到底什么是容器,通俗的讲“容器就是用来装东西的器皿,比如:水桶就是用来盛水的,水桶就是一个容器.” ok,在我们写程序的时候常常要对大量的对象进行管理,比如查询,遍历,修改等.jdk为我们提供的容器位于java.util包,也是我们平时用的最多的包之一. 但是为什么不用数组(其实也不是不用,只是不直接用)呢,因为数组的长度需要提前确定,而且不能改变大小,用起来手脚受限嘛. 下面步入正题,首先我们想,一个对象管理容器需要哪些功能?增加,删除,修改,

给jdk写注释系列之jdk1.6容器(5)-LinkedHashMap源码解析

前面分析了HashMap的实现,我们知道其底层数据存储是一个hash表(数组+单向链表).接下来我们看一下另一个LinkedHashMap,它是HashMap的一个子类,他在HashMap的基础上维持了一个双向链表(hash表+双向链表),在遍历的时候可以使用插入顺序(先进先出,类似于FIFO),或者是最近最少使用(LRU)的顺序. 来具体看下LinkedHashMap的实现. 1.定义  1 public class LinkedHashMap<K,V> 2 extends HashMap&

JDK1.8 动态代理机制及源码解析

动态代理 a) jdk 动态代理 Proxy, 核心思想:通过实现被代理类的所有接口,生成一个字节码文件后构造一个代理对象,通过持有反射构造被代理类的一个实例,再通过invoke反射调用被代理类实例的方法,来实现代理. 缺点:被代理类必须实现一个或多个接口 参考链接:http://rejoy.iteye.com/blog/1627405 源码解析:见第四部分 cglib 动态代理 核心思想:通过生成子类字节码实现,代理类为每个委托方法都生成两个方法,以add方法为例,一个是重写的add方法,一个

Stack&amp;Vector源码分析 jdk1.6

参照:http://www.cnblogs.com/tstd/p/5104099.html Stack(Fitst In Last Out) 1.定义 public class Stack<E> extends Vector<E> { public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Seriali