【Linux C++】log4cpp日志库的安装和使用详解

news2024/11/14 13:51:46

log4cpp 是一个开源的 C++ 日志库,灵感来源于 Java 的 log4j。它提供了灵活的日志记录功能,可以帮助开发者在 C++ 应用程序中记录、管理和格式化日志信息。log4cpp 支持多种日志记录策略和输出目标,可以满足各种不同的需求。

1.安装

下载压缩包

下载地址:https://sourceforge.net/projects/log4cpp/files/

安装步骤

$ tar xzvf log4cpp-1.1.4rc3.tar.gz

$ cd log4cpp

$ ./configure  //进行自动化构建,自动生成makefile

$ make

$ sudo make install //安装  把头文件和库文件拷贝到系统路径下
    
//安装完后
//默认头文件路径:/usr/local/include/log4cpp
//默认lib库路径:/usr/local/lib

打开log4cpp官网:https://log4cpp.sourceforge.net/

拷贝simple example的内容,编译运行

#include "log4cpp/Category.hh"       // 引入 log4cpp 库的 Category 类
#include "log4cpp/Appender.hh"       // 引入 log4cpp 库的 Appender 基类
#include "log4cpp/FileAppender.hh"   // 引入 log4cpp 库的 FileAppender 类,用于将日志写入文件
#include "log4cpp/OstreamAppender.hh" // 引入 log4cpp 库的 OstreamAppender 类,用于将日志输出到流(如 std::cout)
#include "log4cpp/Layout.hh"          // 引入 log4cpp 库的 Layout 基类
#include "log4cpp/BasicLayout.hh"     // 引入 log4cpp 库的 BasicLayout 类,用于简单的布局格式
#include "log4cpp/Priority.hh"        // 引入 log4cpp 库的 Priority 类,用于设置日志优先级

int main(int argc, char** argv) {
    // 创建一个 OstreamAppender 对象,日志输出到标准输出流(std::cout)
    log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
    appender1->setLayout(new log4cpp::BasicLayout()); // 设置日志布局为 BasicLayout

    // 创建一个 FileAppender 对象,日志输出到文件 "program.log"
    log4cpp::Appender *appender2 = new log4cpp::FileAppender("default", "program.log");
    appender2->setLayout(new log4cpp::BasicLayout()); // 设置日志布局为 BasicLayout

    // 获取根日志类别并设置其优先级为 WARN(警告)
    log4cpp::Category& root = log4cpp::Category::getRoot();
    root.setPriority(log4cpp::Priority::WARN);
    root.addAppender(appender1); // 将 OstreamAppender 添加到根类别

    // 获取名为 "sub1" 的日志类别,并将 FileAppender 添加到该类别
    log4cpp::Category& sub1 = log4cpp::Category::getInstance(std::string("sub1"));
    sub1.addAppender(appender2);

    // 使用根类别记录错误和信息消息
    root.error("root error"); // 记录错误级别的日志,输出到 std::cout
    root.info("root info");  // 记录信息级别的日志,输出将被忽略(因根类别的优先级为 WARN)

    // 使用 "sub1" 类别记录错误和警告消息
    sub1.error("sub1 error"); // 记录错误级别的日志,输出到 "program.log"
    sub1.warn("sub1 warn");   // 记录警告级别的日志,输出到 "program.log"

    // 使用 printf 风格记录日志消息
    root.warn("%d + %d == %s ?", 1, 1, "two"); // 格式化日志消息并记录到 std::cout

    // 使用流的方式记录日志消息
    root << log4cpp::Priority::ERROR << "Streamed root error"; // 记录错误级别的日志,输出到 std::cout
    root << log4cpp::Priority::INFO << "Streamed root info";  // 记录信息级别的日志,输出将被忽略(因根类别的优先级为 WARN)
    sub1 << log4cpp::Priority::ERROR << "Streamed sub1 error"; // 记录错误级别的日志,输出到 "program.log"
    sub1 << log4cpp::Priority::WARN << "Streamed sub1 warn";  // 记录警告级别的日志,输出到 "program.log"

    // 使用 errorStream() 记录错误级别的日志消息
    root.errorStream() << "Another streamed error"; // 记录错误级别的日志,输出到 std::cout

    return 0; // 程序结束
}

