【Java可执行命令】(六)调试工具 jdb:深入解析应用程序调试工具jdb ~

news2024/9/20 14:48:06

Java可执行命令详解之jdb

  • 1️⃣ 概念
  • 2️⃣ 优势和缺点
  • 3️⃣ 使用
    • 3.1 语法格式
      • 3.1.1 参数:-sourcepath < path>
      • 3.1.2 指令:run [class [args]]
      • 3.1.3 指令:print < expr>
      • 3.1.4 指令:stop at< class>:< line>
      • 3.1.5 指令:watch [access|all] < class id>.< field name>
      • 3.1.6 指令:step
  • 4️⃣ 应用场景
  • 5️⃣ 注意事项
  • 6️⃣ 扩展:现在流行的调试工具
  • 🌾 总结

在这里插入图片描述

1️⃣ 概念

jdb 是Java开发工具中的一部分,是一个用于调试Java程序的命令行工具。它由Sun Microsystems (现在是Oracle Corporation) 开发,并随JDK(Java Development Kit)一同提供。jdb 旨在提供一个交互式的调试环境,以帮助开发者诊断和修复Java应用程序中的错误。

jdb 提供了一个类似于传统命令行调试器的界面,可以在运行 Java 程序时暂停程序的执行,并允许开发者使用各种调试功能,例如设置断点、查看变量和堆栈信息、执行单步调试、监视变量值等。

其主要作用是帮助开发者定位和解决Java程序中的错误和异常。通过使用 jdb 调试器,开发者可以逐行检查代码、观察变量的值和状态,并在运行时动态地修改和测试代码逻辑。

jdb 是通过使用 Java 虚拟机自身提供的调试接口(JVMTI)来实现的。JVMTI 允许外部工具与正在运行的 Java 程序进行交互,并提供了访问程序状态和控制程序执行的能力,这使得 jdb 可以在程序运行时获取和修改程序的状态。

2️⃣ 优势和缺点

优点:

  • 强大的调试功能jdb 提供了丰富的调试功能,使得开发者能够更深入地理解和追踪程序的执行过程;
  • 命令行界面:命令行界面提供了更快速和高效的调试体验,并且可以在不同的操作系统上使用。

缺点:

  • 学习曲线较陡峭:与一些集成开发环境(IDE)相比,jdb 的命令行界面可能对于初学者来说有一定的学习曲线;
  • 依赖控制台:由于 jdb 是一个命令行工具,它需要运行在支持终端或命令行界面的操作系统上。它可能不适用于所有的场景,特别是在没有命令行界面访问权限的远程服务器上。

3️⃣ 使用

3.1 语法格式

jdb 的使用语法如下所示:

jdb [options] <class> [arguments]

其中:

  • options:可选的调试器参数;
  • <class>:要调试的Java类的名称;
  • arguments:传递给主程序的参数。

jdb 命令支持一些可选参数来控制调试过程。汇总全部的可选参数如下表:

参数作用
-sourcepath <由 ";"分隔的目录>要在其中查找源文件的目录
-attach <address>使用标准连接器附加到指定地址处正在运行的 VM
-listen <address>等待正在运行的 VM 使用标准连接器在指定地址处连接
-listenany等待正在运行的 VM 使用标准连接器在任何可用地址处连接
-launch立即启动 VM 而不是等待 ‘run’ 命令
-listconnectors列出此 VM 中的可用连接器
-connect <connector-name>:<name1>=<value1>,...使用所列参数值通过指定的连接器连接到目标 VM
-dbgtrace [flags]输出信息供调试jdb
-tclient在 HotSpot™ 客户机编译器中运行应用程序
-tserver在 HotSpot™ 服务器编译器中运行应用程序

使用jdb命令和参数之后,就会进入调试行,此时就可以开始调试过程。而在调试行可操作的命令汇总如下表:

