Windows下Release版本Qt程序生成日志和dump文件(用于程序异常崩溃检测)

news2024/11/14 11:07:42

文章目录

  • 前言
  • 一、基于qInstallMessageHandler生成输出日志
  • 二、基于qBreakpad生成dump文件
  • 三、基于DbgHelp和SetUnhandledExceptionFilter生成dump文件
  • 四、示例完整代码
  • 五、下载链接
  • 总结


前言

在实际项目开发时,一般打包发布给客户的程序是release版本Qt程序,然而在客户环境下可能会出现程序异常崩溃的问题,为了解决这个问题,一般会在程序中添加运行日志,或者生成dump文件,用来检测并定位异常。这里总结以下几种方式,用于程序异常崩溃检测定位:
1.基于qInstallMessageHandler生成输出日志
2.基于qBreakpad生成dump文件
3.基于DbgHelp和SetUnhandledExceptionFilter生成dump文件

编译环境:QT:5.14.1
编译器:MSVC_64_bit_Release

项目效果


提示:以下是本篇文章正文内容,下面案例可供参考

一、基于qInstallMessageHandler生成输出日志

在QT中我们可以调用qInstallMessageHandler这个函数来进行消息处理,这里自定义消息处理程序,可以将你调试代码时添加的打印输出保存为文件到指定路径。
1.main.cpp中添加

//自定义消息处理
void outputMessage(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{
    static QMutex mutex;
    mutex.lock();

    //初始化log文件夹
    QString logFilePath = QCoreApplication::applicationDirPath() + "/DebugLog/";
    QDir dstDir(logFilePath);
    if(!dstDir.exists())
    {
        if(!dstDir.mkpath(logFilePath))
        {
            qDebug()<<__FILE__<<__LINE__<<"创建DebugLog文件夹失败!";
        }
    }

    //获取输出内容
    QString debugDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
    QString debugMsg = QString("%1 \r\n%2").arg(debugDateTime).arg(msg);

    //保存文件
    QString logFileName = logFilePath + "log_" + QDate::currentDate().toString("yyyyMMdd") + ".txt";
    QFile file(logFileName);
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream textStream(&file);
    textStream << debugMsg << "\r\n \r\n";
    file.flush();
    file.close();

    mutex.unlock();
}

//使用
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //生成输出日志
    qInstallMessageHandler(outputMessage);
    Widget w;
    w.show();
    return a.exec();
}

二、基于qBreakpad生成dump文件

qBreakpad是对Breakpad的封装,其详细介绍可以参考这篇文章Windows下Qt生成dump文件并定位bug(基于qBreakpad),这里引用一下参考文章中的内容,来了解一下Breakpad的一些常识。

Breakpad介绍
Breakpad是Google公司开发的开源多平台C++崩溃检测库。Breakpad可以捕获发布给用户的应用程序的崩溃,并记录软件崩溃的调试信息到“minidump”文件中,即*.dmp。除此之外,Breakpad还可以调试信息包括错误行号,报错详情,堆栈错误(stack traces)。支持软件崩溃时候把生成的dump文件上传到自己的服务器上就可以方便的获取崩溃详情。

BreakPad工作原理
1.我们在编译的时候,需要在Release版程序中生成调试信息。
2.使用Breakpad提供的dump_syms工具,从release版本程序导出符号文件。
3.当程序崩溃时,breakpad会捕捉崩溃,并生成dump文件。
4.dump文件可以直接发送到指定服务器,或者由用户手动发给开发者。
5.收到dump文件后,结合符号文件,可通过minidump_stackwalk工具生成堆栈调用信息文件,这个文件可以直接阅读,定位bug。

我的示例中已经编译好了相关的库,并将库和头文件集成到项目同级文件夹下方便使用:
请添加图片描述
请添加图片描述
请添加图片描述

接下来讲述一下qBreakpad的使用:
1.pro中添加

#for qBreakpad
#qBreakpad中需要使用到network模块
QT += network

