细说Java中的数据结构-栈

      说到栈,或许不陌生,先进后出,后进先出等等的特点,在日常的编程中也经常用到,但是Java中如何去使用栈,通过这篇文章来详细了解一下

Java中如何使用栈

      栈是一种数据结构,可以分为链式栈数组栈,顾名思义,链式栈的底层是用链表实现的,数组栈的底层是用数组实现的,下面就来谈一下JDK中的栈。

      很多人都说Java中官方实现了专门的栈类Stack,该类位于java.util包下,一起来看一下继承结构:
在这里插入图片描述
      该类继承了Vector类,Vector类是Java中已经淘汰的一个类,该类底层是数组实现,并且是线程安全的,Stack类继承了该类后就具有了这两个特性,这两个特性也是Stack最大的缺点:

  1. 底层使用数组,具体的来说就是动态数组,扩容方式需要重新申请大的内存空间 ,而且要把原数组中的值拷贝,效率是很低的
  2. 该类线程安全,从另一方面来说,确保了线程安全就需要一定的开销,那么在很多场景下是不需要线程安全的,所以这个功能显得有些赘余,降低效率。

既然JDK提供的Stack类有这两个缺点,那么在实际的使用中应该如何去使用栈?

      在Java中提供了一个类LinkedList,该类相信有些人不会陌生,链表啊,但是该类其实不光是链表,其实还是链式栈链式队列,该类提供了一些方法来满足栈和队列的功能,而且该栈的底层是链表实现的,所以扩容就不用担心了,不存在数组动态扩容的低效问题,并且非线程安全,所以完美的解决了上面的两个缺点。一起来看一下常用方法:

push : 入栈
pop : 出栈
peek : 返回栈顶元素
empty : 是否为空
size : 返回栈中元素的个数

用法非常简单,要想更加深入的了解栈,一起来实现一下:

自己实现一个栈
数组栈
class MyStack<T> {
    private T[] elemData;
    private int size;
    private static final int DEFAULT_CAPACITY = 10;
    public MyStack() {
        elemData = (T[])new Object[10];
        size = 0;
    }
    //判断栈满
    public boolean isFull() {
        return size >= elemData.length;
    }
    //判断栈空
    public boolean isEmpty() {
        return size <= 0;
    }
    //扩容
    private void grow() {
        elemData = Arrays.copyOf(elemData,elemData.length*2+2);
    }
    //入栈
    public boolean push(T data) {
        if(isFull()) {
            grow();
        }
        elemData[size++] = data;
        return true;
    }
    //出栈
    public T pop() {
        if(isEmpty()) {
            try {
                throw new IllegalAccessException("栈空,不可出栈");
            } catch (IllegalAccessException e) {
                e.printStackTrace();;
            }
        }
        T temp = elemData[--size];
        //防止内存泄漏
        elemData[size] = null;
        return temp;
    }
    //获得栈顶元素
    public T peek() {
        if(isEmpty()) {
            try {
                throw new IllegalAccessException("栈空,无栈顶元素");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return elemData[size-1];
    }
}

      下面来解释一下这段代码,这是自己实现的一个栈,,使用泛型进行编写,内部是使用数组进行实现的,需要注意的有几点:

  1. 因为底层是使用数组进行编写,所以为了防止开辟的大小不够,改进为动态数组,也就是每次入栈元素前先进行判断是否需要扩容,如果需要扩容则进行2倍扩容,开辟新的数组把原数组的值拷贝过来
  2. 在出栈的时候要注意栈空
  3. 为了防止内存泄漏,在出栈的时候记得把不用的对象置为空
链式栈
class MiNiStack<T> {
    //定义top指针
    private Entry<T> top;

    public MiNiStack() {
        this.top = new Entry<>();
    }

    /**
     * 入栈,相当于链表的头插过程
     * @param data
     */
    public void push(T data) {
        Entry<T> entry = new Entry<>(data, top.next);
        top.next = entry;
    }

    /**
     * 出栈,删除第一个节点
     * @return
     */
    public T pop() {
        if(isEmpty()) {
            throw new IllegalArgumentException("栈空,出栈失败");
        }
        T var = top.next.data;
        top.next = top.next.next;
        return var;
    }

    /**
     * 返回栈中元素的个数
     * @return
     */
    public int size() {
        int size = 0;
        Entry<T> cur = top.next;
        while (cur != null) {
            size++;
            cur = cur.next;
        }
        return size;
    }

    /**
     * 判断栈是否为空
     * @return
     */
    private boolean isEmpty() {
        return top.next == null;
    }

    //定义节点Entry
    class Entry<T>{
        private T data;
        private Entry<T> next;

        public Entry() {
            this(null,null);
        }

        public Entry(T data, Entry<T> next) {
            this.data = data;
            this.next = next;
        }
    }
}
  1. 链式栈的实现底层使用了链表,这样就不用担心扩容的问题,一定程度上提高了效率
  2. 在实现上是把链表的头部当做栈顶,入栈和出栈都是从链表的头部进行操作
  3. 入栈其实就是链表的头插操作
  4. 出栈直接删除链表的第一个数据节点
总结

      综上,我们是不推荐使用jdk提供的Stack,除非是并发情况下。可以使用LinkedList或者自己去实现一个泛型的链式栈,把功能实现的完备一点,方便自己的经常使用。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8之后的ConcurrentHashMap在底层实现上进行了一些改进,主要包括以下两个方面: 1. 数据结构的改进:Java 8之前的ConcurrentHashMap使用了分段锁的方式实现并发控制,内部结构是由多个Segment(段)组成的数组。而Java 8及以后的版本,引入了一种称为"扁平化"(Flattening)的方式来提高并发性能。它将整个数据结构分成了多个独立的桶(Bucket),每个桶内部都是一个链表或红黑树的结构,用于存储键值对。这种扁平化的数据结构减少了锁的粒度,提高了并发性能。 2. 使用CAS操作:Java 8之后的ConcurrentHashMap在并发控制方面使用了CAS(Compare and Swap)操作。CAS是一种乐观锁技术,通过比较并交换的方式来实现线程间的同步。它不需要使用传统的锁机制,避免了线程阻塞和上下文切换的开销,可以提高并发性能。 具体来说,Java 8之后的ConcurrentHashMap采用了以下策略: - 在读取操作(如get)时,不需要加锁,可以实现高效的并发读取。 - 在更新操作(如put、remove)时,使用CAS操作进行原子性的操作,避免了加锁的开销。 - 在冲突(多个线程同时操作一个桶)发生时,采用自旋和重试的方式进行处理,以保证数据的一致性。 总之,Java 8之后的ConcurrentHashMap通过改进数据结构和使用CAS操作,提高了并发性能和可伸缩性。它在高并发场景下具有更好的性能表现,并且保证了数据的一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值