四则运算Java版

news2025/7/21 20:27:29

数据结构之栈的应用之四则运算_文丑颜不良啊的博客-CSDN博客

之前有写过一篇关于栈的应用之四则运算的文章,是用 C++ 写的,涉及到一些指针的操作,同时,那篇文章有一个致命的错误,就是只支持 10 以内的混合运算,两位数的运算并不支持,所以,为了完善上篇文章的缺点,今天写一篇 Java 版本的四则运算。基本逻辑与 C++ 版相同。

比如,现在有一个混合表达式为:800 / { 5 * [ ( 34 + 46 ) / 8 ] },根据运算规则可得,该表达式的结果为 2。对于计算机而言,需要把中缀表达式转换为后缀表达式之后再进行运算,即将中缀表达式 800 / { 5 * [ ( 34 + 46 ) / 8 ] } 转换为后缀表达式为:800 5 34 46 + 8 / * /

其转换过程一般遵循如下规则:

  • 从左到右先遍历中缀表达式的每个数字和符号
  • 若是数字就直接输出,即成为后缀表达式的一部分;
  • 若是左括号,则直接入栈;
  • 若是运算符号,则判断其与栈顶符号的优先级,若是栈顶元素的优先级大于等于当前运算符的优先级,则栈顶元素依次出栈并输出,并将当前运算符号进栈;(即如果当前运算符是加减,只要栈顶符号不是左括号,则全部出栈并输出,再将加减入栈;若当前运算符为乘除,栈顶元素若是乘除,则将乘除出栈并输出,再将乘除入栈,其余情况下将乘除直接入栈)
  • 若是右括号,则需要与左括号进行匹配,所以栈中元素依次输出,直到左括号输出。

所以,对于中缀表达式  800 / { 5 * [ ( 34 + 46 ) / 8 ] } 来说,使用栈 operatorStack 来存储运算符和括号,使用 tempOperandBuffer 作为操作数的缓冲区,方便处理多为操作数,使用 postfixExpressionArray[] 来临时记录后缀表达式。因为涉及到小括号、中括号和大括号,所以为了方便处理,统一把中括号和大括号替换为小括号,这样的话,大概就可分为三个步骤:

  1. 处理中括号和大括号;
  2. 中缀表达式转换为后缀表达式;
  3. 后缀表达式求值

中缀表达式转换为后缀表达式

对于中缀表达式转换为后缀表达式的步骤,有如下处理过程:

1. 初始化后缀表达式数组 postfixExpressionArray、运算符栈 operatorStack 和操作数缓冲区 tempOperandBuffer ;

2. 处理前 3 个字符,即 '8'、'0'、'0', 因为前 3 个字符都是数字,所以存储进操作数缓冲区 tempOperandBuffer 中,待 flag 标识为 true 时存储进后缀表达式数组中;

3. 处理第 4 个字符,即除号运算符 '/',根据转换规则,需要判断当前运算符与栈顶元素的优先级,因为此时栈为空,所以直接将 '/' 入栈,同时更新 flag 标识,准备后续处理操作数缓冲区的内容;

4. 处理第 5 个字符,即括号 '(',因为是左括号,所以直接入栈即可;

5. 处理第 6 个字符,即操作数 '5',根据转换规则,如果是操作数的话,可以直接根据 flag 标识判断是更新草组数缓冲区还是存储进后缀表达式数组中。因为第 5 个字符 '(' 处理完成后,已经更新 flag 为 true 了,所以此时将操作数缓冲区的内容 "800" 直接存储进后缀表达式数组中,同时更新操作数缓冲区为 "5" 和 flag 标识即可;

6. 处理第 7 个字符,即运算符乘号 '*',根据转换规则,需要判断与栈顶元素的优先级,因为此时栈顶元素为左括号 '(',所以直接将当前运算符入栈并更新 flag 标识即可;

7. 处理第 8、9 个字符,因为是左括号 '(',所以直接入栈并更新 flag 标识即可;