#启用多线程、异常、RTTI、STL支持
CONFIG += thread exceptions rtti stl

#没有c++11和AppKit库编译器不能解决符号的地址
CONFIG += c++11
macx: LIBS += -framework AppKit

#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

#配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/include

CONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}

2.main.cpp中添加

//包含头文件
//qBreakpad
#include "qBreakpad/include/QBreakpadHandler.h"

//使用
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //设置生成dump文件路径
    QBreakpadInstance.setDumpPath("BreakCrashes");   //捕获构造时的异常
    Widget w;
    w.show();
    QBreakpadInstance.setDumpPath("BreakCrashes");   //捕获构造完成之后的异常
    return a.exec();
}

三、基于DbgHelp和SetUnhandledExceptionFilter生成dump文件

SetUnhandledExceptionFilter设置未处理的异常筛选器函数,对于该函数的详细介绍可以直接查看windows官方文档,这里也是直接讲述其在Qt下的使用:
1.pro中添加

#for DbgHelp
#方便生成DUMP调试
LIBS += -lDbgHelp
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG

#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

2.main.cpp中添加

//包含头文件
//DbgHelp
#include <windows.h>
#include <DbgHelp.h>
#include <QDateTime>
#include <QMessageBox>

//程序异常捕获
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
{
    //初始化dump文件夹
    QString logFilePath = QCoreApplication::applicationDirPath() + "/DumpCrashes/";
    QDir dstDir(logFilePath);
    if(!dstDir.exists())
    {
        if(!dstDir.mkpath(logFilePath))
        {
            qDebug()<<__FILE__<<__LINE__<<"创建DumpCrashes文件夹失败!";
        }
    }

    //创建Dump文件
    QString dumpFileName = logFilePath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".dmp";
    HANDLE hDumpFile = CreateFile((LPCWSTR)(dumpFileName.toStdWString().c_str()), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hDumpFile != INVALID_HANDLE_VALUE)
    {
        //Dump信息
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ExceptionPointers = pException;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ClientPointers = TRUE;
        //写入Dump文件内容
        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithUnloadedModules), &dumpInfo, NULL, NULL);
    }

    //这里弹出一个错误对话框并退出程序
    EXCEPTION_RECORD* record = pException->ExceptionRecord;
    QString errCode(QString::number(record->ExceptionCode,16));
    QString errAddr(QString::number((uint)record->ExceptionAddress,16));
    QMessageBox::critical(NULL,"错误",QString("程序异常崩溃捕获!\nerrCode:%1 \nerrAddr:%2").arg(errCode.toStdString().c_str()).arg(errAddr.toStdString().c_str()));

    return EXCEPTION_EXECUTE_HANDLER;
}

//使用
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //注冊异常捕获函数
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   //捕获构造时的异常
    Widget w;
    w.show();
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   //捕获构造完成之后的异常
    return a.exec();
}

四、示例完整代码

1.MyDump.pro

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

#CONFIG += c++11

DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    widget.h

FORMS += \
    widget.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

#设置字符
contains( CONFIG,"msvc" ):QMAKE_CXXFLAGS += /source-charset:utf-8 /execution-charset:utf-8
contains( CONFIG,"msvc" ):QMAKE_CFLAGS +=/source-charset:utf-8 /execution-charset:utf-8

#使用其中之一的话,将另外一个屏蔽即可
#for qBreakpad
#qBreakpad中需要使用到network模块
QT += network

#启用多线程、异常、RTTI、STL支持
CONFIG += thread exceptions rtti stl

#没有c++11和AppKit库编译器不能解决符号的地址
CONFIG += c++11
macx: LIBS += -framework AppKit

#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

#配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/include

CONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}

#for DbgHelp
#方便生成DUMP调试
LIBS += -lDbgHelp
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG

#release版程序带上调试信息
#QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
#QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

2.main.cpp

#include "widget.h"

#include <QApplication>