编译指令:** g++ log4cppTest.cc -llog4cpp -lpthread**

可能报错:找不到动态库

解决方法:

cd /etc

sudo vim ld.so.conf

将默认的lib库路径写入,再重新加载

sudo ldconfig

让动态链接库为系统所共享

ld.so.cache 执行了sudo ldconfig之后,会更新该缓存文件,会将所有动态库信息写入到该文件。当可执行程序需要加载相应动态库时,会从这里查找。

完成这些操作后,再使用上面的编译指令去编译示例代码

2.日志系统的设计

日志系统的设计,一般而言要抓住最核心的一条,就是日志从产生到到达最终目的地期间的处理流程。一般而言,为了设计一个灵活可扩展,可配置的日志库,主要将日志库分为4个部分去设计,分别是:记录器、过滤器、格式化器、输出器四部分。

记录器(日志来源):负责产生日志记录的原始信息,比如(原始信息,日志优先级,时间,记录的位置)等等信息。

过滤器(日志系统优先级):负责按指定的过滤条件过滤掉我们不需要的日志。

格式化器(日志布局):负责对原始日志信息按照我们想要的格式去格式化。

输出器(日志目的地):负责将将要进行记录的日志(一般经过过滤器及格式化器的处理后)记录到日志目的地(例如:输出到文件中)。

下面以一条日志的生命周期为例说明日志库是怎么工作的。

一条日志的生命周期:

  1. 产生:info(“log information.”);

  2. 经过记录器,记录器去获取日志发生的时间、位置、线程信息等等信息;

  3. 经过过滤器,决定是否记录;

  4. 经过格式化器处理成设定格式后传递给输出器。例如输出“2018-3-22 10:00:00 [info] log information.”这样格式的日志到文件中。日志的输出格式由格式化器实现,输出目的地则由输出器决定;

  5. 这条日志信息生命结束。

3.log4cpp的核心组件

3.1日志目的地(Appender)

Appender 是 log4cpp 的核心组件之一,负责将日志消息输出到不同的目标,如控制台、文件、网络等。log4cpp 提供了多种类型的 Appender,允许你将日志信息输出到多种地方。

  • FileAppender:将日志消息写入文件。适合持久化日志记录。
  • OstreamAppender :C++通用输出流(如 cout)
  • ConsoleAppender:将日志消息输出到控制台。适用于调试和开发阶段。
  • SyslogAppender:将日志消息发送到系统日志服务。
  • RollingFileAppender:将日志写入文件,并支持日志文件的滚动,避免单个日志文件过大。

3.2日志布局(Layout)

Layout 定义了日志消息的格式。log4cpp 提供了不同类型的 Layout,用于格式化日志消息的输出内容。

  • PatternLayout:最常用的布局,允许自定义格式化模式。例如,可以指定时间戳、日志级别、日志消息等。
  • SimpleLayout:输出简洁的日志格式,通常只包含日志级别和消息。
  • XMLLayout:将日志消息格式化为 XML 格式,适用于需要结构化日志数据的场景。

注意(极易出错):

当日志系统有多个日志目的地时,每一个目的地Appender都需要设置一个布局Layout(一对一关系)

3.3日志记录器(Category)

Category 是日志记录的核心组件,代表一个日志记录器。它管理不同的 Appender 和日志级别。Category 提供了多种方法来记录日志,如 debuginfowarnerrorfatal

  • Root Category:系统默认的根日志记录器,所有日志记录都可以通过根记录器进行管理。
  • 子类 Category:可以创建子类记录器,用于更细粒度的日志记录和管理。

创建Category对象时,可以用getRoot先创建root模块对象,对root模块对象设置优先级和目的地;

再用getInstance创建叶模块对象,叶模块对象会继承root模块对象的优先级和目的地,可以再去修改优先级、目的地

补充:如果没有创建根对象,直接使用getInstance创建叶对象,会先隐式地创建一个Root对象。

子Category可以继承父Category的信息:优先级、目的地

3.4日志优先级(Priority)