8. 处理第 10 个字符,即操作数 '3',根据转换规则,如果是操作数的话,可以直接根据 flag 标识判断是更新操作数缓冲区还是存储进后缀表达式数组中。因为第 9 个字符 '(' 处理完成后,已经更新 flag 为 true 了,所以此时将操作数缓冲区的内容 "5" 直接存储进后缀表达式数组中,同时更新操作数缓冲区为 "3" 和 flag 标识即可;

 9. 处理第 11 个字符,即操作数 '4',根据转换规则,如果是操作数的话,可以直接根据 flag 标识判断是更新操作数缓冲区还是存储进后缀表达式数组中。因为第 10 个字符 '3' 处理完成后,已经更新 flag 为 false 了,所以此时选择更新操作数缓冲区的内容为 "34" 并更新 flag 标识即可;

10. 处理第 12 个字符,即运算符加号 '+', 根据转换规则,需要判断与栈顶元素的优先级,因为此时栈顶元素为左括号 '(',所以直接将当前运算符入栈并更新 flag 标识即可;

11. 处理第 13、14 个字符,即操作数 '4' 和 '6', 根据转换规则,如果是操作数的话,可以直接根据 flag 标识判断是更新操作数缓冲区还是存储进后缀表达式数组中。因为第 12 个字符 '+' 处理完成后,已经更新 flag 为 true 了,所以此时将操作数缓冲区的内容 "34" 直接存储进后缀表达式数组中,同时更新操作数缓冲区为 "46" 和 flag 标识即可;

12. 处理第 15 个字符,即右括号 ')',根据转换规则,需要匹配左括号 '(',即需要将栈中距离栈顶最近的 '(' 之前的运算符依次出栈,并且存储进后缀表达式数组中,需要注意的是将运算符存储后缀表达式数组之前需要先将操作数缓冲区中的内容(即 "46")存储进后缀表达式数组中,并更新操作数缓冲区和 flag 标识,处理完成之后还需要将栈中的栈顶元素出栈,即将左括号 '(' 出栈,需要注意的是只需要出栈一个左括号即可,所以可以通过 break 实现。于是,处理完成此右括号之后为:

 13. 处理第 16 个字符,即运算符除号 '/',根据转换规则,需要判断与栈顶元素的优先级,因为此时栈顶元素为左括号 '(',所以直接将当前运算符入栈并更新 flag 标识即可;

14. 处理第 17 个字符,即操作数 '8',根据转换规则,如果是操作数的话,可以直接根据 flag 标识判断是更新操作数缓冲区还是存储进后缀表达式数组中。因为第 16 个字符 '/' 处理完成后,已经更新 flag 为 true 了,所以此时选择将操作数缓冲区的内容存储进后缀表达式数组中,同时更新操作数缓冲区的内容为 "8" 并更新 flag 标识即可;

 15. 处理第 18 个字符,即右括号 ')',根据转换规则,需要匹配左括号 '(',即需要将栈中距离栈顶最近的 '(' 之前的运算符依次出栈,并且存储进后缀表达式数组中,需要注意的是将运算符存储后缀表达式数组之前需要先将操作数缓冲区中的内容(即 "8")存储进后缀表达式数组中,并更新操作数缓冲区和 flag 标识,处理完成之后还需要将栈中的栈顶元素出栈,即将左括号 '(' 出栈,需要注意的是只需要出栈一个左括号即可,所以可以通过 break 实现。于是,处理完成此右括号之后为:

16.  处理第 19 个字符,即右括号 ')',根据转换规则,需要匹配左括号 '(',即需要将栈中距离栈顶最近的 '(' 之前的运算符依次出栈,并且存储进后缀表达式数组中,需要注意的是将运算符存储后缀表达式数组之前需要先将操作数缓冲区中的内容存储进后缀表达式数组中,并更新操作数缓冲区和 flag 标识,处理完成之后还需要将栈中的栈顶元素出栈,即将左括号 '(' 出栈,需要注意的是只需要出栈一个左括号即可,所以可以通过 break 实现。于是,处理完成此右括号之后为:

 17. 此时,中缀表达式遍历完成,需要处理栈中剩余元素和操作数缓冲区中的内容。先处理数据缓冲区中的内容,直接存储进后缀表达式数组中即可;然后将栈中剩余元素依次存储进后缀表达式数组中,则处理完成之后为:

 