//logfile
#include <QDir>
#include <QDebug>

//qBreakpad
#include "qBreakpad/include/QBreakpadHandler.h"

//DbgHelp
#include <windows.h>
#include <DbgHelp.h>
#include <QDateTime>
#include <QMessageBox>

//自定义消息处理
void outputMessage(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{
    static QMutex mutex;
    mutex.lock();

    //初始化log文件夹
    QString logFilePath = QCoreApplication::applicationDirPath() + "/DebugLog/";
    QDir dstDir(logFilePath);
    if(!dstDir.exists())
    {
        if(!dstDir.mkpath(logFilePath))
        {
            qDebug()<<__FILE__<<__LINE__<<"创建DebugLog文件夹失败!";
        }
    }

    //获取输出内容
    QString debugDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
    QString debugMsg = QString("%1 \r\n%2").arg(debugDateTime).arg(msg);

    //保存文件
    QString logFileName = logFilePath + "log_" + QDate::currentDate().toString("yyyyMMdd") + ".txt";
    QFile file(logFileName);
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream textStream(&file);
    textStream << debugMsg << "\r\n \r\n";
    file.flush();
    file.close();

    mutex.unlock();
}

//程序异常捕获
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
{
    //初始化dump文件夹
    QString logFilePath = QCoreApplication::applicationDirPath() + "/DumpCrashes/";
    QDir dstDir(logFilePath);
    if(!dstDir.exists())
    {
        if(!dstDir.mkpath(logFilePath))
        {
            qDebug()<<__FILE__<<__LINE__<<"创建DumpCrashes文件夹失败!";
        }
    }

    //创建Dump文件
    QString dumpFileName = logFilePath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".dmp";
    HANDLE hDumpFile = CreateFile((LPCWSTR)(dumpFileName.toStdWString().c_str()), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hDumpFile != INVALID_HANDLE_VALUE)
    {
        //Dump信息
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ExceptionPointers = pException;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ClientPointers = TRUE;
        //写入Dump文件内容
        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithUnloadedModules), &dumpInfo, NULL, NULL);
    }

    //这里弹出一个错误对话框并退出程序
    EXCEPTION_RECORD* record = pException->ExceptionRecord;
    QString errCode(QString::number(record->ExceptionCode,16));
    QString errAddr(QString::number((uint)record->ExceptionAddress,16));
    QMessageBox::critical(NULL,"错误",QString("程序异常崩溃捕获!\nerrCode:%1 \nerrAddr:%2").arg(errCode.toStdString().c_str()).arg(errAddr.toStdString().c_str()));

    return EXCEPTION_EXECUTE_HANDLER;
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //生成输出日志
    qInstallMessageHandler(outputMessage);

    //二选一,将异常捕获函数放在此处可捕获构造时的异常
    //QBreakpadInstance.setDumpPath("BreakCrashes");   //设置生成dump文件路径
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);   //注冊异常捕获函数

    Widget w;
    w.show();

    //二选一,将异常捕获函数放在此处可捕获构造完成之后的异常
    //QBreakpadInstance.setDumpPath("BreakCrashes");
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);

    return a.exec();
}

3.widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QLabel>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pb_crash_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

4.widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    qDebug()<<__FILE__<<__LINE__<<"widget structure!";

    //异常测试1
    //QLabel *label_1;
    //label_1->setText("hello world!");
    //label_1->show();
}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_pb_crash_clicked()
{
    qDebug()<<__FILE__<<__LINE__<<"pb_crash clicked!";

    //异常测试2
    QLabel *label_2;
    label_2->setText("hello world!");
    label_2->show();
}

5.widget.ui
请添加图片描述

五、下载链接

我的示例百度网盘链接:https://pan.baidu.com/s/19eGJH2OnLACxc_C4VDMtWg
提取码:xxcj


总结