对于 log4cpp 而言,有两个优先级需要注意,一个是日志记录器的优先级,另一个就是某一条日志的优先级。Category对象就是日志记录器,在使用时须设置好其优先级;某一行日志的优先级,就是Category对象在调用某一个日志记录函数时指定的级别,如 logger.debug("this is a debug message") ,这一条日志的优先级就是DEBUG级别的。简言之:

日志系统有一个优先级A,日志信息有一个优先级B

只有B高于或等于A的时候,这条日志才会被输出(或保存),当B低于A的时候,这条日志会被过滤;

class LOG4CPP_EXPORT Priority {
public:
	typedef enum {
			EMERG = 0,
			FATAL = 0,
			ALERT = 100,
			CRIT = 200,
			ERROR = 300,
			WARN = 400,
			NOTICE = 500,
			INFO = 600,
			DEBUG = 700,
			NOTSET = 800 //这个不代表可以使用的优先级
	} PriorityLevel;
	//......
};  //数值越小,优先级越高;数值越大,优先级越低

4.使用示例

4.1将日志输出到屏幕

#include <iostream>
#include <fstream>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/Category.hh>
#include <log4cpp/Priority.hh>
using std::cout;
using std::endl;
using std::ofstream;
using namespace log4cpp;

void test0(){
    //1.创建布局对象
    PatternLayout * ptn1 = new PatternLayout();
    ptn1->setConversionPattern("%d %c [%p] %m%n");

    //2.创建目的地对象
    OstreamAppender* appender1 = new OstreamAppender("output",&cout);

    /* ofstream ofs("wd.log",std::ios::app); */
    /* OstreamAppender* appender1 = new OstreamAppender("output",&ofs); */
    //目的地绑定布局
    appender1->setLayout(ptn1);

    //3.创建记录器
    /* Category & sub1 = Category::getRoot(); */
    Category & sub1 = Category::getRoot().getInstance("sub1");
    //4.设置系统的优先级
    sub1.setPriority(Priority::WARN);
    //5.记录器添加目的地
    sub1.addAppender(appender1);

    //6.写日志
    sub1.emerg("this is an emerg msg");
    sub1.fatal("this is a fatal msg");
    sub1.alert("this is an alert msg");
    sub1.crit("this is a crit msg");
    sub1.error("this is an error msg");
    sub1.warn("this is a warn msg");
    sub1.notice("this is a notice msg");
    sub1.info("this is an info msg");
    sub1.debug("this is a debug msg");
    
    
    //7.关闭资源
    Category::shutdown();


}

int main(void){
    test0();
    return 0;
}

编译:g++ 01-log4cppPattern.cpp -llog4cpp -lpthread

4.2将日志输出到屏幕和保存到日志文件中

#include <iostream>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/Category.hh>
#include <log4cpp/Priority.hh>
using std::cout;
using std::endl;
using namespace log4cpp;

void test0(){
    //1.创建布局对象
    PatternLayout * ptn1 = new PatternLayout();
    ptn1->setConversionPattern("%d %c [%p] %m%n");

    PatternLayout * ptn2 = new PatternLayout();
    ptn2->setConversionPattern("%d %c [%p] %m%n");

    //2.创建目的地对象
    OstreamAppender* appender1 = new OstreamAppender("output",&cout);
    //目的地绑定布局
    appender1->setLayout(ptn1);

    FileAppender * appender2 = new FileAppender("fileApp","wd.log");
    appender2->setLayout(ptn2);//目的地与布局一对一进行绑定

    //3.创建记录器
    /* Category & sub1 = Category::getRoot(); */
    Category & sub1 = Category::getRoot().getInstance("sub1");
    //4.设置系统的优先级
    sub1.setPriority(Priority::WARN);
    //5.记录器添加目的地
    sub1.addAppender(appender1);
    sub1.addAppender(appender2);

    //6.写日志
    sub1.emerg("this is an emerg msg");
    sub1.fatal("this is a fatal msg");
    sub1.alert("this is an alert msg");
    sub1.crit("this is a crit msg");
    sub1.error("this is an error msg");
    sub1.warn("this is a warn msg");
    sub1.notice("this is a notice msg");
    sub1.info("this is an info msg");
    sub1.debug("this is a debug msg");
    
    //7.关闭资源
    Category::shutdown();
    
    //不要这样使用
    /* delete ptn1; */
}

