滚雪球学Java(18):解密JavaSE中的堆栈:你真的了解Java内存吗?

简介: 【4月更文挑战第7天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

在这里插入图片描述


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!


前言

  在 Java 编程中,堆栈是非常常见的数据结构。堆栈是一种线性数据结构,它具有后进先出 (Last In First Out, LIFO) 的特性。在 Java 中,堆栈可以使用数组或链表实现。本文旨在介绍 Java 的堆栈的实现方式,并提供一些相关的代码示例。

摘要

  本文主要介绍了 Java 中堆栈的实现方式以及相关的代码示例。首先,我们介绍了堆栈的基本概念以及其操作。接着,我们分别介绍了使用数组和链表实现堆栈的方法,并提供了相应的代码示例。最后,我们总结了本文的内容,并提出了一些进一步的思考。

正文

1. 堆栈的基本概念和操作

  堆栈是一种线性数据结构,它具有后进先出 (Last In First Out, LIFO) 的特性。堆栈通常支持两种基本操作:

  • 入栈 (push):将一个元素放入堆栈顶部;
  • 出栈 (pop):将堆栈顶部的元素移除;

  除此之外,堆栈还可以支持一些其他的操作,例如:

  • 获取栈顶元素 (peek):查看堆栈顶部的元素,但不将其移除;
  • 判断堆栈是否为空 (isEmpty):判断堆栈是否为空;
  • 获取堆栈中元素的个数 (size):获取堆栈中元素的个数;

2. 使用数组实现堆栈

  使用数组实现堆栈非常简单,我们只需要定义一个数组和一个指针,指针指向堆栈顶部元素的下一个位置。入栈操作就是将元素放入数组的当前指针位置,然后指针加一;出栈操作就是将指针减一,然后返回当前指针位置的元素。

public class ArrayStack<E> {
   

    private int top; // 栈顶指针
    private E[] array;

    public ArrayStack(int capacity) {
   
        array = (E[]) new Object[capacity];
        top = 0;
    }

    public void push(E element) {
   
        if (top >= array.length) {
   
            throw new StackOverflowError();
        }
        array[top++] = element;
    }

    public E pop() {
   
        if (isEmpty()) {
   
            throw new EmptyStackException();
        }
        return array[--top];
    }

    public E peek() {
   
        if (isEmpty()) {
   
            throw new EmptyStackException();
        }
        return array[top - 1];
    }

    public boolean isEmpty() {
   
        return top == 0;
    }

    public int size() {
   
        return top;
    }
}

  上面的代码中,我们使用泛型来定义了 ArrayStack 类,它可以存储任意类型的元素。在构造方法中,我们创建了一个指定容量的数组和一个初始值为 0 的栈顶指针。在 push 方法中,如果栈已满,就抛出一个 StackOverflowError 异常;否则,就将元素放入数组当前指针位置,然后指针加一。在 pop 方法中,如果栈为空,就抛出一个 EmptyStackException 异常;否则,就将指针减一,然后返回当前指针位置的元素。peek、isEmpty 和 size 方法也是类似的实现。

拓展:

  此代码实现了一个基于数组的栈结构。具体分析如下:

  1. 类定义:public class ArrayStack<E> 表示这是一个泛型类,使用泛型类型参数 E 表示栈中元素的类型。

  2. 私有字段:private int top; 表示栈顶指针,标识当前栈顶元素的位置。private E[] array; 表示存储栈元素的数组。

  3. 构造方法:public ArrayStack(int capacity) 接受一个整型参数 capacity,表示栈的容量。在构造方法内部,通过 (E[]) new Object[capacity] 创建了一个数组,并将其赋值给 array 字段。此处使用类型转换 (E[]) 是因为 Java 的泛型不允许直接创建泛型数组。

  4. push(E element) 方法:将元素 element 入栈。首先检查栈是否已满,即 top >= array.length,如果已满则抛出 StackOverflowError 异常。否则,将 element 存储到 array[top],并将 top 指针向上移动一位。

  5. pop() 方法:出栈操作,即移除并返回栈顶元素。首先检查栈是否为空,即 isEmpty() 方法返回 true,如果为空则抛出 EmptyStackException 异常。否则,将 top 指针向下移动一位,并返回 array[top]

  6. peek() 方法:返回栈顶元素,但不移除它。首先检查栈是否为空,即 isEmpty() 方法返回 true,如果为空则抛出 EmptyStackException 异常。否则,返回 array[top - 1]

  7. isEmpty() 方法:判断栈是否为空,即 top == 0

  8. size() 方法:返回栈的元素个数,即 top 的值。

  需要注意的是,该代码实现没有对栈的容量进行动态扩容,如果栈满时继续入栈会抛出 StackOverflowError 异常。在使用时需要注意栈的容量是否足够。还有,由于使用了泛型,需要在创建栈对象时传入具体的元素类型参数。

3. 使用链表实现堆栈

  使用链表实现堆栈也是一种常见的方式。链表的头部代表堆栈顶部元素,因此入栈操作就是在链表头部插入元素,出栈操作就是从链表头部移除元素。

public class LinkedStack<E> {
   

