020-第三代软件开发-日志模块

news2025/1/15 20:59:22
头图

第三代软件开发-日志模块

文章目录

  • 第三代软件开发-日志模块
    • 项目介绍
    • 日志模块
    • 日志Demo
    • 第一代日志系统
    • 第二代日志系统

关键字: QtQml日志LogSQLite

项目介绍

欢迎来到我们的 QML & C++ 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C++ 的强大功能,旨在开发出色的用户界面和高性能的后端逻辑。

在项目中,我们利用 QML 的声明式语法和可视化设计能力创建出现代化的用户界面。通过直观的编码和可重用的组件,我们能够迅速开发出丰富多样的界面效果和动画效果。同时,我们利用 QML 强大的集成能力,轻松将 C++ 的底层逻辑和数据模型集成到前端界面中。

在后端方面,我们使用 C++ 编写高性能的算法、数据处理和计算逻辑。C++ 是一种强大的编程语言,能够提供卓越的性能和可扩展性。我们的团队致力于优化代码,减少资源消耗,以确保我们的项目在各种平台和设备上都能够高效运行。

无论您是对 QML 和 C++ 开发感兴趣,还是需要我们为您构建复杂的用户界面和后端逻辑,我们都随时准备为您提供支持。请随时联系我们,让我们一同打造现代化、高性能的 QML & C++ 项目!

重要说明☝

☀该专栏在第三代软开发更新完将涨价

日志模块

软件中日志的重要性是不可忽视的,以下是几个关键原因:

  1. 故障排除和调试:日志记录是排查和解决软件故障的重要工具。当软件出现问题时,日志记录可以帮助开发人员查看系统行为、错误和异常,以便进行故障排除和调试,快速定位并解决问题。

  2. 性能分析和优化:通过记录关键的性能指标和日志信息,开发人员可以分析系统的性能瓶颈和瓶颈所在,以便进行优化。日志记录可以揭示资源使用情况、响应时间、吞吐量等数据,从而帮助开发人员发现并改进性能问题。

  3. 安全审计和合规性:日志记录对于安全审计和合规性要求至关重要。通过记录关键事件和活动,可以跟踪系统的访问、操作和行为,以确保合规性要求得到满足,并提供审计追踪功能,帮助监测和检测潜在的安全威胁和异常行为。

  4. 用户行为分析:通过记录用户的操作和行为,可以了解用户的需求、偏好和行为模式,从而提供更好的用户体验和个性化服务。通过分析用户日志,可以改进产品功能、优化界面设计和提高用户满意度。

  5. 数据分析和决策支持:日志记录产生的数据可以用于进行数据分析,从中提取有价值的信息以支持决策制定。通过分析用户行为、系统运行情况和业务指标等日志数据,可以发现潜在的趋势、问题和机会,为业务决策提供依据。

综上所述,日志记录对于故障排除、性能优化、安全审计、用户分析和决策支持等方面都非常重要。它是软件开发和运维过程中必不可少的一部分,有助于提升系统的可靠性、性能和用户体验。

日志Demo

日志Demo老早就搞过了,最简单的日志记录,可以看这里QtApplets-MyLog

image-20230725214812949

第一代日志系统

如果我没有记错,Demo的日志是在主线程中直接运行的,那么,在程序开发后期,其实我们的的日志量也是不小的,所以呢,在正式项目中,我是把我的日式系统弄到了一个线程里面了,当然,实际我还没有遇到性能瓶颈,因为我们项目一直还是在功能开发阶段,日志还是比较少的,当下的拍错和解Bug大多还是依赖Debug模式,或者DGB调试。这里简单分享下我们第一代日志系统的代码:

头文件

#ifndef TURING_LOG_H
#define TURING_LOG_H
#include <QThread>
#include <QObject>
#include <QDir>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QDebug>
#define LOGPATH "./T_log"

/**
 * 对外宏
 **/

#define LOCATION "$" << __FILE__ << "$" << __LINE__                                                         // 获取代码位置宏
#define CURRENTTHREADID "$" << QThread::currentThread()                                                     // 获取线程ID宏
#define LOGINFOR "$" << __FILE__ << "$" << __LINE__ << "$" << QThread::currentThread()                      // 加上日志内容

