自学内容网 自学内容网

使用ArrayList逐步手撕stack

在日常开发中,Stack 是一个非常经典的数据结构,用于实现后进先出(LIFO,Last In First Out)的功能。Java 标准库里已经提供了 Stack 类,但它实际上是基于 Vector 实现的,性能一般,而且不推荐用于高并发场景。那么我们就自己手撕一个!

今天我们就用 ArrayList 来实现一个自定义的轻量级、灵活性更高的栈 Stack,顺便把源码中的一些核心方法拆开理解清除。


1,为什么选择 ArrayList 实现 Stack?

ArrayList 是 Java 中常用的动态数组,它有几个非常适合实现栈功能的特点:

  1. 动态扩展:     ArrayList 会根据需要动态调整容量,避免手动扩容的麻烦。

  2. 末尾操作快: ArrayList 在尾部的增删操作是 O(1)O(1)O(1),完全符合栈的需求。

  3. 够轻量:        相比 Stack(基于线程安全的 Vector),ArrayList 不带锁,性能更高。

2,所需要实现栈的哪些基本功能

(1)push(E item):向栈顶添加元素。

(2)pop():移除并返回栈顶元素。

(3)peek():获取栈顶元素但不移除。

(4)isEmpty():判断栈是否为空。

(5)size():获取栈的大小。

3,实现代码

import java.util.ArrayList;

public class MyStack<E> {
    private ArrayList<E> list; // 用来存储栈元素

    public MyStack() {
        list = new ArrayList<>(); // 初始化 ArrayList
    }

    // 添加元素到栈顶
    public void push(E item) {
        list.add(item); // 添加到 ArrayList 的末尾
    }

    // 移除并返回栈顶元素
    public E pop() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty, cannot pop!");
        }
        return list.remove(list.size() - 1); // 移除并返回最后一个元素
    }

    // 获取栈顶元素但不移除
    public E peek() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty, cannot peek!");
        }
        return list.get(list.size() - 1); // 返回最后一个元素
    }

    // 判断栈是否为空
    public boolean isEmpty() {
        return list.isEmpty(); // ArrayList 自带的方法
    }

    // 获取栈的大小
    public int size() {
        return list.size(); // ArrayList 自带的方法
    }

   // 重写toString来方便测试等等
     @Override
    public String toString() {
        return String.valueOf(list);
    }
}

4,逐行拆解源码,知其然更知其所以然

接下来,让我们结合 ArrayList 的源码,拆解一下 MyStack 的核心方法。

1. push:添加元素到栈顶

public void push(E item) {
    list.add(item);
}
底层源码(ArrayListadd 方法):
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 确保容量足够
    elementData[size++] = e;          // 将元素添加到数组末尾
    return true;
}
扩容机制: 当容量不足时,ArrayList 会扩容为当前容量的 1.5 倍,具体操作:
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩展为 1.5 倍
    elementData = Arrays.copyOf(elementData, newCapacity);
}
特点:

        绝大多数情况下,添加元素的时间复杂度是 O(1)O(1)O(1)。

        当需要扩容时,时间复杂度为 O(n)O(n)O(n)。

2. pop:移除并返回栈顶元素

public E pop() {
    if (isEmpty()) {
        throw new RuntimeException("Stack is empty, cannot pop!");
    }
    return list.remove(list.size() - 1);
}
底层源码(ArrayListremove 方法):
public E remove(int index) {
    rangeCheck(index); // 检查索引合法性
    modCount++; // 记录结构修改次数(支持快速失败)

    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    elementData[--size] = null; // 清理最后一个元素,帮助垃圾回收
    return oldValue;
}
分析:

        移除操作需要移动数组中的元素。

        但因为我们总是移除最后一个元素,所以无需移动其他元素,效率很高。

3. peek:获取栈顶元素但不移除

public E peek() {
    if (isEmpty()) {
        throw new RuntimeException("Stack is empty, cannot peek!");
    }
    return list.get(list.size() - 1);
}
底层源码(ArrayListget 方法):
public E get(int index) {
    rangeCheck(index); // 检查索引合法性
    return elementData(index);
}
特点:
  • 获取最后一个元素的时间复杂度是 O(1)O(1)O(1)。
  • 没有对数组进行修改,仅仅返回数据。

4. isEmpty:判断栈是否为空

public boolean isEmpty() {
    return list.isEmpty();
}
底层源码(ArrayListisEmpty 方法):
public boolean isEmpty() {
    return size == 0; // 判断大小是否为 0
}

解析:

  • 直接读取 size 字段,时间复杂度为 O(1)。

5. size:获取栈的大小

底层源码(ArrayListsize 方法):

public int size() {
    return size; // 直接返回大小
}

测试实例:

总结

通过手撕一个基于 ArrayList 的栈,我们可以发现:

  1. ArrayList 提供的动态扩容和快速访问非常适合栈的实现。
  2. 自定义栈更加灵活,可以根据业务需求进一步扩展功能。
  3. 当然,如果在多线程环境中使用,还需要手动添加同步机制(比如 Collections.synchronizedList())。

自己动手实现一遍,既能加深对栈的理解,也能学到 ArrayList 的底层设计精髓!赶紧试试看吧~如果你有更好的思路或者疑问,欢迎在评论区交流~


原文地址:https://blog.csdn.net/ymr1843762281/article/details/143804575

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!