这里日志的话比较方便,但很多时候无法定位到具体出错的代码行,你不可能在每一行代码后面都加输出吧?所以用dump文件配合在编译该程序时生成的pdb文件,可以准确定位到调用堆栈、代码行,这样能很好的解决bug啦!
dump文件,后缀*.dmp,是程序崩溃时的内存转储文件;
pdb文件,后缀*.pdb,是程序的符号文件。
关于dump文件和pdb文件的使用,这里我就不做具体介绍了,参考博客中的介绍很详细,更多的可以查看参考博客。


hello:
共同学习,共同进步,如果还有相关问题,可在评论区留言进行讨论。

参考博客:
Qt 之 qInstallMessageHandler(输出详细日志)
Windows下Qt生成dump文件并定位bug(基于qBreakpad)
Qt下使用DbgHelp和SetUnhandledExceptionFilter来获取Crash log/dump文件

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

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

相关文章

Spark大数据处理学习笔记(2.2)搭建Spark Standalone集群

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【http://t.csdn.cn/DrziJ】 文章目录 一、在master虚拟机上安装配置Spark1.1 将spark安装包上传到master虚拟机1.2 将spark安装包解压到指定目录1.3 配置spark环境变量1.4 编辑spark环境配置文件1.5 创建slaves文件&…

Unity记录3.3-地图-柏林噪声生成 2D 地图

文章首发及后续更新&#xff1a;https://mwhls.top/4486.html&#xff0c;无图/无目录/格式错误/更多相关请至首发页查看。 新的更新内容请到mwhls.top查看。 欢迎提出任何疑问及批评&#xff0c;非常感谢&#xff01; 汇总&#xff1a;Unity 记录 摘要&#xff1a;柏林噪声生成…

再学C语言51:C库中的字符串函数(3)

一、strcpy()函数 功能&#xff1a;复制字符串&#xff0c;在字符串中的作用等价于赋值运算符 示例代码&#xff1a; /* test of strcpy() function */ #include <stdio.h> #include <string.h>int main(void) {char arr1[] "Easy doesnt enter into grow…

基于html+css的图片展示19

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

【AI生产力工具】Midjourney:为创意人士提供创造性灵感和支持的工具

文章目录 一、Midjourney是什么&#xff1f;二、Midjourney的优势三、Midjourney的应用四、结语 在现代社会&#xff0c;创意和创新成为越来越重要的能力。然而&#xff0c;创意灵感的获取却不是一件容易的事情&#xff0c;这就需要我们使用一些辅助工具来帮助我们发现和实现创…

照片尺寸怎么修改,3大工具推荐

照片尺寸怎么修改&#xff1f;对于许多人来说&#xff0c;调整图片的尺寸可能是一个日常任务&#xff0c;无论是个人或者工作上都可能会遇到这个需求。适当地调整图片的尺寸可以让图片更具专业性和美观性&#xff0c;而且能够减小文件大小&#xff0c;提高图片的加载速度。在电…

2023年4月北京/西安/郑州/深圳CDGA/CDGP数据治理认证考试报名

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

如何用 Leangoo领歌做迭代规划及迭代执行。

迭代是敏捷开发的核心&#xff0c;正确的迭代可以帮助敏捷团队提高工作交付速度&#xff0c;今天&#xff0c;我们深度看下如何用Leangoo领歌敏捷工具进行迭代规划和迭代执行&#xff0c;高效落地 Scrum。 1、确定迭代需要做的需求 在需求看板中&#xff0c;将已经梳理好的用…

C++ -3- 类和对象 (中) | 构造函数与析构函数(一)

文章目录 1.类的6个默认成员函数2.构造函数3.析构函数构造函数与析构函数应用场景缺省值初始化 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自…

网络安全必学渗透测试流程

这个靶场是一个对渗透新手很友好的靶场。而且&#xff0c;该靶场包含了渗透测试的信息收集&#xff0c;漏洞利用和权限提升的全过程&#xff0c;对新手理解渗透测试的流程有很好的帮助。 靶场地址&#xff1a;https://hackmyvm.eu/machines/machine.php?vmHundred 靶场基本情…