/**
 * @brief The LogType enum
 * 彩色日志控制台输出
 * *********************************************************************************************************************************************************
 */
//enum class LogType {
//    Reset = 0,

//    Bold,
//    Unbold,

//    FrontBlack,
//    FrontRed,
//    FrontGreen,
//    FrontYellow,
//    FrontBlue,
//    FrontPurple,
//    FrontCyan,
//    FrontWhite,
//    BackBlack,
//    BackRed,
//    BackGreen,
//    BackYellow,
//    BackBlue,
//    BackPurple,
//    BackCyan,
//    BackWhite,

//    TypeCount
//};
//static const char* logCommands[] = {
//    "\033[0m",
//    "\033[1m",
//    "\033[2m",
//    "\033[30m",
//    "\033[31m",
//    "\033[32m",
//    "\033[33m",
//    "\033[34m",
//    "\033[35m",
//    "\033[36m",
//    "\033[37m",
//    "\033[40m",
//    "\033[41m",
//    "\033[42m",
//    "\033[43m",
//    "\033[44m",
//    "\033[45m",
//    "\033[46m",
//    "\033[47m",
//};

//template <typename EnumType, typename IntType = int>
//int enumToInt(EnumType enumValue);


/*

    彩色控制台日志输出 demo

int main(int argc, char *argv[])
{
    for (int i = enumToInt(LogType::Bold); i < enumToInt(LogType::TypeCount); ++i)
    {
        qInfo().nospace() << logCommands[i] << i << " Hello World" << logCommands[0];
    }
    qWarning() << logCommands[enumToInt(LogType::FrontBlue)]
               << logCommands[enumToInt(LogType::BackRed)]
               << u8"感谢大家对涛哥系列文章的支持,也"
                  "欢迎直接联系我寻求帮助" << logCommands[0];
    return 0;
}


 * **********************************************************************************************************************************************************/
/**
 * @brief The Log_Base class
 * 真实LOG日志处理线程
 */
class Log_Base : public QObject
{
    Q_OBJECT
public:
    explicit Log_Base(QObject *parent = nullptr);

    ~Log_Base();

    void log(QtMsgType type, const QMessageLogContext &context, const QString &msg);

    bool makeLogDir(QString &msg);

    void cleanOldLog(bool isClean = false);

    bool openLogDataBase(QString& mes);

public slots:
    void slot_IntiLog_Base();

private:
    QDir*                                       mLogDir = nullptr;                                                                              // 全局文件夹
    QString                                     mpath;                                                                                          // 数据库路径
    QSqlDatabase                                mTuringLogDB;                                                                                   // 日志数据库
    QSqlQuery                                   sql_query;                                                                                      // 日志执行器
    QString                                     sqlString = "NULL";                                                                             // sql语句
    QString                                     mThreadID = "NULL";                                                                             // 线程ID
    QString                                     mFileName = "NULL";                                                                             // 文件名称
    QString                                     mCurrentLine = "NULL";                                                                          // 代码行数
    QString                                     mInfor = "NULL";                                                                                // 信息
    QString                                     messageType = "";                                                                               // 消息类型
};

/**
 * @brief The Turing_Log class
 * 日志线程管理类
 */
class Turing_Log : public QThread
{
public:
    explicit Turing_Log(QObject *parent = nullptr);

    void log(QtMsgType type, const QMessageLogContext &context, const QString &msg);

    bool makeLogDir(QString &msg);

    void cleanOldLog(bool isClean = false);

    bool openLogDataBase(QString& mes);
private:

    Log_Base*                   mLog_Base = nullptr;                                                                            // 日志核心模块
    QThread*                    mLogThread = nullptr;                                                                           // 日志线程

};

#endif // TURING_LOG_H5

源文件

#include "xxxxx.h"
#include <QSettings>
#include <QSql>
#include <QCoreApplication>
#include <QSqlError>
#include <QSqlDriver>
#include <QDateTime>
#include <QString>