因此,中缀表达式:800 / { 5 * [ ( 34 + 46 ) / 8 ] } 转换为后缀表达式为:800 5 34 46 + 8 / * /。

后缀表达式求值

规则:从左到右遍历后缀表达式中的每个数字和符号,遇到数字就进栈,遇到符号,就将处于栈顶的两个数字出栈,并进行运算,将运算结果入栈,直到获得最终结果,最终结果即为栈顶元素。使用 calculateStack 用来记录结果。

1. 初始化 calculateStack;

 2. 处理前 4 个元素,根据规则,前 4 个元素都为操作数,即全部入栈;

3. 处理第 5 个元素,即运算符 "+",根据规则,需要使用栈中前两个元素进行计算,即 34 + 46 = 60,并将结果 60 入栈;

4. 处理第 6 个元素,即操作数 "8",直接入栈即可;

5.  处理第 7 个元素,即运算符 "/",根据规则,需要使用栈中前两个元素进行计算,即 80 / 8 = 10,并将结果 10 入栈;

 6.  处理第 8 个元素,即运算符 "*",根据规则,需要使用栈中前两个元素进行计算,即 5 * 10 = 50,并将结果 50 入栈;

7.  处理第 9 个元素,即运算符 "/",根据规则,需要使用栈中前两个元素进行计算,即 800 / 50 = 16,并将结果 16 入栈;

8. 后缀表达式处理完成,直接返回栈顶元素即为最终计算结果。

完整代码如下:

package mixingOperation;

import java.util.Stack;

public class MixingOperation {

    public static int mixingOperation(String infixExpression) {
        System.out.println("原始中缀表达式为: " + infixExpression);
        // 1. 处理中括号和小括号
        String newInfixExpression = handlerParentheses(infixExpression);
        System.out.println("处理括号后的新的中缀表达式为: " + newInfixExpression);
        // 2. 中缀表达式转换为后缀表达式
        String[] postfixExpressionArray = transformExpression(newInfixExpression);
        StringBuffer postfixExpressionBuffer = new StringBuffer();
        for (int i = 0; i < postfixExpressionArray.length; i++) {
            if (null == postfixExpressionArray[i] || postfixExpressionArray[i].isEmpty()) {
                break;
            }
            postfixExpressionBuffer.append(postfixExpressionArray[i]).append(" ");
        }
        System.out.println("后缀表达式为: " + postfixExpressionBuffer.toString());
        // 后缀表达式求值
        int result = calculateResultByPostfixExpression(postfixExpressionArray);
        System.out.println("表达式: " + infixExpression + " = " + result);
        return 0;
    }

    private static int calculateResultByPostfixExpression(String[] postfixExpressionArray) {
        // 运算符和运算数栈
        Stack<String> calculateStack = new Stack<>();
        // 遍历中缀表达式数组
        for (int i = 0; i < postfixExpressionArray.length; i++) {
            if (null == postfixExpressionArray[i] || postfixExpressionArray[i].isEmpty()) {
                break;
            }
            String currentStr = postfixExpressionArray[i];
            switch (currentStr) {
                // 如果是运算符, 将栈顶元素作为第二个操作数并出栈, 然后新的栈顶元素作为第一个操作数并出栈,
                // 然后使用 操作数1、运算符、操作数2 进行运算, 并将结果入栈
                case "+" :
                case "-" :
                case "*" :
                case "/" :
                    if (!calculateStack.empty()) {
                        // 栈顶元素作为操作数2并出栈
                        int operand2 = Integer.valueOf(calculateStack.pop());
                        // 新的栈顶元素作为操作数1
                        int operand1 = Integer.valueOf(calculateStack.pop());
                        // 计算结果
                        int result = calculateResult(operand1, currentStr, operand2);
                        // 将结果入栈
                        calculateStack.push(String.valueOf(result));
                    }
                    break;
                default:
                    // 如果是操作数, 则直接入栈
                    calculateStack.push(currentStr);
            }

        }
        // 处理完成之后输出栈顶元素即为最终结果
        return Integer.valueOf(calculateStack.pop());
    }