指令作用
connectors 列出此 VM 中可用的连接器和传输
run [class [args]]开始执行应用程序的主类
--
threads [threadgroup] 列出线程
thread <thread id> 设置默认线程
suspend [thread id(s)] 挂起线程 (默认值: all)
resume [thread id(s)] 恢复线程 (默认值: all)
where [<thread id> | all] 转储线程的堆栈
wherei [<thread id> | all]转储线程的堆栈, 以及 pc 信息
up [n frames] 上移线程的堆栈
down [n frames] 下移线程的堆栈
kill <thread id> <expr> 终止具有给定的异常错误对象的线程
interrupt <thread id> 中断线程
--
print <expr> 输出表达式的值
dump <expr> 输出所有对象信息
eval <expr> 对表达式求值 (与 print 相同)
set <lvalue> = <expr> 向字段/变量/数组元素分配新值
locals 输出当前堆栈帧中的所有本地变量
--
classes 列出当前已知的类
class <class id> 显示已命名类的详细资料
methods <class id> 列出类的方法
fields <class id> 列出类的字段
--
threadgroups 列出线程组
threadgroup <name> 设置当前线程组
--
stop in <class id>.<method>[(argument_type,...)] 在方法中设置断点
stop at <class id>:<line> 在行中设置断点
clear <class id>.<method>[(argument_type,...)] 清除方法中的断点
clear <class id>:<line> 清除行中的断点
clear 列出断点
catch [uncaught|caught|all] <class id>|<class pattern> 出现指定的异常错误时中断
ignore [uncaught|caught|all] <class id>|<class pattern> 对于指定的异常错误, 取消 ‘catch’
watch [access|all] <class id>.<field name> 监视对字段的访问/修改
unwatch [access|all] <class id>.<field name> 停止监视对字段的访问/修改
trace [go] methods [thread] 跟踪方法进入和退出。除非指定 ‘go’, 否则挂起所有线程
trace [go] method exit | exits [thread] 跟踪当前方法的退出, 或者所有方法的退出。除非指定 ‘go’, 否则挂起所有线程
untrace [methods] 停止跟踪方法进入和/或退出
step 执行当前行
step up 一直执行, 直到当前方法返回到其调用方
stepi 执行当前指令下一步。步进一行 (步过调用)
cont 从断点处继续执行
--
list [line number|method] 输出源代码
use (或 sourcepath) [source file path] 显示或更改源路径
exclude [<class pattern>, ... | "none"] 对于指定的类, 不报告步骤或方法事件
classpath 从目标 VM 输出类路径信息
--
monitor <command> 每次程序停止时执行命令
monitor 列出监视器
unmonitor <monitor#> 删除监视器
read <filename> 读取并执行命令文件
--
lock <expr> 输出对象的锁信息
threadlocks [thread id] 输出线程的锁信息
--
pop 通过当前帧出栈, 且包含当前帧
reenter 与 pop 相同, 但重新进入当前帧
redefine <class id> <class file name> 重新定义类的代码
--
disablegc <expr> 禁止对象的垃圾收集
enablegc <expr> 允许对象的垃圾收集
--
!! 重复执行最后一个命令
<n> <command> 将命令重复执行 n 次
# <command> 放弃 (无操作)
help (或 ?) 列出命令
version 输出版本信息
exit (或 quit) 退出调试器

上面表格汇总了jdb的调试模式中,所有一共近60个调试命令,读者可以根据自己的需求参照表格选择所需指令。下是主要介绍一些常用的 jdb 可选参数或指令:

  • -sourcepath <path>:指定源代码路径;
  • run [class [args]]:开始执行应用程序的主类;
  • print <expr>:打印表达式的值;
  • stop at<class>:<line>:在指定的源代码位置设置断点;
  • watch [access|all] <class id>.<field name>:监视指定变量的值;
  • step:执行单步调试。

3.1.1 参数:-sourcepath < path>

jdb -sourcepath <path> 命令用于指定源代码的路径,这对于在调试过程中查看源代码非常有用。以下是一个演示:

假设有一个名为 MyApp 的Java应用程序,并且源代码位于 /path/to/source 目录下。我们希望在调试期间能够访问到正确的源代码。在这种情况下,我们可以使用以下命令来启动 jdb 并设置源代码路径:

jdb -sourcepath /path/to/source MyApp

运行该命令后,jdb 将启动并等待连接到 MyApp 应用程序进程。此时,如果在应用程序中设置了断点,jdb 将暂停应用程序的执行,并允许您逐步调试代码。使用 -sourcepath 选项后,可以使用 list 命令查看当前断点所在位置的源代码,从而更容易地理解正在调试的代码。

3.1.2 指令:run [class [args]]