    private Node top; // 栈顶节点
    private int size; // 元素个数

    private class Node {
   
        E element;
        Node next;

        public Node(E element, Node next) {
   
            this.element = element;
            this.next = next;
        }
    }

    public void push(E element) {
   
        top = new Node(element, top);
        size++;
    }

    public E pop() {
   
        if (isEmpty()) {
   
            throw new EmptyStackException();
        }
        E element = top.element;
        top = top.next;
        size--;
        return element;
    }

    public E peek() {
   
        if (isEmpty()) {
   
            throw new EmptyStackException();
        }
        return top.element;
    }

    public boolean isEmpty() {
   
        return top == null;
    }

    public int size() {
   
        return size;
    }
}

  上面的代码中,我们定义了一个 Node 类作为链表的节点,每个节点包含一个元素和一个指向下一个节点的指针。在类中,我们定义了一个头节点 top 和一个元素个数 size。在 push 方法中,我们创建一个新的节点,并将它作为新的头节点;在 pop 方法中,我们移除当前头节点,并将下一个节点作为新的头节点。peek、isEmpty 和 size 方法也是类似的实现。

拓展:

  该代码实现了一个基于链表的栈数据结构。栈是一种特殊的线性数据结构,具有先进后出(LIFO)的特点。

  该类中有两个内部类:Node和LinkedStack。Node类用于表示栈中的节点,保存元素和下一个节点的引用;LinkedStack类是栈的实现类,包括栈顶节点和元素个数。

代码中的主要方法包括:

  1. push方法:将元素压入栈顶。创建一个新的节点,将该节点设置为栈顶节点的下一个节点,并将栈顶节点更新为新节点。同时,元素个数加一。

  2. pop方法:弹出栈顶元素。如果栈为空,则抛出EmptyStackException异常。否则,获取栈顶节点的元素,并将栈顶节点更新为其下一个节点。同时,元素个数减一。

  3. peek方法:返回栈顶元素,但不弹出。如果栈为空,则抛出EmptyStackException异常。

  4. isEmpty方法:判断栈是否为空。如果栈顶节点为null,则认为栈为空。

  5. size方法:返回栈中元素的个数。

  这个实现基于链表的栈相比于基于数组的栈,具有动态性,可以根据实际情况调整栈的大小。但也因此牺牲了一些性能,如访问元素的时间复杂度在最坏情况下为O(n),而不是O(1)。

  需要注意的是,该代码中使用了泛型E表示元素的类型,可以为任意类型。

4. 测试用例

为了验证我们的实现是否正确,我们编写了以下测试用例:

@Test
public void testArrayStack() {
   
    ArrayStack<Integer> stack = new ArrayStack<>(10);
    assertTrue(stack.isEmpty());
    assertEquals(0, stack.size());

    stack.push(1);
    stack.push(2);
    stack.push(3);
    assertFalse(stack.isEmpty());
    assertEquals(3, stack.size());
    assertEquals(3, (int) stack.pop());
    assertEquals(2, (int) stack.peek());
    assertEquals(2, (int) stack.pop());
    assertEquals(1, (int) stack.pop());

    assertThrows(EmptyStackException.class, stack::pop);
    assertThrows(EmptyStackException.class, stack::peek);
}

@Test
public void testLinkedStack() {
   
    LinkedStack<Integer> stack = new LinkedStack<>();
    assertTrue(stack.isEmpty());
    assertEquals(0, stack.size());

    stack.push(1);
    stack.push(2);
    stack.push(3);
    assertFalse(stack.isEmpty());
    assertEquals(3, stack.size());
    assertEquals(3, (int) stack.pop());
    assertEquals(2, (int) stack.peek());
    assertEquals(2, (int) stack.pop());
    assertEquals(1, (int) stack.pop());

    assertThrows(EmptyStackException.class, stack::pop);
    assertThrows(EmptyStackException.class, stack::peek);
}

  在测试用例中,我们先分别创建了一个数组栈和一个链表栈,并进行了一系列 push、pop、peek、isEmpty 和 size 操作的验证。最后,我们使用 assertThrows 方法验证了在栈为空时,pop 和 peek 操作是否会抛出 EmptyStackException 异常。

拓展:

  这段代码是对栈数据结构进行单元测试的代码。

  首先,定义了两个测试方法:testArrayStack()testLinkedStack()。分别对基于数组和链表实现的栈进行测试。

  在testArrayStack()方法中,首先创建了一个容量为10的ArrayStack对象,并进行了一系列断言验证。验证了栈的初始状态应该为空,大小为0。

  然后,通过push()方法向栈中添加了三个元素:1,2和3。进行了一系列断言验证:栈不为空,大小为3。然后依次进行了pop()peek()操作,并进行了相应的断言验证。

  最后,使用assertThrows()方法验证了在栈为空时进行pop()peek()操作会抛出EmptyStackException异常。

  testLinkedStack()方法与testArrayStack()方法的逻辑类似,不过是使用了LinkedStack类来进行测试。

5. 小结

  本文介绍了 Java 中堆栈的基本概念和操作,以及使用数组和链表分别实现堆栈的方法。我们还提供了相应的代码示例和测试用例。在实际编程中,我们可以根据实际情况选择不同的堆栈实现方式。在使用堆栈时,我们需要确保堆栈中的元素满足后进先出的原则。