关系数据库(查询优化)

选择操作的实现select * from student where Sno201212128; 简单的全表扫描法优点&#xff1a;对于小表简单有效 缺点&#xff1a;对于大表顺序扫描浪费时间效率低下索引扫描方法 连接操作的实现 连接操作是查询处理中最耗时的操作之一 select *from student,sc where s…

云上数据变革:Databend Cloud 正式发布

2023 年 4 月 20 日&#xff0c;Databend Cloud 经历了近两年的打磨终于发布了&#xff01;&#x1f389; 此次发布会由北京数变科技有限公司【Databend Labs】联合阿里云共同举办。Databend Cloud 借助于云原生数仓 Databend 实现了云简单易用的大数据分析场景。 以下内容来…

Python语言中的注释方法应用

Python语言中的注释方法 在Python编程中&#xff0c;与其他编程语言一样&#xff0c;有良好的注释部分&#xff0c;会让你的程序在后续的改进或优化中&#xff0c;变得便利。同时&#xff0c;给自己培养了良好的编程习惯。 在Python语言中&#xff0c;有两种注释方法。 1.单行…

报告回顾丨模型进化狂飙,DetectGPT能否识别最新模型生成结果?

导读 语言模型给我们的生产生活带来了极大便利&#xff0c;但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。 在3月9日智源Live第33期活动「DetectGPT&#xff1a;判断文本是否为机器生成的工具」中&#xff0c;主讲人Eric为我…

API Testing 一个基于 YAML 文件的开源接口测试工具

API Testing 一个基于 YAML 文件的开源接口测试工具&#xff0c;同时支持运行在本地、服务端。 在选择工具时&#xff0c;可以从很多方面进行考量、对比&#xff0c;以下几点是该工具的特色或者优点&#xff1a; 开源与否&#xff0c;atest 采用 MIT 开源协议&#xff0c;是最流…

传智教育“大同互联网职业技术学院”奠基仪式盛大举行,开拓高等职业教育发展新版图

2023年4月20日&#xff0c;大同市“数字经济职业教育发展论坛暨大同互联网职业技术学院奠基仪式”在大同隆重举行。论坛由中共大同市委、大同市人民政府主办&#xff0c;大同市教育局、传智教育承办&#xff0c;并全程在多平台进行线上直播。 大同市委副书记、市长张强&#x…

学生成绩管理系统winform+SqlServer

主要技术&#xff1a; 基于C#winform架构和sql server数据库 功能模块&#xff1a; 学生选课&#xff0c; 可以查询个学期各科成绩 查看或者选择选课信息 显示当前课表&#xff0c;管理员后台管理 开设课程 课程查询&#xff08;上课时间和地点&#xff09; 录入该课程下的…

【Java】jieba结巴分词器自定义分词词典 超详细完整版

发现一款很轻量好用的分词器->结巴分词器 分享给大家 不仅可以对常规语句分词&#xff0c;还可以自定义分词内容&#xff0c;很强大&#xff01;&#xff01; 源码地址&#x1f449;&#xff1a;https://github.com/huaban/jieba-analysis 简单使用 如果是常规的语句&#…

transformer与vit

transformer结构&#xff0c;位置编码复现 https://wandb.ai/authors/One-Shot-3D-Photography/reports/-Transformer—Vmlldzo0MDIwMjc 训练部分 https://nlp.seas.harvard.edu/2018/04/03/attention.html#training transform训练代码从0构建 https://blog.csdn.net/BXD1…

全注解下的SpringIoc 续1

上篇文章介绍了ioc的基本用法和ComponentScan注解的使用&#xff0c;这篇文章我们来看看依赖注入的部分。 提起依赖注入&#xff0c;想必大家肯定会想到Autowired注解&#xff0c;的确&#xff0c;它是我们用的最多的一个。 还记得容器的顶级接口BeanFactory 吗&#xff0c;它定…