QString mMessage = "";                      //消息
xxxxx::xxxxx(QObject *parent)
    : QThread{parent}
{
    mLog_Base = new Log_Base;
    mLogThread = new QThread;
    mLog_Base->moveToThread(mLogThread);
    connect(mLogThread,&QThread::started,mLog_Base,&Log_Base::slot_IntiLog_Base);
    mLogThread->start();
}
/**
 * @brief xxxxx::log
 * @param type
 * @param context
 * @param msg
 * 解析劫持日志
 */
void xxxxx::log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    if(mLog_Base)
        mLog_Base->log(type,context,msg);
}
/**
 * @brief xxxxx::makeLogDir
 * @param msg
 * @return
 * 创建日志目录
 */
bool xxxxx::makeLogDir(QString& msg)
{
    if(mLog_Base)
        return mLog_Base->makeLogDir(msg);
    else
        return false;
}
/**
 * @brief xxxxx::cleanOldLog
 * @param isClean
 * 清理旧的日志
 */
void xxxxx::cleanOldLog(bool isClean)
{
    if(mLog_Base)
        mLog_Base->cleanOldLog(isClean);
}
/**
 * @brief xxxxx::openLogDataBase
 * @param mes
 * @return
 * 打开数据库
 */
bool xxxxx::openLogDataBase(QString &mes)
{
    if(mLog_Base)
        return mLog_Base->openLogDataBase(mes);
    else
        return false;
}
/**
 * @brief Log_Base::log
 * @param type
 * @param context
 * @param msg
 * 日志线程
 */
Log_Base::Log_Base(QObject *parent)
{
    Q_UNUSED(parent)
}
/**
 * @brief Log_Base::~Log_Base
 * 析构函数,关闭数据库
 */
Log_Base::~Log_Base()
{
    if(mTuringLogDB.isOpen())
        mTuringLogDB.close();
}
/**
 * @brief Log_Base::log
 * @param type
 * @param context
 * @param msg
 * 日志函数
 * 目前这里会保存所有的日志记录,后期需要加入标志,仅仅保留需要的数据
 *
 *
 *
 */
void Log_Base::log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    Q_UNUSED(context);
    mMessage = msg;
    switch(type)
    {
    default:
        break;
    case QtDebugMsg:
        messageType = "输出";
        break;
    case QtInfoMsg:
        messageType = "消息";
        break;
    case QtWarningMsg:
        messageType = "警告";
        break;
    case QtCriticalMsg:
        messageType = "严重";
        break;
    case QtFatalMsg:
        messageType = "致命";
        break;
    }

    while (!mMessage.isEmpty()) {
        if(mMessage.indexOf("$") > 0)
        {
            mInfor = mMessage.mid(0,mMessage.indexOf("$"));
            mMessage = mMessage.mid(mMessage.indexOf("$")+1);
            if(mMessage.indexOf("$") > 0)
            {
                mFileName = mMessage.mid(0,mMessage.indexOf("$"));
                mMessage = mMessage.mid(mMessage.indexOf("$")+1);
                if(mMessage.indexOf("$") > 0)
                {
                    mCurrentLine = mMessage.mid(0,mMessage.indexOf("$"));
                    mMessage = mMessage.mid(mMessage.indexOf("$")+1);
                    if(mMessage.length() > 1)
                    {
                        mThreadID = mMessage;
                    }
                    else
                        mThreadID = "NULL";
                }
                else
                {
                    mCurrentLine = mMessage;
                    mThreadID = "NULL";
                }
            }
            else
            {
                mFileName = mMessage;
                mCurrentLine = "NULL";
                mThreadID = "NULL";
            }
        }
        else
        {
            mInfor = mMessage;
            mFileName = "NULL";
            mCurrentLine = "NULL";
            mThreadID = "NULL";
        }

        if(mThreadID != "NULL")                             //这里需要做下条件判断,只有给了线程ID的日志信息才被记录
        {
            sqlString = QString("insert into day%1(Date,Level,Info,File,Line,ThreadId) values ('%2', '%3', '%4','%5','%6','%7')")
                    .arg(QDateTime::currentDateTime().toString("dd"),
                         QDateTime::currentDateTime().toString("hh:mm:ss"),
                         messageType,
                         mInfor,
                         mFileName,
                         mCurrentLine,
                         mThreadID);
            sql_query.exec(sqlString);
        }
        mMessage.clear();
    }
}
/**
 * @brief Log_Base::makeLogDir
 * @param msg
 * @return
 * 创建日志文件夹
 */