int main(void){
    test0();
    return 0;
}

4.3将日志输出保存到回滚文件

#include <iostream>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/RollingFileAppender.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/Category.hh>
#include <log4cpp/Priority.hh>
using std::cout;
using std::endl;
using namespace log4cpp;

void test0(){
    //1.创建布局对象
    PatternLayout * ptn1 = new PatternLayout();
    ptn1->setConversionPattern("%d %c [%p] %m%n");

    PatternLayout * ptn2 = new PatternLayout();
    ptn2->setConversionPattern("%d %c [%p] %m%n");

    PatternLayout * ptn3 = new PatternLayout();
    ptn3->setConversionPattern("%d %c [%p] %m%n");

    //2.创建目的地对象
    OstreamAppender* appender1 = new OstreamAppender("output",&cout);
    //目的地绑定布局
    appender1->setLayout(ptn1);

    FileAppender * appender2 = new FileAppender("fileApp","wd.log");
    appender2->setLayout(ptn2);//目的地与布局一对一进行绑定

    auto appender3 = new RollingFileAppender("rolling",
                                             "ROLL.log",
                                             5 * 1024,
                                             9); 
    appender3->setLayout(ptn3);



    //3.创建记录器
    /* Category & sub1 = Category::getRoot(); */
    Category & sub1 = Category::getRoot().getInstance("sub1");
    //4.设置系统的优先级
    sub1.setPriority(Priority::DEBUG);
    //5.记录器添加目的地
    sub1.addAppender(appender1);
    sub1.addAppender(appender2);
    sub1.addAppender(appender3);

    //6.写日志
    int count = 0;
    while(count < 30){
        sub1.emerg("this is an emerg msg");
        sub1.fatal("this is a fatal msg");
        sub1.alert("this is an alert msg");
        sub1.crit("this is a crit msg");
        sub1.error("this is an error msg");
        sub1.warn("this is a warn msg");
        sub1.notice("this is a notice msg");
        sub1.info("this is an info msg");
        sub1.debug("this is a debug msg");
        count++;
    }

    //7.关闭资源
    Category::shutdown();

    //不要这样使用
    /* delete ptn1; */
}

int main(void){
    test0();
    return 0;
}

4.4log4cpp配置文件读取

#include <log4cpp/Category.hh>          // 引入 log4cpp 库的 Category 类,用于日志记录
#include <log4cpp/PropertyConfigurator.hh> // 引入 log4cpp 库的 PropertyConfigurator 类,用于从配置文件中配置日志

int main(int argc, char* argv[])
{
    // 指定配置文件名,通常是 log4cpp 的配置文件
    std::string initFileName = "log4cpp.properties";
    
    // 从配置文件中配置 log4cpp 的日志系统
    log4cpp::PropertyConfigurator::configure(initFileName);

    // 获取根日志类别
    log4cpp::Category& root = log4cpp::Category::getRoot();

    // 获取名为 "sub1" 的日志类别
    log4cpp::Category& sub1 = 
        log4cpp::Category::getInstance(std::string("sub1"));

    // 获取名为 "sub1.sub2" 的日志类别
    log4cpp::Category& sub2 = 
        log4cpp::Category::getInstance(std::string("sub1.sub2"));

    // 使用根类别记录警告消息
    root.warn("Storm is coming");

    // 使用 "sub1" 类别记录调试和信息消息
    sub1.debug("Received storm warning");
    sub1.info("Closing all hatches");

    // 使用 "sub1.sub2" 类别记录调试和错误消息
    sub2.debug("Hiding solar panels");
    sub2.error("Solar panels are blocked");
    sub2.debug("Applying protective shield");
    sub2.warn("Unfolding protective shield");
    sub2.info("Solar panels are shielded");

    // 使用 "sub1" 类别记录信息消息
    sub1.info("All hatches closed");

    // 使用根类别记录信息消息
    root.info("Ready for storm.");

    // 关闭 log4cpp 日志系统
    log4cpp::Category::shutdown();

    return 0; // 程序结束
}
//log4cpp.properties
log4cpp.rootCategory=DEBUG, rootAppender
log4cpp.category.sub1=DEBUG, A1, A2
log4cpp.category.sub1.sub2=DEBUG, A3