    private static int calculateResult(int operand1, String currentStr, int operand2) {
        int result = 0;
        switch (currentStr) {
            case "+" :
                result =  operand1 + operand2;
                break;
            case "-" :
                result =  operand1 - operand2;
                break;
            case "*" :
                result =  operand1 * operand2;
                break;
            case "/" :
                if (operand2 == 0) {
                    return 0;
                }
                result =  operand1 / operand2;
                break;
            default:
        }
        return result;
    }

    private static String[] transformExpression(String newInfixExpression) {
        // 运算符栈
        Stack<String> operatorStack = new Stack<>();
        // 操作数缓冲区
        StringBuffer tempOperandBuffer = new StringBuffer();
        // 后缀表达式
        String postfixExpression = null;
        // 后缀表达式数组
        String[] postExpressionArray = new String[newInfixExpression.length()];
        // 数组下标
        int index = 0;
        // 存储数组的标识
        boolean flag = false;

        // 遍历中缀表达式
        for (int i = 0; i < newInfixExpression.length(); i++) {
            // 当前字符转换为字符串, 存储栈
            String currentStr = String.valueOf(newInfixExpression.charAt(i));
//            System.out.println("当前字符为: " + currentStr);
            switch (currentStr) {
                // 如果是加号运算符或者减号运算符
                // 根据转换规则, 需判断当前运算符与栈顶元素运算符的优先级, 并将当前运算符入栈
                case "+":
                case "-":
                    // 栈顶元素如果不是左括号 "(", 则依次出栈并输出
                    while (!operatorStack.empty()) {
                        if (!"(".equals(operatorStack.peek())) {
                            postExpressionArray[index++] = operatorStack.peek();
                            operatorStack.pop();
                        } else {
                            break;
                        }
                    }
                    // 处理完出战的逻辑或者栈为空, 则将当前运算符入栈
                    operatorStack.push(currentStr);
                    flag = true;
                    break;

                // 如果是乘号运算符或者除号运算符
                // 根据转换规则, 需判断当前运算符与栈顶元素运算符的优先级, 并将当前运算符入栈
                case "*":
                case "/":
                    while (!operatorStack.empty()) {
                        // 如果栈顶元素的优先级与当前运算符的优先级相同, 即栈顶元素为 "*" 或 "/", 则栈顶元素出栈并输出
                        if ("*".equals(operatorStack.peek()) || "/".equals(operatorStack.peek())) {
                            postExpressionArray[index++] = operatorStack.peek();
                            operatorStack.pop();
                        } else {
                            break;
                        }
                    }
                    operatorStack.push(currentStr);
                    flag = true;
                    break;

                // 如果是左括号, 直接入栈
                case "(":
                    operatorStack.push(currentStr);
                    flag = true;
                    break;

                // 如果是右括号, 则匹配左括号
                case ")":
                    while (!operatorStack.empty()) {
                        if (!tempOperandBuffer.toString().isEmpty()) {
                            postExpressionArray[index++] = tempOperandBuffer.toString();
                            tempOperandBuffer = new StringBuffer();
                            flag = false;
                        }
                        // 将距离栈顶元素最近的左括号之前的运算符全部出栈并输出, 并将左括号也输出
                        if (!"(".equals(operatorStack.peek())) {
                            postExpressionArray[index++] = operatorStack.peek();
                            operatorStack.pop();
                        } else {
                            operatorStack.pop();
                            break;
                        }
                    }
                    break;

                // 如果是数字, 则直接记录进后缀表达式缓冲区中
                default:
                    if (flag) {
                        if (!tempOperandBuffer.toString().isEmpty()) {
                            postExpressionArray[index++] = tempOperandBuffer.toString();
                        }
                        tempOperandBuffer = new StringBuffer(currentStr);
                        flag = false;
                    } else {
                        tempOperandBuffer.append(currentStr);
                    }
            }
        }
        // 处理操作数缓冲区的内容
        if (!tempOperandBuffer.toString().isEmpty()) {
            postExpressionArray[index++] = tempOperandBuffer.toString();
        }
        // 遍历完中缀表达式之后, 将栈中元素依次输出
        while (!operatorStack.empty()) {
            postExpressionArray[index++] = operatorStack.peek();
            operatorStack.pop();
        }
        return postExpressionArray;
    }