bool Log_Base::makeLogDir(QString &msg)
{
    if(mLogDir->exists(LOGPATH))                                                                    //检查日志文件加是否存在
    {
        mpath = LOGPATH + QString("/") +QDateTime::currentDateTime().toString("yyyy");
        if(mLogDir->exists(mpath))                                                                  //检查对应年份日志文件夹是否旬在
            return true;
        else
            if(mLogDir->mkdir(mpath))
                return true;
            else
            {
                msg = "创建xxxxx文件夹失败";
                return false;
            }
    }
    else
    {
        if(mLogDir->mkdir(LOGPATH))
        {
            mpath = LOGPATH + QString("/") +QDateTime::currentDateTime().toString("yyyy");
            if(mLogDir->exists(mpath))
                return true;
            else
            {
                if(mLogDir->mkdir(mpath))
                    return true;
                else
                {
                    msg = QString("创建%1文件夹失败").arg(QDateTime::currentDateTime().toString("yyyy"));
                    return false;
                }
            }
        }
        else
        {
            msg = "创建xxxxx文件夹失败";
            return false;
        }
    }
    return true;
}
/**
 * @brief Log_Base::cleanOldLog
 * @param isClean
 * 清理旧的日志
 */
void Log_Base::cleanOldLog(bool isClean)
{
    if(isClean)
    {
        mLogDir->setPath(LOGPATH);
        mLogDir->setFilter(QDir::Dirs);
        QFileInfoList list = mLogDir->entryInfoList();
        int i = 0;
        do{
            QFileInfo fileInfo = list.at(i);
            if((fileInfo.fileName() != ".") &&
                    (fileInfo.fileName() != "..") &&
                    (fileInfo.fileName() != QDateTime::currentDateTime().toString("yyyy")))
            {
                mLogDir->setPath(LOGPATH + QString("/") +fileInfo.fileName());
                mLogDir->removeRecursively();
            }
            ++i;
        }while (i<list.size());
    }
}
/**
 * @brief Log_Base::openLogDataBase
 * @param mes
 * @return
 * 打开数据库
 */
bool Log_Base::openLogDataBase(QString &mes)
{
    if(QSqlDatabase::contains("xxxxx_database"))
        mTuringLogDB = QSqlDatabase::database("xxxxx_database");
    else
    {
        mpath = LOGPATH + QString("/") +QDateTime::currentDateTime().toString("yyyy")+QString("/")+ QDateTime::currentDateTime().toString("MM") + ".db";
        mTuringLogDB = QSqlDatabase::addDatabase("QSQLITE","xxxxx_database");
        if(!mTuringLogDB.isValid())
        {
            mes = "数据库驱动无效";
            return false;
        }
        mTuringLogDB.setDatabaseName(mpath);
        mTuringLogDB.setUserName("Root");
        mTuringLogDB.setPassword("Root");
        if(mTuringLogDB.open())
        {
            sql_query = QSqlQuery(mTuringLogDB);
            sqlString = QString("select count(*) from sqlite_master where type='table' and name='day%1'").arg(QDateTime::currentDateTime().toString("dd"));
            sql_query.exec(sqlString);
            if(sql_query.next())
            {
                if(sql_query.value(0).toUInt() == 0)
                {
                    sqlString = QString("create table day%1 (Id INTEGER PRIMARY KEY AUTOINCREMENT,"
                                        "Date varchar(30),"
                                        "Level varchar(30),"
                                        "Info varchar(300),"
                                        "File varchar(300),"
                                        "Line int,"
                                        "ThreadId int)").arg(QDateTime::currentDateTime().toString("dd"));
                    if(!sql_query.exec(sqlString))
                    {
                        mes = "数据库建表";
                        return false;
                    }
                }
            }
        }
        else
        {
            mes = "数据库打开失败";
            return false;
        }
    }
    return true;
}
/**
 * @brief Log_Base::slot_IntiLog_Base
 * 初始化日志核心
 */