log4cpp.appender.rootAppender=ConsoleAppender
log4cpp.appender.rootAppender.layout=PatternLayout
log4cpp.appender.rootAppender.layout.ConversionPattern=%d [%p] %m%n 

log4cpp.appender.A1=FileAppender
log4cpp.appender.A1.fileName=A1.log
log4cpp.appender.A1.layout=BasicLayout

log4cpp.appender.A2=FileAppender
log4cpp.appender.A2.threshold=WARN
log4cpp.appender.A2.fileName=A2.log
log4cpp.appender.A2.layout=PatternLayout
log4cpp.appender.A2.layout.ConversionPattern=%d [%p] %m%n 

log4cpp.appender.A3=RollingFileAppender
log4cpp.appender.A3.fileName=A3.log
log4cpp.appender.A3.maxFileSize=200
log4cpp.appender.A3.maxBackupIndex=1
log4cpp.appender.A3.layout=PatternLayout
log4cpp.appender.A3.layout.ConversionPattern=%d [%p] %m%n 

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

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

相关文章

2.2.2 Posix API与网络协议栈2

接上回2.2.2 posix api 2 传输数据 四线一⚪&#xff0c;kernel 协议栈 send- write&#xff0c; recv-read 如何知道send成功&#xff1f; 如果返回整数&#xff0c;只能证明data从user copy到内核kernel wmemory了 就是数据传输是异步的&#xff0c;send是从application cop…

二叉树的层序遍历 C++

给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示例 2&#xff1a; 输入&…

李宏毅 机器学习与深度学习【2022版】 02

文章目录 一、机器学习任务攻略二、优化失败1、梯度很小的情况2、Batch and Momentum 三、自适应学习率 Adaptive Learning Rate四、分类问题简述五、重温神奇宝贝和数码宝贝分类器六、深度学习的优化 一、机器学习任务攻略 因为在训练集中&#xff0c;56层的network一定至少和…

等保测评(三级)服务器和终端-测评项及整改措施(详细)

本文按照三级等保标准进行测评&#xff0c;可参考进行加固。 等保测评是信息安全等级保护工作的基本制度、基本策略和基本方法。 等保测评是信息安全等级保护工作的基本制度、基本策略和基本方法。信息系统运营、使用单位应选择符合国家要求的测评机构&#xff0c;依据《信息…

Java中常用的API

具体的例子就不再列出了 自己尝试 // 注意第三个方法 1.如果数据源数组和目的地数组都是基本数据类型&#xff0c;那么两者的类型必须保持一致&#xff0c;否则会报错 2.如果数据源数组和目的地数组都是引用数据类型&#xff0c;那么子类类型可以赋值给父类类型 public cl…

并发服务器---IO多路复用

单循环服务器&#xff1a;同一时刻只能处理一个客户端任务 并发服务器&#xff1a; 同一时刻&#xff0c;只能处理多个客户端的任务 实现方法&#xff1a;多进程 多线程 IO多路复用 IO多路复用&#xff1a; 1.阻塞io&#xff08;fgets scanf recv getchar read&#x…

【python计算机视觉编程——1.基本的图像操作和处理】

python计算机视觉编程——1.基本的图像操作和处理 1.基本的图像操作和处理1.1 PIL:Python图像处理类库1.1.1 转换图像格式1.1.2 创建缩略图1.1.3 复制和粘贴图像区域1.1.4 调整尺寸和旋转 1.2 Matplotlib1.2.1绘制图像、点和线1.2.2 图像轮廓和直方图 1.3 Numpy1.3.1 图像数组表…

【STM32】DMA

描述 大部分图片来源&#xff1a;正点原子HAL库课程 专栏目录&#xff1a;记录自己的嵌入式学习之路-CSDN博客 目录 0 概述 1 原理&#xff08;用于个人理解的&#xff0c;非常重要&#xff01;&#xff09; 1.1 DMA请求 1.2 循环模式与DMA请求的关系 1.3 …

以科研为本 创新突破的品牌理念 朵拉朵尚荣获2023年度影响力品牌奖

