今天来说说java里面的栈。栈是一种先进后出或者后进先出的一种数据结构。先来看下图谱。java源码系列。
Stack继承了vector,大家都知道vector是线程安全的。
为什么是线程安全的呢?因为方法前面用了synchronized修饰。
为什么用了synchronized就是线程安全的呢?因为synchronized内部有锁的概念,线程在没有获取的资源的时候放入entry set,获取到了资源就是owner,放弃现有资源方法就是wait set。使用的是monitor锁实现,用javap 命令可以直观的看到monitor加锁过程。
好了,前戏就简单讲到这里了,现在来看看java栈的源码实现。
一 .入栈
public Stack() { 栈的构造函数
}
public E push(E item) { //添加item元素,这里用的是泛型 addElement(item); //这个方法是Vector里面的方法,是一个同步方法 return item; }
public synchronized void addElement(E obj) { modCount++; //修改记录数 ensureCapacityHelper(elementCount + 1); //确保容量方法。为什么要这个方法,接口往下看 elementData[elementCount++] = obj; //值赋上去,数值长度加1 }
private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) //怎么还有比较?不出惊讶,用的是数组,默认是10, grow(minCapacity); //扩容,和Arraylist源码里面的方法貌似差不多 }
private void grow(int minCapacity) { //注意啦,扩容开始 // overflow-conscious code int oldCapacity = elementData.length; //保存数组现在的长度 int newCapacity = oldCapacity + ((capacityIncrement > 0) ? //如果调用Vector的默认构造方法,capacityIncrement为0,否则capacityIncrement有具体的值,具体请看Vector(int,int)方法 capacityIncrement : oldCapacity); //如果capacityIncrement默认是0,数组的长度扩大一倍 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) //如果数组长度扩大的查过数组最大长度 newCapacity = hugeCapacity(minCapacity); 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; }
好了,到此栈的添加就完成了,现在来总结一下:
1.确定数组长度,默认是10
2.看看添加的值需不需要扩容,如果要扩容就将数组的长度加一倍,2n
3.将值添加进去
二.获取栈顶元素
public synchronized E peek() { //这是一个同步方法 int len = size(); //获取栈中元素的个数 if (len == 0) //栈里面没有值,就抛出一个栈空的异常 throw new EmptyStackException(); return elementAt(len - 1); //获取栈顶的元素 }
public synchronized E elementAt(int index) { if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } return elementData(index); }
E elementData(int index) { return (E) elementData[index]; }
三.出栈
public synchronized E pop() { E obj; int len = size(); obj = peek(); //获取栈顶元素 removeElementAt(len - 1); //删除栈顶元素 return obj; }
public synchronized void removeElementAt(int index) { //栈顶元素的索引 modCount++; if (index >= elementCount) { //如果要删除的索引大于数组的长度,抛出一个数组越界异常。 throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } else if (index < 0) { //如果索引小于0 也要抛出异常 throw new ArrayIndexOutOfBoundsException(index); } int j = elementCount - index - 1; //需要赋值的长度,由于栈是删除栈顶元素,这里是0 if (j > 0) { System.arraycopy(elementData, index + 1, elementData, index, j); // } elementCount--; //数组长度减一 elementData[elementCount] = null; //将栈顶元素的赋值为null,变成不可引用对象,后面gc会自行收集,腾出内存空间 }
四.查询栈元素索引
public synchronized int search(Object o) { //说实话,这个还真没用过,看源码才知道有这方法,这个方法是查询元素在栈的序号 int i = lastIndexOf(o); if (i >= 0) { return size() - i; } return -1; }
public synchronized int lastIndexOf(Object o) { //这个方法还真没什么讲的 return lastIndexOf(o, elementCount-1); //唯一需要注意的就是第二个参数传的是数组做大的索引值 }
public synchronized int lastIndexOf(Object o, int index) { if (index >= elementCount) throw new IndexOutOfBoundsException(index + " >= "+ elementCount); if (o == null) { //如果查询的对象是null,查到就返回序号 for (int i = index; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = index; i >= 0; i--) if (o.equals(elementData[i])) //不是空,就用equals判断,其实这个方法现在可以改一版了,方便理解用Objects.equals(jdk7) return i; } return -1; //没有找到索引就是-1 }
到此,栈的源码解读就完全讲完了。在java中,栈使用的数组。若果让你用链表来实现,你会么?
给一个简单提示哈。
1入栈,用头插法将数据插入到对列中。主要是出栈的时间变短。
2获取栈顶元素,head->next就是栈顶元素了,时间复杂度为o(1)。
3出栈,也只要删除head->next就可以,时间复杂度也会o(1)。
4查询栈索引,从链表头一直循环比较过去,最坏的情况也是o(n)。
以上就是用链表来实现栈。个人感觉比数组实现好很多。