当使用 jdbrun [class [args]] 指令时,可以运行 Java 应用程序进行调试。以下是一个案例演示:

使用 jdb 运行应用程序,可按照以下步骤操作:

  1. 在命令行中输入命令来启动 jdb 调试器:

    jdb
    
  2. jdb 命令提示符下,使用 run 命令并指定要运行的类名和参数(可选):

    run MyApp
    

    如果应用程序需要命令行参数,可以在 run 命令后添加它们:

    run MyApp arg1 arg2
    
  3. jdb 将尝试加载并运行指定的类。如果成功,它将开始执行应用程序。

  4. 应用程序开始执行时,jdb 将以调试模式暂停应用程序的执行,并返回到 jdb 的命令提示符。这意味着我们可以开始在代码中设置断点、查看变量值,并进行其他调试操作。

需要注意确保已经编译并生成了可调试的 Java 类文件,才能进行运行和调试。

3.1.3 指令:print < expr>

当使用 jdbprint <expr> 指令时,可以在调试过程中打印表达式的值。以下是一个案例演示:

假设正在调试一个 Java 应用程序,下面是一个简单的 Java 类:

public class MyApp {
    public static void main(String[] args) {
        int x = 10;
        int y = 5;
        int sum = x + y;

        System.out.println("Sum: " + sum);
        System.out.println("Product: " + multiply(x, y));
    }

    public static int multiply(int a, int b) {
        return a * b;
    }
}

现在,我们在 jdb 中设置断点,并使用 print 命令来查看变量的值:

  1. 在命令行中输入以下命令来启动 jdb 调试器并指定要调试的类:

    jdb MyApp
    
  2. 使用 stop at 命令设置断点,例如在 multiply 方法内的第一行:

    stop at MyApp.multiply:3
    
  3. 使用 run 命令启动应用程序:

    run
    
  4. 当应用程序执行到断点处时,jdb 将暂停应用程序的执行。

  5. 现在,可以使用 print 命令来打印表达式的值。例如,要打印变量 xy 的值:

    print x
    print y
    

    jdb 将在命令行中显示变量的值。

  6. 还可以在 print 命令中使用表达式。例如,要打印 xy 的和以及调用 multiply 方法的返回值:

    print x + y
    print multiply(x, y)
    

    jdb 将计算并打印表达式的结果。

  7. 在完成调试操作后,可以使用 cont 命令继续执行应用程序。

这个案例演示了如何在 jdb 中使用 print 命令来查看变量和表达式的值。注意,在每个断点处,变量的作用域是可见的。

3.1.4 指令:stop at< class>:< line>

当使用 jdbstop at <class>:<line> 指令时,可以在指定的类和行号上设置断点。以下是一个案例演示:

同样使用上边案例的 MyApp Java类来演示在特定类和行号上设置断点以进行调试。

  1. 在命令行中输入以下命令来启动 jdb 调试器并指定要调试的类:

    jdb MyApp
    
  2. jdb 提示符下,使用 stop at 命令来设置断点。例如,设置在 main 方法内的第 6 行处:

    stop at MyApp:6
    

    这将在指定的类和行号上设置一个断点。

  3. 使用 run 命令启动应用程序:

    run
    

    应用程序将开始执行,并在达到断点位置时暂停。

  4. 当应用程序暂停时,可以查看变量的值、执行其他调试操作以及逐行调试。

注意,jdb 默认情况下会在应用程序开始执行后立即停止。因此需要在应用程序达到断点之前设置断点。

3.1.5 指令:watch [access|all] < class id>.< field name>

当使用 jdbwatch [access|all] <class id>.<field name> 指令时,可以设置监视点以在字段访问时触发暂停。以下是一个案例演示:

假设有一个 MyClass 的Java类,其中包含一个实例字段 count,现在希望在每次修改 count 字段值时暂停并进行调试。下面是一个简单的示例:

public class MyClass {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.increment();
        System.out.println("Count: " + obj.getCount());
    }
}