以科研为本 创新突破的品牌理念 朵拉朵尚荣获2023年度影响力品牌奖 随着国内美妆行业经过数十年的快速发展&#xff0c;已经形成了数千亿规模的产业链&#xff0c;在产业集聚、行业新技术不断涌现、产业链持续优化等背景下&#xff0c;美妆行业该如何通过科技创新&#xff0c;…

流量太炸裂了!一键AI替换视频人物,几分钟极速制作爆款视频!

目录 一、案例分析 二、制作教程 1.1、通义千问APP——角色扮演 1.2、Motionshop 1.3、Wonder Studio 1.4、Viggle 三、结束语 最近网上火了一种新玩法&#xff1a;用AI技术&#xff0c;一键就能把视频里的人物换成机器人或者任何你想要的角色。就像之前那些视频&#x…

二叉树高频题目-上-不含树型dp

二叉树高频题目-上-不含树型dp 题目1 : 二叉树的层序遍历 测试链接 : https://leetcode.cn/problems/binary-tree-level-order-traversal/ 思路 自己使用数组实现队列, 在队列中进行广度优先遍历先将根结点进队, 如果队列里还有东西, 按照队列大小进行循环, 让队列里的结点进…

创建一个最简单的FastAPI

如何生成一个最简单的 FastAPI 文件&#xff1f; FastAPI官方文档&#xff1a;https://fastapi.tiangolo.com/zh/tutorial/first-steps/ # -*- coding: utf-8 -*-""" file: main.py author: CSDN-北极的三哈 time: 2024/8/27 22:11 email:flymeawei163.com so…

【自动驾驶】控制算法(六)前馈控制与航向误差

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

Python爬虫使用实例_1

Python爬虫使用实例 —— 续 IDE: Pycharm or Jupyter Notebook 6. 网易云歌榜 &#x1f95d; 获取地址 记得把#/去掉就好&#xff0c;一定要记得&#xff0c;否则没用。 热歌榜的url是 https://music.163.com/discover/toplist?id3778678 &#xff0c;同理可得其他榜的ur…

Java设计模式之单例模式详细讲解和案例示范

单例模式&#xff08;Singleton Pattern&#xff09;是Java设计模式中最简单但却非常实用的一种。它确保一个类只有一个实例&#xff0c;并提供一个全局的访问点。本文将通过电商交易系统为例&#xff0c;详细探讨单例模式的使用场景、常见问题及解决方案。 1. 单例模式简介 …

【LeetCode Cookbook(C++ 描述)】平衡二叉树

目录 平衡二叉树基础不同插入节点方式的不同旋转LL 型失衡RR 型失衡LR 型失衡RL 型失衡 删除操作删除节点为二叉树的叶子节点删除的节点只有左子树或者右子树删除的节点既有左子树又有右子树 LeetCode #110&#xff1a;Balanced Binary Tree 平衡二叉树递归法&#xff08;自底向…

[C++番外] 抛异常

一、C语言的时候我们怎么判断错误的呢&#xff1f; C语言的错误处理机制&#xff1a; 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。返回错误码&#xff0c;缺陷&#xff1a;需要程序员自己去查…

字典序排数

题目链接 字典序排数 题目描述 注意点 1 < n < 5 * 10^4 解答思路 参照题解使用dfs完成本题&#xff0c;需要注意的是结果不包含0&#xff0c;所以先遍历第一层&#xff08;1~9&#xff09;&#xff0c;再根据每个节点继续深搜&#xff0c;将访问到的节点按顺序添加…

测试开发面试题目汇总

之前因为面临换工作&#xff0c;所以通过一些渠道汇总了一些面试题目&#xff0c;然后加入了部分自己面试过程中遇到的问题&#xff0c;因此记录下来。文末有惊喜。 1. 项目经验 2. 测试的过程 3. 京东登录页面怎么测&#xff1f; 4. 如果一个普通用户&#xff0c;他的百度首…

EasyExcel动态映射Excel数据到任意实体类教程

在使用EasyExcel进行Excel导入时&#xff0c;我们经常需要将Excel中的数据映射到Java实体类中。如果Excel的列名是固定的&#xff0c;我们可以通过ExcelProperty("列名")注解直接在实体类中指定列名。但如果Excel的列名不固定&#xff0c;或者我们希望根据Excel的第一…