void Log_Base::slot_IntiLog_Base()
{
    mLogDir = new QDir;
    QCoreApplication::addLibraryPath("./plugins");
}

//template<typename EnumType, typename IntType>
//int enumToInt(EnumType enumValue)
//{

//    static_assert (std::is_enum<EnumType>::value, "EnumType must be enum");
//    return static_cast<IntType>(enumValue);
//}

在第一代日志系统中,是存在一定问题的,细心的你有发现吗?欢迎在评论去留言。

第二代日志系统

第二代日志系统,也是在第一代日志系统上的改良,就是在数据库存入的时候,不再是单独一条一条的IO存入,而是采用了事物模式,到达一应数量后批量存入数据库,但是这就会触发一个风险,就是如果我们的日志数量没有达到数量级,那就会有丢日志的风险,所以加入了定时器。哎嗨,这里是不是发现一点套路了,没错,咱的灵感就是来源于汽车的那个 3年8万公里,那个先到算那个。所以这样就就极大的保证了日志数据的完整性。至于代码,不用分享了,就加了一个定时器和事物,聪明的你一定可以,不会的话,留言,我教你。


博客签名2021

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

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

相关文章

【Java基础面试三十四】、接口中可以有构造函数吗?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;接口中可以有构造函数吗…

Day3 Qt

作业 1. 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到新的界面中 如果账号和密码不…

经典算法试题(一)

文章目录 一、19头牛1、题目2、思路讲解3、代码实现4、结果 二、分钱1、题目2、思路讲解3、代码实现4、结果 三、儿子做题1、题目2、思路讲解3、代码实现4、结果 四、乐队人数1、题目2、思路讲解3、代码实现4、结果 五、靶子趣谈1、题目2、思路讲解3、代码实现4、结果 六、里程…

外骨骼机器人和人形机器人概览

前言&#xff1a;一点思考 外骨骼机器人和人形机器人都曾随着一些爆品的出现火热过一段时间&#xff0c;但总感觉当前技术条件还不成熟&#xff0c;真正能落地的应用场景不多。马斯克在擎天柱发布会上被问到人形机器人的落地与前景问题时并没有给出明确答案&#xff0c;只是用…

《Selenium 2 自动化测试实战》读书笔记

背景 最近在弄 appium&#xff0c;然后顺便发现了 Selenium 框架和这本书&#xff0c;恰好这本书也介绍了一些软件测试 & 自动化测试的理论知识&#xff0c;遂拿过来学习学习。所以本文几乎没有实践内容&#xff0c;大多都是概念和工具的 mark&#xff0c;后续若有实践&am…

bulldog 靶机

bulldog 信息搜集 存活检测 详细扫描 后台网页扫描 网页信息搜集 正在开发的如果你正在读这篇文章&#xff0c;你很可能是Bulldog Industries的承包商。恭喜你!我是你们的新老板&#xff0c;组长艾伦布鲁克。CEO解雇了整个开发团队和员工。因此&#xff0c;我们需要迅速招到一…

JVM(Java Virtual Machine)垃圾收集算法篇

前言 本文参考《深入理解Java虚拟机》&#xff0c;主要介绍GC相关的算法&#xff0c;引用计数法、可达性分析算法、垃圾收集算法&#xff08;分代收集理论&#xff0c;标记-清除/整理/复制&#xff09; 本系列其他文章链接&#xff1a; JVM&#xff08;Java Virtual Machine&…

视频监控/安防监控平台EasyCVR新功能——视频播放id调阅来咯

TSINGSEE青犀视频监控汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&…

中小型企业云存储选型指南:要点与建议

随着信息技术的快速发展&#xff0c;中小型企业越来越依赖于云存储来管理和存储其日益增长的数据。选择适合自己企业的云存储解决方案是确保数据安全、提高工作效率的关键。 选型注意点&#xff1a; 选择适合自己企业的云存储方案 在选择适合自己企业的云存储方案时&#xff…

日志回滚工作原理剖析及在文件系统的作用