现在,让我们在 jdb 中设置监视点并进行调试:

  1. 在命令行中输入以下命令来启动 jdb 调试器并指定要调试的类:

    jdb MyClass
    
  2. jdb 提示符下,使用 stop in 命令设置断点,例如在 increment 方法内的第一行:

    stop in MyClass.increment:1
    
  3. 使用 run 命令启动应用程序:

    run
    

    应用程序将开始执行,并在达到断点位置时暂停。

  4. 当应用程序暂停时,可以使用 watch 命令设置监视点。例如,设置对 count 字段的访问进行监视:

    watch access MyClass.count
    

    此命令将在字段访问时触发暂停。

    如果希望在字段读取或写入时都触发暂停,可以使用 watch all 命令:

    watch all MyClass.count
    
  5. 在监视点设置后,当应用程序访问或修改 count 字段时,jdb 将暂停,并返回到 jdb 的命令提示符以供进一步调试。

需要注意监视点只能设置在可调试的字段上,并且只在第一个访问或修改操作发生时触发暂停。

3.1.6 指令:step

当使用 jdbstep 指令时,可以逐语句执行代码并进入方法调用。以下是一个案例演示:

现在有一个 MyClass 的Java类,其中包含一个方法调用链,我们希望在每个方法调用和逐语句执行代码时进行调试。下面是一个简单的示例:

public class MyClass {
    public void methodA() {
        System.out.println("Inside methodA");
        methodB();
    }

    public void methodB() {
        System.out.println("Inside methodB");
        methodC();
    }

    public void methodC() {
        System.out.println("Inside methodC");
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.methodA();
    }
}
  1. 在命令行中输入以下命令来启动 jdb 调试器并指定要调试的类:

    jdb MyClass
    
  2. jdb 提示符下,使用 stop in 命令设置断点,例如,在 methodA 方法内的第一行:

    stop in MyClass.methodA:1
    
  3. 使用 run 命令启动应用程序:

    run
    

    应用程序将开始执行,并在达到断点位置时暂停。

  4. 当应用程序暂停时,您可以使用 step 命令逐语句执行代码。键入以下命令并按回车键:

    step
    

    这将执行当前行,并逐语句地执行代码。

  5. 可以重复使用 step 命令,每次执行一行代码并进入方法调用,直到代码执行完成或到达断点。

step 命令会进入所有方法调用,因此可能需要多次执行该命令来查看每个方法的执行。

4️⃣ 应用场景

jdb 在以下情况下特别有用:

  • 错误排查:当程序出现错误、崩溃或异常时,可以使用 jdb 来分析问题并找到修复代码的位置;
  • 代码调试:可以在程序执行的不同位置设置断点,逐行调试以便观察代码行为和验证逻辑;
  • 性能调优:可以使用 jdb 来检测程序中的性能瓶颈,分析 CPU 和内存使用情况,并找到优化的机会。

5️⃣ 注意事项

在使用 jdb 进行调试时,请注意以下几点:

  • 确保使用 -g 选项对源代码进行编译,以便在调试器中能够正确地查看变量和源代码;
  • 仔细阅读 jdb 的文档,并了解每个命令和选项的作用;
  • 使用合适的断点,以避免存在太多或不必要的断点;
  • 在调试期间,观察变量的值及其对程序行为的影响;
  • 避免在生产环境中使用 jdb 调试,应将其限制在开发和测试环境中使用。

6️⃣ 扩展:现在流行的调试工具

在Java开发领域,有许多流行的代码调试工具可供选择,尤其是现在的IDE软件基本都集成了更加直观的图形化调试工具,这也使得本文介绍的传统的命令行调试方式,因其比较繁多及复杂的指令格式逐渐被大众开发者所遗忘或摒弃。以下是一些目前广泛使用的Java代码调试工具:

  1. IntelliJ IDEA:IntelliJ IDEA是一个很流行的Java IDE,也提供了高级的调试功能。它支持断点调试、条件断点、表达式求值和远程调试等功能,并具有用户友好的界面;
  2. Eclipse:Eclipse是一个强大的Java集成开发环境(IDE),具有内置的调试功能。它提供了逐行调试、断点设置、变量监视和堆栈跟踪等实用特性;
  3. NetBeans:NetBeans是一款免费的开源Java IDE,内置了强大的调试功能。它支持基本调试操作,如断点设置、单步执行和变量监视,同时还提供了高级功能,如异常捕获和线程调试;
  4. jdb:在本文我们已经详细介绍了jdb,这是Java自带的命令行调试器,适用于那些更喜欢命令行界面和脚本化操作的开发者。

