尽管人生有那么多的徒劳无功,为了梦想,我还是要一次次全力以赴。
背景与引言:
今天在做一道算法题的时候,看到了有关于算法的,而题目中扯到了栈的知识,以前了解过一点,没有深入了解过,嘿嘿,这几天把Java八大数据接口都总结一下,本人才疏学浅,只是想把我自己遇到的问题和学到的东西总结一下,再就是让大家也指出我文章中的问题,谢谢。
题目做的是力扣上的,名字叫 有效的括号
看到一种解法是使用的是,Stack的pop与push,这种解发做出来的,看了他的解法和我的解法,我默默留下了悔恨的眼泪,所以今天好好学学Stack的相关知识。
什么是Stack?
图源自百度:
虽然百度解释 的比较笼统,但是关于栈的几个要点已经出来了,第一:栈的特点就是 ** 后进先出**,
什么是后进先出,这张图带你了解
压栈也就是进栈,是后面进来的会在上面,那么,自然弹栈也就是出栈,也只能从上面的先出
第二点: Stack继承了Vector,那什么是Vector,我都先只笼统的说一下,等下实际那肯定是得看源码的,学会看源码,你才会走的更远。
Vector:
Vector 类实现了一个动态数组。和 ArrayList 很相似,但是两者是不同的:
Vector 是同步访问的。
Vector 包含了许多传统的方法,这些方法不属于集合框架。
Vector 主要用在事先不知道数组的大小,或者只是需要一个可以改变大小的数组的情况。
Vector 类支持 4 种构造方法。
第一种构造方法创建一个默认的向量,默认大小为 10:
Vector()
第二种构造方法创建指定大小的向量。
Vector(int size)
第三种构造方法创建指定大小的向量,并且增量用 incr 指定。增量表示向量每次增加的元素数目。
Vector(int size,int incr)
第四种构造方法创建一个包含集合 c 元素的向量:
Vector(Collection c)
以上大概就是Vector的一些介绍,接下来,我们一起跟随源码来深入解读Stack。
push方法,其实它调用的是 Vector中的 addElement方法
因为加了 synchronized,所以是线程安全的,自然效率就低
核心方法 grow
**
源码解读:
**
//真正存放element的数组
protected Object[] elementData;
//数组的真实长度
protected int elementCount;
//向量增量
protected int capacityIncrement;
//首先add方法被synchronized修饰,表示该方法是线程安全的
public synchronized void addElement(E obj) {
//与ArrayList的add方法一样,modCount 表示修改次数。每次新增对象就会加1
modCount++;
//elementCount表示底层数组中存放对象的真实数量,每次新增对象时,真实数量就要加1
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
private void ensureCapacityHelper(int minCapacity) {
// minCapacity表示最小容量,假如原来数组中有10个对象,那么新增1个对象,那么最小容量就是11,否则装不下
// 最小容量减去数组的真实容量,如果大于0,就执行扩容操作
if (minCapacity - elementData.length > 0)
//真正扩容的方法,grow 表示 生长的意思
grow(minCapacity);
}
private void grow(int minCapacity) {
// 获取数组的真实容量(实际存放成员的个数)
int oldCapacity = elementData.length;
// 由于capacityIncrement = 0 ,因此newCapacity = oldCapacity + oldCapacity
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将数组容量扩容至原来的2倍,并保留原数据
elementData = Arrays.copyOf(elementData, newCapacity);
}
其中add方法与其类似,只不过add返回了一个boolean值,而addElement无返回值
实际上这个addElement方法就是每次判断数组是否满了,未满则直接将元素放入栈顶,如果满了则将栈的空间在旧elementData.length的基础上扩充一倍,然后将新的元素放入栈顶。
总结
1.调用无参构造器创建Vector
①首次将底层数组的容量先被初始化为10。
②当Vector底层数组被填满后,再次add对象时,底层数组的容量会扩容至原来的2倍,并将原数组数据拷贝进来。
3.调用有参构造器创建Vector
①首先将底层数组初始化为指定入参的大小。
②当ArrayList底层数组被填满后,再次add对象时,底层数组的容量会扩容至原来的2倍,并将原数组数据拷贝进来。## 标题
pop方法解读:
出栈时,先通过peek方法拿到栈顶元素,然后删除栈顶元素,返回元素。pop方法也是线程安全的,注意这里,与peek方法的区别,pop会拿到栈顶元素并删除,而peek只会拿到栈顶元素,并不会删除:
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
//移除栈顶的元素
removeElementAt(len - 1);
return obj;
}
删除元素也是通过父类Vector里面的removeElementAt方法实现:
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
这里的System.arraycopy(elementData, index + 1, elementData, index, j);实际上就是将整个elementData数组从index+1开始向前移一位,将index所在位置的值覆盖掉,以此来达到remove的效果,复制后将栈顶置空,方便GC回收内存。在pop方法中,传入的是len-1,因此每次 pop 都只会移除栈顶上的元素。System.arrayCopy方法如下
/**
*将 源数组src 从起点位置srcPos开始的lenght长度的部分,
*复制到目标数组的destPos位置
* @param src 源数组.
* @param srcPos 源数组的起点位置.
* @param dest 目标数组.
* @param destPos (复制的内容放在)目标数组的开始位置.
* @param length 需要copy的数组长度.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
能力有限,希望,我的总结能帮到各位,如有不对,请不吝赐教,谢谢。