日志回滚原理 当涉及到崩溃恢复和一致性保护时&#xff0c;日志回滚是一种常见的机制。它通过记录写入操作到一个事务日志中&#xff0c;而不是直接应用到文件系统&#xff0c;以保护文件系统的一致性。下面是日志回滚的一般工作原理&#xff1a; 日志记录&#xff1a;在进行写…

【COMP305 LEC 1 2】

Part 1 Artificial Neural Networks(ANN) Topic 1 Historical/Biological Introduction 1. Biological Excitability (a. Virtually all living cells maintain an electrical potential difference between their interiors and the environment (exteriors) . 内部和外…

1812_参考spacemacs的文档拆解ivy layer的组成

全部学习汇总&#xff1a; GreyZhang/editors_skills: Summary for some common editor skills I used. (github.com) 升级了spacemacs的配置&#xff0c;之后重新翻了一下spacemacs的文档。看到了这里面的一个核心的结构layer。这样&#xff0c;结合文档并且找出一个layer来看…

【多线程】线程安全问题和解决方案

我们来看下面这一段代码 public class demo {public static void main(String[] args) throws InterruptedException {Cou count new Cou();Thread t1 new Thread(() -> {for (int i 0; i < 10000; i) {count.add();}});Thread t2 new Thread(() -> {for (int i …

王道计算机考研 操作系统学习笔记篇章二: 进程管理

目录 进程与线程 进程的概念 概念 进程的组成 PCB 程序段、数据段 进程的特征 总结 进程的状态与转换 进程的状态 创建态、就绪态 运行态 阻塞态 终止态 进程的转换 进程的组织 链接方式 索引方式 总结 进程控制 什么是进程控制 如何实现进程控制 进程控制相关的原…

Opencv之RANSAC算法用于直线拟合及特征点集匹配详解

Opencv之RANSAC算法用于直线拟合及特征点集匹配详解 讲述Ransac拟合与最小二乘在曲线拟合上的优缺点 讲述在进行特征点匹配时&#xff0c;最近邻匹配与Ransac匹配的不同之处 另外&#xff0c;Ransac也被用于椭圆拟合、变换矩阵求解等 1. 直线拟合 1.1 原理 RANSAC(RANdom …

两分钟搞懂UiAutomator自动化测试框架

1. UiAutomator简介 UiAutomator是谷歌在Android4.1版本发布时推出的一款用Java编写的UI测试框架&#xff0c;基于Accessibility服务。其最大的特点就是可以跨进程操作&#xff0c;可以使用UiAutomator框架提供的一些方便的API来对安卓应用进行一系列的自动化测试操作&#xf…

Linux程序调试器——gdb的使用

gdb的概述 GDB 全称“GNU symbolic debugger”&#xff0c;从名称上不难看出&#xff0c;它诞生于 GNU 计划&#xff08;同时诞生的还有 GCC、Emacs 等&#xff09;&#xff0c;是 Linux 下常用的程序调试器。发展至今&#xff0c;GDB 已经迭代了诸多个版本&#xff0c;当下的…

C#上位机序列9: 批量读写+事件广播+数据类型处理

一、源码结构&#xff1a; 二、运行效果&#xff1a; 三、源码解析 1. 读取配置文件及创建变量信息&#xff08;点位名称&#xff0c;地址&#xff0c;数据类型&#xff08;bool/short/int/float/long/double&#xff09;&#xff09; 2. 异步任务处理&#xff1a;读任务&…

c++_learning-并发与多线程

并发与多线程 并发&#xff1a;进程&#xff1a;线程&#xff1a;基本概念&#xff1a;线程安全&#xff1a;问题出现的场景&#xff1a;涉及的性质&#xff1a;如何保证线程安全&#xff1f; 并发的实现手段&#xff08;优先使用多线程并发&#xff09;&#xff1a;多进程并发…

【特征重要性揭秘:为什么同一个数据集会有不同结果】

文章目录 特征重要性概要为什么特征重要性分析很重要特征重要性分析方法内置特征重要性(coef_或feature_importances_)Leave-one-out相关性分析递归特征消除 Recursive Feature EliminationXGBoost特性重要性主成分分析 PCA方差分析 ANOVA卡方检验&#xff08;Chi-Square Test&…