这些是当前流行的Java代码调试工具,每个工具都有其特定的优点,并适用于不同类型的项目和开发需求。选择适合自己的工具取决于个人偏好、项目要求和团队协作等因素。建议根据自身的需求,尝试并选择最适合您的Java代码调试工具。

🌾 总结

jdb 是一个功能强大且灵活的 Java 调试器,提供了一系列功能和选项来帮助开发者定位、调试和修复 Java 应用程序中的问题。通过设置断点、单步调试、监视变量等操作,开发者可以深入了解程序的执行状态,并进行错误排查和性能调优。

尽管 jdb 的命令行界面对于新手而言可能有一定的学习曲线,但通过熟悉其语法和常用选项,开发者可以更加高效地利用 jdb 进行调试工作。同时,需要注意在合适的情况下使用 jdb,并遵循最佳实践以确保有效地使用调试器工具。

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

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

相关文章

如何利用Idea回滚代码以及Cherry-Pick部分代码

引言 大家在版本迭代过程中&#xff0c;是否遇到过开发好的需求&#xff0c;都已经合并到Master分支等待发布后&#xff0c;临时通知不需要上线了的情况。这个时候一般会要求只上一部分紧急功能或者别的新功能&#xff0c;那么这个时候就需要用到Git的Reset以及Cherry-Pick功能…

钉钉机器人用bitmap实现签到记录

现在是周五晚上&#xff0c;下面是一个二进制数字&#xff0c;其中&#xff0c;有16位&#xff0c;最后一位下标是15&#xff0c;今天晚上是14&#xff0c;我签到成功了

一定要收藏的30套可视化大屏制作模板!升职加薪不再是梦想!

前几天和朋友吃饭聊天&#xff0c;他吐槽说老板让他做可视化大屏&#xff0c;但他不会敲代码根本做不出来&#xff0c;老板动动嘴巴子根本不考虑技术难度只想看到成果&#xff0c;他焦虑得都睡不着觉。我给他分享了一套可视化大屏模板&#xff0c;10分钟就制作完成了老板要求的…

了解浏览器缓存

什么是HTTP缓存&#xff0c;如何工作的&#xff1f; 当我们打开一个页面时&#xff0c;会向服务端发起很多次请求&#xff0c;如下图打开百毒首页&#xff0c;发起了HTML、各种图片、JS、CSS等资源共101次请求。这里面很多资源并不会频繁变化&#xff0c;每次打开页面都重新请…

利用Python构建宁德时代、比亚迪、隆基绿能股票时间序列预测模型

存货 import tushare as ts # 导包 import numpy as np import matplotlib.pyplot as plt from scipy.signal import find_peaks from scipy.stats import norm import datetime import pandas as pd import seaborn as sns # pip install seaborn import matplotlib.patches …

Oracle报错:“Error in invoking target ‘agent nmhs’ of makefile...”

前言&#xff1a;Oracle在安装过程中的报错一定要重视&#xff0c;这决定你后续是否能完成安装以及是否能使用。我这边会陆续汇总一些报错现象以及解决方案共享。 ##Install Product 86%报错信息 &#xff1a;“Error in invoking target agent nmhs of makefile...”解决方案…

意大利语翻译哪个公司比较专业?

据了解&#xff0c;意大利语除了通行于意大利之外&#xff0c;还通行于美国、加拿大、阿根廷和巴西等29个国家的&#xff0c;其应用极为广泛。随着意大利语翻译需求量的日益增加&#xff0c;也促进了意大利语与其它语言间的交流、转化和发展。那么&#xff0c;意大利语翻译难吗…

抖音短视频矩阵号系统开源部署搭建分享(二)

开发背景&#xff1a;抖音seo源码&#xff0c;抖音矩阵系统源码。抖音获客系统源码&#xff0c;短视频矩阵系统源码开源搭建&#xff08;MySQL数据库&#xff0c;NGINX&#xff0c;PHP7.4&#xff0c;MySQL5.7&#xff0c;redis&#xff09; 技术要点&#xff1a; 服务器配置服…

vue-router.esm.js:2248 Error: Cannot find module ‘@/views/dylife/ 报错解决