总结

  本文介绍了 Java 中堆栈的实现方式以及基本概念和操作。在 Java 中,堆栈是一种非常常见的数据结构,它具有后进先出 (Last In First Out, LIFO) 的特性。堆栈通常支持入栈、出栈、获取栈顶元素、判断堆栈是否为空以及获取堆栈中元素个数等基本操作。

  在 Java 中,我们可以使用数组或链表来实现堆栈。使用数组实现堆栈非常简单,我们只需要定义一个数组和一个指针,指针指向堆栈顶部元素的下一个位置。入栈操作就是将元素放入数组的当前指针位置,然后指针加一;出栈操作就是将指针减一,然后返回当前指针位置的元素。使用链表实现堆栈也是一种常见的方式,链表的头部代表堆栈顶部元素。入栈操作就是在链表头部插入元素,出栈操作就是从链表头部移除元素。

  在实际编程中,我们可以根据实际需求选择不同的堆栈实现方式。使用数组实现的堆栈通常需要指定一个固定的容量,而链表实现的堆栈可以根据需要动态扩展。无论使用哪种实现方式,我们都需要确保堆栈中的元素满足后进先出的原则。

  最后,我们编写了相应的测试用例来验证数组和链表实现的堆栈是否正常工作。在编写测试用例时,我们对入栈、出栈、获取栈顶元素、判断堆栈是否为空以及获取堆栈中元素个数等操作进行了验证。

  ... ...

  好啦,这期的内容就基本接近尾声啦,若你想学习更多,你可以看看专栏的导读篇《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。功不唐捐,久久为功!

「赠人玫瑰,手留余香」,咱们下期拜拜~~

附录源码

  如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你

  无论你是计算机专业的学生,还是对编程感兴趣的跨专业小白,都建议直接入手「滚雪球学Java」专栏;该专栏不仅免费,bug菌还郑重承诺,只要你学习此专栏,均能入门并理解Java SE,以全网最快速掌握Java语言,每章节源码均同步「Gitee」,你真值得拥有;学习就像滚雪球一样,越滚越大,带你指数级提升。

  码字不易,如果这篇文章对你有所帮助,帮忙给bugj菌来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。

  同时也推荐大家关注我的硬核公众号:「猿圈奇妙屋」 ;以第一手学习bug菌的首发干货,不仅能学习更多技术硬货,还可白嫖最新BAT大厂面试真题、4000G Pdf技术书籍、万份简历/PPT模板、技术文章Markdown文档等海量资料,你想要的我都有!


目录
相关文章
|
6天前
|
存储 缓存 Java
Java内存模型
Java内存模型
8 0
|
1天前
|
缓存 监控 算法
【Java】Java内存溢出:原因、预防和解决方法
【Java】Java内存溢出:原因、预防和解决方法
9 2
|
1天前
|
存储 分布式计算 监控
Java一分钟之-Hazelcast:内存数据网格
【6月更文挑战第17天】**Hazelcast是开源的内存数据网格(IMDG),加速分布式环境中的数据访问,提供内存存储、分布式计算、线性扩展及高可用性。常见挑战包括内存管理、网络分区和数据分布不均。通过配置内存限制、优化网络和分区策略可避免问题。示例展示如何创建Hazelcast实例并使用分布式Map。使用Hazelcast提升性能和扩展性,关键在于理解和调优。**
17 1
|
1天前
|
Java 测试技术 C++
滚雪球学Java(63):Java高级集合之TreeSet:什么是它,为什么使用它?
【6月更文挑战第17天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
8 0
滚雪球学Java(63):Java高级集合之TreeSet:什么是它,为什么使用它?
|
2天前
|
存储 缓存 算法
滚雪球学Java(62):HashSet的底层实现原理解析
【6月更文挑战第16天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 3
滚雪球学Java(62):HashSet的底层实现原理解析
|
3天前
|
存储 Java 测试技术
滚雪球学Java(61):从源码角度解读Java Set接口底层实现原理
【6月更文挑战第15天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
13 1
滚雪球学Java(61):从源码角度解读Java Set接口底层实现原理
|
4天前
|
存储 缓存 安全
深入理解 Java 内存模型(Java Memory Model, JMM)
深入理解 Java 内存模型(Java Memory Model, JMM)
7 0
|
4天前
|
存储 安全 Java
滚雪球学Java(60):深入解析Java中的Vector集合类!
【6月更文挑战第14天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
139 59
滚雪球学Java(60):深入解析Java中的Vector集合类!
|
5天前
|
存储 缓存 Java
滚雪球学Java(59):从基础到高阶:Java中LinkedList的操作指南
【6月更文挑战第13天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 1
滚雪球学Java(59):从基础到高阶:Java中LinkedList的操作指南
|
6天前
|
存储 Java 测试技术
滚雪球学Java(57):解密Java中List接口底层实现原理
【6月更文挑战第11天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
21 2
滚雪球学Java(57):解密Java中List接口底层实现原理

热门文章

最新文章