    private static String handlerParentheses(String infixExpression) {
        return infixExpression.replace("[", "(")
                .replace("]", ")").replace("{", "(")
                .replace("}", ")");
    }

    public static void main(String[] args) {
//        String infixExpression = "6*8";
        String infixExpression = "800/{5*[(34+46)/8]}";
        mixingOperation(infixExpression);
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/17080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32微控制 -STM32命名规则-STM32寄存器缩写列表-STM32存储器和总线架构

STM32使用说明第一篇【1】STM32微控制器【2】STM32命名规则【3】STM32寄存器缩写列表【4】STM32存储器和总线架构【1】STM32微控制器 STM32是一个微控制器产品系列的总称&#xff0c;目前这个系列中已经包含了多个子系列&#xff0c;分别是&#xff1a; 【1】STM32小容量产品 【…

操作系统基础教程——第五章课后作业答案

1.思考题 &#xff08;3&#xff09;什么是文件的逻辑结构&#xff1f;它有哪几种组织方式? 文件的逻辑结构&#xff08;逻辑文件&#xff09;&#xff1a;独立于物理环境的&#xff0c;用户概念中的抽象信息组织方式&#xff0c;用户能观察到的&#xff0c;并加以处理的数据…

[datawhale202211]跨模态神经搜索实践:跨模态模型

结论速递 本次任务首先了解了CLIP模型及其原理&#xff0c;CLIP模型将图像标签替换为图像的文本描述信息&#xff0c;来监督视觉任务的训练&#xff0c;引入了语义匹配实现下游任务的zero-shot。 多模态和跨模态可能是未来模型的发展方向&#xff0c;多模态尝试结合不同信息表…

数字集成电路设计(四、Verilog HDL数字逻辑设计方法)(三)

文章目录4. 有限同步状态机4.1 编码4.2 有限状态机的写法4.3 举例4.3.1 用Verilog HDL 设计顺序脉冲发生器4.3.2 设计-个自动售报机报纸价钱为八角&#xff0c;纸币有 1角、2 角5 角、一元。该自动售报机不考虑投币为大额面值等特殊情况4. 有限同步状态机 &#xff01;&#xf…

『Java』类和对象

文章目录一、面向对象的初步认识&#x1f333;1、什么是面向对象&#x1f333;2、面向对象与面向过程&#x1f351;&#xff08;1&#xff09;洗衣服&#x1f351;&#xff08;2&#xff09;大象装冰箱汽车拼装二、类定义和使用&#x1f333;1、简单认识类&#x1f333;2、类的…

网络安全重点知识

单选&#xff08;抽20个&#xff09;、判断&#xff08;抽5个&#xff09; 第二章&#xff1a; 第三章&#xff1a; 第四章&#xff1a; 第五章&#xff1a; 第六章&#xff1a; 第八章&#xff1a; 填空&#xff08;抽3个&#xff09; 1、网络安全&#xff1a; 是在网络各个…

【附源码】Python计算机毕业设计税务综合信息平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

jquery基础--学习笔记

jQuery选择器 元素、Class、id选择器 属性选择器 注意&#xff1a;&#xff01;如果不指定标签&#xff0c;会输出所有的html标签不满足的元素 如何指定&#xff1f;可以用之前的元素、标签或者id选择器 层级选择器 上面图片写错了&#xff0c;box应该是div&#xff0c;看例子…

【微信小程序】数据绑定

&#x1f3c6;今日学习目标&#xff1a;第十一期——数据绑定 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;25分钟 &#x1f389;专栏系列&#xff1a;我的第一个微信小程序 文章目录前言实现数据绑定初始化数据…

[附源码]java毕业设计农村留守儿童帮扶系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Flink cdc 2.3.0 日前发布,支持众多新特性

新连接器 2.3.0 新增了 DB2 CDC 连接器 增量快照迎来新的连接器成员 2.3.0 版本MongoDB CDC&#xff0c;Oracle CDC 两大连接器均支持了增量快照&#xff0c;实现无锁读取并发读取断点续传 优化 2.3.0 版本 MySQL CDC 连接器性能和稳定性大幅提升 Flink 支持 2.3.0 版本…

11月19日绿健简报,星期六,农历十月廿六

11月19日绿健简报&#xff0c;星期六&#xff0c;农历十月廿六1. 文旅部&#xff1a;不随意关停娱乐场所&#xff0c;无疫情发生地原则上不限制大型经营性演出观众人数。2. 人社部等五部门&#xff1a;企业不得以年龄为由“一刀切”清退大龄农民工。3. 英国政府以国家安全为由&…

Java 枚举(Enum)使用

文章目录枚举引入enum关键字实现枚举enum关键字实现枚举注意事项enum常用方法一览表enum课堂练习enum实现接口枚举引入 创建Season类, 实例化春夏秋冬四个实例 Season对象有如下特点 1.季节的值是有限的几个值(spring, summer, autumn, winter) 2.只读&#xff0c;不需要修改…

开始数据治理时三个常见的陷阱和解决方法

当我们与客户合作帮助他们提高数据管理能力时,大多数部门都同意更好的数据治理将有助于解决他们的数据问题。然而,我们发现数据治理很少是优先事项,而且往往被搁置一旁,去支持更紧迫的业务工作。这有点像使用牙线——当你在牙医诊所时很容易获得动力,但当你回到家时很难保…

GC垃圾回收相关算法(宋红康JVM学习笔记)

什么是垃圾&#xff1f; 垃圾收集机制是Java的招牌能力&#xff0c;极大地提高了开发效率。如今&#xff0c;垃圾收集几乎成为现代语言的标配&#xff0c;即使经过如此长时间的发展&#xff0c;Java的垃圾收集机制仍然在不断的演进中&#xff0c;不同大小的设备、不同特征的应用…

[附源码]java毕业设计辽宁科技大学疫苗接种管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

果蔬同城配送小程序有什么作用_分享果蔬同城配送小程序的作用

1、蔬菜生鲜产品展示&#xff1a;用户打开买菜必备软件&#xff0c;就能查看琳琅满目的新鲜水果、蔬菜、肉类、零食等产品&#xff0c;为用户展示更多信息&#xff0c;提升用户下单率。经常更新商品的照片、视频&#xff0c;让客户可以在线浏览和挑选&#xff0c;足不出户就能买…

二叉树的最大深度(C++两种思路递归和层序)超详解小白入

原题链接–>戳这里直达 二叉树的最大深度深度搜索&#xff08;递归&#xff09;递归思想和详解C代码代码效率广度搜索&#xff08;层序查找&#xff09;层序查找的思路C代码代码效率总结深度搜索&#xff08;递归&#xff09; 最近新学习了树形结构&#xff0c;上课的时候听…

MATLAB算法实战应用案例精讲-【数模应用】随机梯度下降法(SGD)

前言 随机梯度下降算法(Stochastic gradient descent, SGD)源于1951年Robbins和Monro[6]提出的随机逼近, 最初应用于模式识别和神经网络. 这种方法在迭代过程中随机选择一个或几个样本的梯度来替代总体梯度, 从而大大降低了计算复杂度. 1958年Rosenblatt等研制出的感知机采用了…

JAVA开发(Redis使用缺陷场景)

常见的redis使用缺陷场景主要有3个&#xff0c;分别是缓存穿透&#xff0c;缓存击穿&#xff0c;缓存雪崩。 穿透&#xff0c;&#xff08;关键词&#xff0c;缓存中没有的&#xff0c;数据也没有&#xff09; 击穿&#xff08;大量同时请求过期的key&#xff09; 雪崩&…