具体是展示 一直加载 控制台报找不到模块 webpack版本问题&#xff0c;webpack4 不支持变量方式的动态 import &#xff0c;新版本需要使用 require() 来解决此问题。 return () > import(/views/${view}) 改写成 return (resolve) > require([/views/${view}], reso…

python接口自动化(四)--接口测试工具介绍(详解)

简介 “工欲善其事必先利其器”&#xff0c;通过前边几篇文章的介绍&#xff0c;大家大致对接口有了进一步的认识。那么接下来让我们看看接口测试的工具有哪些。 目前&#xff0c;市场上有很多支持接口测试的工具。利用工具进行接口测试&#xff0c;能够提供测试效率。例如&…

【C++】浅析C++中的虚函数

关于虚函数 Q1&#xff1a;观察一个类引入虚函数后&#xff0c;类会发生什么变化&#xff1f; 首先&#xff0c;创建一个空类A&#xff0c;并实例化出A的一个对象a&#xff0c;计算一下这个对象占用多少字节&#xff1a; #include<iostream> using namespace std;clas…

02-阴影

使用阴影 1.给立方体添加castShadow&#xff0c;让立方体产生阴影 cube.castShadow true;2.创建一个地面用于接收阴影 const planeGemetry new THREE.PlaneGeometry(20, 30)const planeMaterial new THREE.MeshPhongMaterial({ color: 0xffffff })const plane new THREE.…

LeetCode 2. 两数相加

文章目录 1. 题目描述2. 解题代码 1. 题目描述 链接&#xff1a;https://leetcode.cn/problems/add-two-numbers/ 2. 解题代码 public ListNode AddTwoNumber(ListNode l1, ListNode l2) {ListNode head new ListNode();ListNode cur head;int carry 0;while (l1 ! null…

CIO 访谈|财达证券 IT 基础架构云化转型思考与实践

作为河北省证券行业的主力军&#xff0c;财达证券始终坚持用科技赋能业务&#xff0c;全方位推动信息化和数字化建设。在本期视频中&#xff0c;我们请到了财达证券首席信息官谢井民&#xff0c;分享如何基于 SmartX 超融合逐步实现 IT 基础架构云化转型&#xff0c;满足公司“…

RPC框架(一):扫盲

文章目录 一、概要二、RPC组成部分三、影响RPC框架性能的因素 一、概要 RPC作用&#xff1f; 让不同服务间调用方法像同一服务间调用本地方法一样 二、RPC组成部分 Client&#xff1a;RPC协议调用方 Server&#xff1a;远程服务方法的具体实现 Stub/Proxy&#xff1a;RPC代…

基于SpringBoot的家庭理财记账系统的设计与开发

1.引言 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…

Apache组件POI,将图片下载到Excel文件中并导出。

在日常的工作中&#xff0c;有时我们会遇到需要将数据库表中图片字段下载到Excel中的需求&#xff0c;为方便各位小伙伴今后的开发工作&#xff0c;我将分享出自己写的代码&#xff0c;在文章末尾有我上传到 Gitee 上的 Demo案例&#xff0c;希望大家喜欢。 将图片下载到Excel文…

[CKA]考试之Deployment管理pod扩缩容

由于最新的CKA考试改版&#xff0c;不允许存储书签&#xff0c;本博客致力怎么一步步从官网把答案找到&#xff0c;如何修改把题做对&#xff0c;下面开始我们的 CKA之旅 题目为&#xff1a; Task 扩容 deployment guestbook 为 6个pod 注意&#xff0c;如果题目要求先切换K8…

低价高品质的头戴式降噪耳机,还支持主动降噪,QCY H4体验

每天办公的时候&#xff0c;我都喜欢戴上耳机听音乐&#xff0c;开会的时候也会方便一些。以前我用过无线入耳式耳机&#xff0c;但是戴时间长了会让耳朵很痛苦&#xff0c;因为室内也不算热&#xff0c;所以我觉得头戴式蓝牙耳机很合适&#xff0c;目前我用的是这款QCY H4头戴…

Laravel 多字段去重count计数

Laravel 多字段去重count计数 背景&#xff1a;需要统计数据列表总条数&#xff08;字段1、字段2去重统计&#xff09; table&#xff1a;policy_view,去重字段admin_id和permission 期望结果&#xff1a;count不含重复统计数据 解决思路&#xff1a; 语法&#xff1a;DISTI…