当前位置: 首页 > news >正文

集合类

栈(Stack)和队列(Queue)

1. 栈的原理和使用场景

栈(stack)又名堆栈,它是一种运算受限的线性表,限定仅在表尾进行插入和删除操作的线性表其是先进后出栈的尾部(即最后进入栈的那一段)被叫做栈顶(top), 另一端叫做栈底, 在栈顶进行插入的操作叫做入栈(或压栈), 在栈顶进行删除的操作叫做出栈(或弹栈)。栈没有编号和索引只能从栈顶向栈尾弹出数据。

最先入栈的数据被压在栈底,最后入栈的数据处在栈顶,读取数据时从栈顶开始依次向栈底读取。

栈的基本操作

import java.util.Stack;public class num1 {public static void main(String[] args) {Stack<String> stack = new Stack<>();//1.入栈操作(push)stack.push("1");stack.push("2");stack.push("3");//2.出栈操作(pop)System.out.println(stack.pop()); //会返回出栈的元素//3.获取栈顶元素(peek)System.out.println(stack.peek());//4.判断栈是否为空(isEmpty)System.out.println(stack.isEmpty());//5.获取栈的大小(size)System.out.println(stack.size());//6.清空栈(clear)stack.clear();}
}

 顺序栈(基于数组实现)

package step1;import java.util.NoSuchElementException;/*** Created by sykus.*/
public class MyStack<T> {private T[] S;private int top; // 栈顶元素下标,初始为-1public MyStack() {this(10); // 默认初始容量设为10更合理}public MyStack(int capacity) {if (capacity <= 0) {throw new IllegalArgumentException("容量必须大于0");}S = (T[]) new Object[capacity];top = -1;}/*** 入栈操作,把item压入栈中** @param item*/public void push(T item) {if (item == null) {throw new IllegalArgumentException("不允许压入null元素");}int len = S.length;if (top == len - 1) {resize(2 * len);}S[++top] = item;}/*** 返回栈顶元素并从栈中移除** @return*/public T pop() {if (isEmpty()) {throw new NoSuchElementException("栈为空!");}T item = S[top];S[top--] = null; // 避免内存泄漏// 缩容机制:当元素数量小于容量的1/4时,将容量减半if (top > 0 && top == S.length / 4) {resize(S.length / 2);}return item;}/*** 获取栈顶元素但不移除* * @return 栈顶元素*/public T peek() {if (isEmpty()) {throw new NoSuchElementException("栈为空!");}return S[top];}/*** 判断栈是否为空** @return*/public boolean isEmpty() {return top < 0;}/*** 返回栈中元素的数量* * @return 元素数量*/public int size() {return top + 1;}/*** 动态扩展数组大小** @param capacity*/private void resize(int capacity) {assert capacity > top;T[] tmp = (T[]) new Object[capacity];for (int i = 0; i <= top; i++) {tmp[i] = S[i];}S = tmp;}}

链式栈(基于链表实现) 

package step2;import java.util.NoSuchElementException;/*** 基于链表实现的栈结构*/
public class MyStack<E> {private Node<E> head;  // 头结点(虚拟节点,不存储数据)private Node<E> top;   // 栈顶指针private int size;      // 栈的元素数量public MyStack() {head = new Node<>();head.next = null;top = null;size = 0;}/*** 将元素压入栈顶*/public void push(E item) {if (item == null) {throw new IllegalArgumentException("栈不允许存储null元素");}Node<E> newNode = new Node<>();newNode.item = item;newNode.next = head.next;head.next = newNode;top = newNode;size++;}/*** 弹出并返回栈顶元素*/public E pop() {if (isEmpty()) {throw new NoSuchElementException("栈为空");}Node<E> temp = top;top = top.next;head.next = top;  // 头结点指向新的栈顶(若栈为空,head.next自动为null)size--;return temp.item;}/*** 返回栈顶元素但不弹出*/public E peek() {if (isEmpty()) {throw new NoSuchElementException("栈为空");}return top.item;}/*** 判断栈是否为空*/public boolean isEmpty() {return size == 0;}/*** 返回栈的元素数量*/public int size() {return size;}/*** 清空栈*/public void clear() {top = null;head.next = null;size = 0;}// 链表节点类private static class Node<E> {private E item;private Node<E> next;}
}

使用场景

函数调用栈:在程序运行时,每进行一个函数运算就把数据入栈,在下一个函数需要这个数据时出栈,这时获取的数据就是最新的

表达式求值:中缀表达式(如3+4*2)转后缀表达式(3 4 2 * +)及后缀表达式计算均依赖栈:运算符和操作数按规则入栈 / 出栈,实现优先级处理。

括号匹配:验证字符串中括号(()[]{})的嵌套有效性,左括号入栈,右括号与栈顶匹配,最终栈空则有效。

浏览器前进 / 后退:用两个栈分别存储 “前进历史” 和 “后退历史”,点击后退时当前页面入前进栈,后退栈顶页面出栈并显示;点击前进则相反。

2.队列的原理和使用场景

队列是一种先进先出的数据结构,进行插入操作的端称为队尾,进行删除操作的端称为队头,当队列中没有元素时,称为空队列,在队列中插入数据的操作为入队,在队列中删除数据的操作为出队。

最先入队的数据在队头,最后入队的数据在队尾,读取数据时从队头开始依次向队尾读取。

队列的基本操作

import java.util.LinkedList;
import java.util.Queue;public class num1 {public static void main(String[] args) {Queue<String> queue = new LinkedList<>();//1.入队操作(offer)queue.offer("1");queue.offer("2");queue.offer("3");//2.出队操作(poll)System.out.println(queue.poll());//会返回出队元素//3.获取队首元素(peek)System.out.println(queue.peek());//4.判断队列是否为空(isEmpty)System.out.println(queue.isEmpty());//5.获取队列的大小(size)System.out.println(queue.size());//6.清空队列(clear)queue.clear();}
}

循环队列(基于数组实现)

package step3;import java.util.NoSuchElementException;/*** 基于循环数组实现的队列*/
public class MyQueue<T> {private T[] Q;private int head;private int tail;private int size;public MyQueue() {this(10); // 默认容量设为10更合理}public MyQueue(int capacity) {if (capacity <= 0) {throw new IllegalArgumentException("容量必须大于0");}Q = (T[]) new Object[capacity];size = 0;head = tail = 0;}/*** 入队操作*/public void enqueue(T item) {if (item == null) {throw new IllegalArgumentException("不允许入队null元素");}if (isFull()) {resize(2 * Q.length); // 队列满时扩容为原来的2倍}Q[tail] = item;tail = (tail + 1) % Q.length;size++;}/*** 出队操作*/public T dequeue() {if (isEmpty()) {throw new NoSuchElementException("队列为空");}T item = Q[head];Q[head] = null; // 避免内存泄漏head = (head + 1) % Q.length;size--;// 当元素数量小于容量的1/4时,缩容为原来的1/2if (size > 0 && size == Q.length / 4) {resize(Q.length / 2);}return item;}/*** 返回队首元素但不出队*/public T peek() {if (isEmpty()) {throw new NoSuchElementException("队列为空");}return Q[head];}/*** 判断队列是否为空*/public boolean isEmpty() {return size == 0;}/*** 判断队列是否已满*/public boolean isFull() {return size == Q.length;}/*** 返回队列中元素的数量*/public int size() {return size;}/*** 动态调整数组容量*/private void resize(int newCapacity) {T[] newArray = (T[]) new Object[newCapacity];for (int i = 0; i < size; i++) {newArray[i] = Q[(head + i) % Q.length];}Q = newArray;head = 0;tail = size;}
}

 链式队列(基于链表实现)

package step4;import java.util.NoSuchElementException;/*** 基于链表实现的队列*/
public class MyQueue<T> {private Node<T> head;    // 头结点,不存储数据private Node<T> front;   // 指向队首元素private Node<T> tail;    // 指向队尾元素private int size;        // 队列元素数量public MyQueue() {head = new Node<>();front = null;tail = null;size = 0;}/*** 入队操作*/public void enqueue(T item) {if (item == null) {throw new IllegalArgumentException("不允许入队null元素");}Node<T> newNode = new Node<>();newNode.item = item;if (isEmpty()) {head.next = newNode;front = newNode;} else {tail.next = newNode;}tail = newNode;size++;}/*** 出队操作*/public T dequeue() {if (isEmpty()) {throw new NoSuchElementException("队列为空");}T item = front.item;head.next = front.next;front = head.next;size--;// 关键修复:若出队后队列为空,更新tail指针if (front == null) {tail = null;}return item;}/*** 返回队首元素但不出队*/public T peek() {if (isEmpty()) {throw new NoSuchElementException("队列为空");}return front.item;}/*** 判断队列是否为空*/public boolean isEmpty() {return front == null;}/*** 返回队列中元素的数量*/public int size() {return size;}/*** 清空队列*/public void clear() {front = null;tail = null;head.next = null;size = 0;}/*** 链表结点内部类*/private static class Node<E> {private E item;private Node<E> next;}
}

队列的使用场景

广度优先搜索(BFS):如二叉树层序遍历、图的最短路径,通过队列按 “层次” 处理元素,确保先访问的节点优先扩展其邻居。

任务调度:多任务按提交顺序执行(如打印机队列、进程调度),先入队的任务先处理,避免插队。

缓冲队列:用于异步通信(如消息队列),解耦生产者(入队)和消费者(出队),平衡速度差异(如日志收集、订单处理)。

ArrayList 与 CopyOnWriteArrayList 的原理与使用场景

1.ArrayList的原理与使用场景

ArrayList是List接口最常用的实现之一,它的底层是基于动态数组实现。

基本特性
  • 允许null元素
  • 有序集合
  • 非同步(线程不安全)
  • 随机访问效率高
  • 插入和删除操作可能较慢(特别是在列表中间)

内部实现

ArrayList内部使用一个对象数组来存储元素。当数组容量不足时,会进行扩容操作。

扩容机制

当ArrayList的大小超过其当前容量时,会触发扩容操作:

  1. 创建一个新的更大的数组(通常是原容量的1.5倍)
  2. 将原数组中的元素复制到新数组
  3. 将新数组赋值给elementData字段
使用场景

ArrayList: 适用于随机访问频繁、插入删除操作较少的场景。

2.CopyOnWriteArrayList 的原理与使用场景

CopyOnWriteArrayList是一个线程安全的List实现,专门设计用于处理读多写少的并发场景。

CopyOnWrite一般就是写时复制:当要对某个对象进行修改时,先对该对象进行复制一份,在副本上进行修改,修改完毕后再将原来的对象指向副本完成更新,因此要注意当对象较大时会耗费较大的空间

特性
  • 线程安全
  • 适用于读多写少的场景
  • 写操作开销大(复制整个数组)
  • 读操作不需要加锁,性能高
实现原理
  • 读操作不加锁,直接返回数组元素(如果有在修改,那就先读取原来的数组)。
  • 写操作会创建一个新的数组副本,在副本上进行修改,然后用新数组替换旧数组。
import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteArrayListExample {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();// 添加元素list.add("Red");list.add("Green");list.add("Blue");System.out.println("初始 list: " + list);// 创建一个读线程Runnable reader = () -> {for (String color : list) {System.out.println(Thread.currentThread().getName() + " 正在读: " + color);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}};// 创建一个写线程Runnable writer = () -> {list.add("Yellow");System.out.println(Thread.currentThread().getName() + " 写入 Yellow");list.remove("Green");System.out.println(Thread.currentThread().getName() + " 删除 Green");};// 启动多个读线程和一个写线程new Thread(reader, "Reader1").start();new Thread(reader, "Reader2").start();new Thread(writer, "Writer").start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("最后的 list: " + list);}
}
使用场景
  • 读操作远多于写操作的并发场景
  • 监听器列表
  • 需要频繁遍历的线程安全列表

HashMap 与 ConcurrentHashMap 的区别与线程安全性

1.HashMap

HashMap效率高,操作速度快,但是只适应于单线程环境,多线程情况下可能会出现数据错误甚至是死循环。

HashMap线程不安全的原因是:HashMap 的操作不是原子性的,多个线程同时操作时会覆盖彼此的数据。

HashMap使用单个数组和链表(或红黑树)存储数据;没有锁,操作简单

多线程不安全案例

HashMap<Integer, String> map = new HashMap<>();
Runnable task = () -> {for (int i = 0; i < 1000; i++) {map.put(i, "Value" + i);}
};
new Thread(task).start();
new Thread(task).start();
System.out.println(map.size()); // 结果可能小于 2000,甚至崩溃!

2.ConcurrentHashMap

ConcurrentHashMap线程安全,支持高并发。ConcurrentHashMap 是为了多线程场景设计的,它通过各种机制(分段锁、CAS 等),在保证线程安全的同时尽可能提高性能。

ConcurrentHashMap虽然线程安全但是其为了保证线程安全牺牲了性能,其在单线程下性能是不如HashMap的,所以在不需要保证线程安全的情况下用HashMap更好些。

ConcurrentHashMap实现原理:

  • JDK 1.7:分段锁,数据被分成多个 Segment,锁粒度小,减少冲突。
  • JDK 1.8:改用 CAS 和红黑树,取消了 Segment,但依然通过细粒度锁保证性能。

 ConcurrentHashMap 有一些特别的操作,比如:

  • putIfAbsent():只有在 key 不存在时才插入值,避免线程安全问题。
  • compute():对某个 key 的值进行原子更新。
  • forEach():支持并发安全的遍历。

对于HashMap的那个案例改用ConcurrentHashMap就能解决

ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
Runnable task = () -> {for (int i = 0; i < 1000; i++) {map.put(i, "Value" + i);}
};
new Thread(task).start();
new Thread(task).start();
System.out.println(map.size()); // 稳定输出 2000

http://www.dtcms.com/a/276379.html

相关文章:

  • 【2024CSP-J初赛】阅读程序(1)试题详解
  • python-while循环
  • Raft-领导者选举
  • import 和require的区别
  • python-range函数
  • jxWebUI--数据表
  • Anthropic:从OpenAI分支到AI领域的领军者
  • 连接池深度解析:原理、实现与最佳实践
  • 第六章 公司分析——基础
  • Kubernetes Volume存储卷概念
  • 骁龙8 Gen4前瞻:台积3nm工艺如何平衡性能与发热
  • 信号量核心机制说明及实际应用(结合ArduPilot代码)
  • C++类模版2
  • 人工智能大语言模型提供了一种打败小朋友十万个为什么的捷径
  • 附件1.2025年世界职业院校技能大赛赛道简介
  • 1. JVM介绍和运行流程
  • 计算机毕业设计springboot的零食推荐系统 基于SpringBoot的在线零食商城个性化推荐平台 JavaWeb驱动的智能零食选购与推荐系统
  • HT8313功放入门
  • 【论文阅读】HCCF:Hypergraph Contrastive Collaborative Filtering
  • 创建uniapp项目引入uni-id用户体系使用beforeRegister钩子创建默认昵称
  • Pandas-数据加载与保存
  • Can201-Introduction to Networking: Application Layer应用层
  • 深入解析 Stack 和 Queue:从原理到实战应用
  • 【读书笔记】从AI到Transformer:LLM技术演进全解析
  • 推荐系统-Random算法
  • jieba 库:中文分词的利器
  • 【Lucene/Elasticsearch】**Query Rewrite** 机制
  • day68—DFS—二叉树的所有路径(LeetCode-257)
  • 微信小程序form组件的使用
  • 从json中提取i18n字段