ejson4cpp——一个使用极致简单且性能可比rapidjson的C++json解析库

news2024/11/15 2:19:07

文章目录

  • ejson4cpp
    • 快速开始
      • 要求
      • 安装与引入
      • 开始使用
    • 常见用法
    • API介绍
      • 通过命名风格识别API
      • 宏定义
        • FROM_JSON_FUNC&FROM_JSON_FRIEND_FUNC
        • TO_JSON_FUNC&TO_JSON_FRIEND_FUNC
        • AUTO_GEN_NON_INTRUSIVE&AUTO_GEN_INTRUSIVE
        • ENABLE_JSON_COUT
      • FromJSON系列函数
        • 参数说明
        • 使用示例
      • ToJSON系列函数
        • 参数说明
        • 使用示例
      • JObject系列函数
        • JObject的构造函数
        • JObject的成员函数
          • kDict
          • kList
          • to_string
          • static
    • 注意事项

代码仓库: https://github.com/ACking-you/ejson4cpp

ejson4cpp

ejosn4cpp :意味着这是一个使用上非常 easy,同时性能上也非常 efficiency c++ json解析库。 支持c++11及以上,并且完全的跨平台。

  • 使用 easy 体现在:

    1. api简单,你只需要关注两个函数(FromJSON、ToJSON),且支持一键json结构体互转。
    2. 引入简单,支持cmake命令一键引入项目并使用。
    3. 错误定位简单,无论是解析json还是序列化为json,任何错误的操作都会有详细的报错信息(模拟打印了堆栈信息),让错误定位更简单。
  • 性能 efficiency 体现在:
    本机benchmark(3000行json)结果如图:
    benchmark

    1. 反序列化(Parse)性能明显领先 nlohmann-jsonjsoncpp,但只有 rapidjson 的一半性能。
    2. 序列化(Stringify)性能遥遥领先其他所有json库一个数量级。
    3. 查找(FindMember):由于看过 rapidjson 源码,发现其内部每个元素的节点是以数组的形式组织的,并没有用到其他高深的数据结构,故专门对他进行成员查找测试,发现确实是 O(n) 级别的查找性能。

    benchmark的代码仓库:https://github.com/ACking-you/bench_json4cpp

快速开始

要求

  • C++11及以上,是跨全平台的

安装与引入

推荐用以下两种方式进行引入:

  • 方法一:通过cmake中的 FetchContent 模块引入

    1. 在项目的cmake中添加下列代码进行引入,国内如果因为网络问题无法使用可以换这个gitee的镜像源:https://gitee.com/acking-you/ejson4cpp.git

      include(FetchContent)
      
      FetchContent_Declare(
              ejson4cpp
              GIT_REPOSITORY https://github.com/ACking-you/ejson4cpp.git
              GIT_TAG v1.5.2
              GIT_SHALLOW TRUE)
      FetchContent_MakeAvailable(ejson4cpp)
      
    2. 在需要使用该库的目标中链接 ejson 即可。

      target_link_libraries(target  ejson)
      
  • 方法二:手动下载包,然后通过cmake命令引入

    1. 通过git命令下载项目源码

      git clone https://github.com/ACking-you/ejson4cpp.git
      
    2. 将该项目添加到子项目中:

      add_subdirectory(ejson4cpp)
      
    3. 在需要使用该库的目标中链接 ejson 即可。

      target_link_libraries(target  ejson)
      

开始使用

这里以解析 json 的配置文件映射到 C++ 结构体为例子来进行讲解。

假设有redis、mysql、日志服务需要通过配置文件来进行配置,我们先写下结构体如下:

struct server
{
   int         port{};
   std::string host;
};

struct log
{
   std::string level;
   std::string filedir;
   std::string formatter;
};

struct config
{
   log    logger;
   server redis;
   server mysql;
};

一个模拟的json配置文件如下:

{
"logger": {
  "filedir": "home/project/1",
  "formatter": "default",
  "level": "debug"
},
"mysql": {
  "host": "192.31.1.1",
  "port": 1314
},
"redis": {
  "host": "127.0.0.1",
  "port": 1444
}
}

现在要实现的功能是读取json配置文件的数据将 config 结构体进行初始化,我们可以按照下面的步骤进行:

完整代码请看 example/example1.cc

  1. 让server、log、config这几个自定义类型支持 json 序列化,添加下列宏定义即可:

    // auto generate log/server/config to_json and from_json
    AUTO_GEN_NON_INTRUSIVE(log, level, filedir, formatter)
    AUTO_GEN_NON_INTRUSIVE(server, host, port)
    AUTO_GEN_NON_INTRUSIVE(config, logger, redis, mysql)
    
  2. 定义config变量,调用 FromFile 函数,即可完成需求:

    struct config s_config;
    // init config from config.json
    Parser::FromFile(CONFIG_PATH, s_config);
    

    如果需要重新写回文件,则可调用 ToFile 函数:

    // write config to file
    Parser::ToFile(CONFIG_PATH, s_config);
    

    如果读取json字符串的数据并初始化对应的变量(反序列化)则可以调用 FromJSON 函数:

    // init config struct from json string
    Parser::FromJSON(j, s_config);
    

    如果需要将变量转化为json字符串(序列化),则可调用 ToJSON 函数:

    auto json_str = Parser::ToJSON(s_config);
    

好的,经过以上两步,你已经学会了整个库的核心用法,没错,这个库提倡使用直接的函数而不是类来实现对应的功能,这样能减少你的记忆和思考过程。当然如果需要更为细致的使用它,你可以去了解 JObject 类的相关用法,在API介绍里面写的很详细。

常见用法

在进行后端开发的过程中,前端传来的数据很多时候是 json 数据,那么我们现在就使用该库来模拟一个简单的后端业务。

比如一个视频平台的评论区,首先映入眼帘的是一条条评论,然后是发出该条评论的用户。

那么我们可以抽离出 commentuser_info 这两个结构体表示前端需要展示的消息,那么它在我们C++后端的请看可能是下面这样的结构体:

完整代码在 example/example2.cc

struct user_info
{
   bool        is_follow{};//是否关注
   int64_t     id{};//id信息
   int64_t     follow_count{};//关注的博主数量
   int64_t     follower_count{};//粉丝数量
   std::string name;//用户名
};

struct comment
{
   int64_t     id{};//id信息
   int64_t     user_id{};//用户id信息
   std::string created_date;//创建时间
   std::string content;//评论内容
};

那么我们的后端逻辑可能会经历下面的过程:

  1. 从前端获取json数据(中间一般有鉴权的过程)。
  2. 接收json数据并其初始化为对应的C++结构体。
  3. 进行该次接口调用的业务逻辑处理。
  4. 保存数据到数据库。

那么我们用模拟数据来模拟上述过程:

  1. 前端的数据:

    const char* comment_json   = "{\n"
        "  "content": "这是一条\"测试\"评论",\n"
        "  "created_date": "2023-01-16",\n"
        "  "id": 1,\n"
        "  "user_id": 10\n"
        "}";
    const char* user_info_json = "{\n"
        "  "follow_count": 12,\n"
        "  "follower_count": 23,\n"
        "  "id": 1,\n"
        "  "is_follow": false,\n"
        "  "name": "某人名字"\n"
        "}";
    
  2. 将数据转为C++的结构体:
    需要先添加下列宏让对应的结构支持json互转

    AUTO_GEN_NON_INTRUSIVE(user_info, is_follow, id, follow_count, follower_count,name)
    AUTO_GEN_NON_INTRUSIVE(comment, id, user_id, created_date, content)
    

    然后调用对应函数即可转化

    comment   cmt;
    user_info uinfo;
    Parser::FromJSON(comment_json, cmt);
    Parser::FromJSON(user_info_json, uinfo);
    
  3. 处理业务逻辑,这个跳过。

  4. 保存数据到数据库,这个模拟为保存数据到文件:
    我们可以创建一个 dict_t 类型的 JObject ,然后把刚才的结构体以 key-value 对的形式放进去,最后再调用 ToFile 函数。

    // 4.save data to database(we simulate it to local file)
    auto object = JObject::Dict();
    object.at("comment").get_from(cmt);
    object.at("user_info").get_from(uinfo);
    ejson::Parser::ToFile(DATA_PATH, object);
    

    最终得到下列json数据到文件中:

    {
      "comment": {
        "content": "这是一条"测试"评论",
        "created_date": "2023-01-16",
        "id": 1,
        "user_id": 10
      },
      "user_info": {
        "follow_count": 12,
        "follower_count": 23,
        "id": 1,
        "is_follow": false,
        "name": "某人名字"
      }
    }
    

API介绍

对所有类成员的描述的信息,可以点开 doc/html/index.html 进行查看。如果需要其他语言版本的文档,可以自己通过 Doxygen 进行生成。

通过命名风格识别API

  1. 所有对外暴露的静态成员函数均以 PascalCase 风格命名。如下:

    namespace ejson {
    class Parser
    {
       static JObject FromJSON(const str_t &content,bool skip_comment=false);
        
       template <class T>
       static void FromJSON(string_view const &src, T &dst,bool skip_comment=false);
        
       static JObject FromFile(string_view const &filename,bool skip_comment=false);
        
       template <class T>
       static void FromFile(string_view const &filename, T &dst);
       
       template <class T>
       static std::string ToJSON(T &&src,const int indent = -1,
                            const char indent_char = ' ', bool is_esc = false);
        
       template <class T>
       static void ToFile(string_view const &filename, T const &src, 
                          const int indent = -1, const char indent_char = ' ',
                          bool is_esc = false);
        
       static void ToFile(string_view const &filename, JObject const &src,
                         const int indent = -1, const char indent_char = ' ',
                          bool is_esc = false)
    };
    
    class JObject
    {
       static auto Dict() -> JObject;
       static auto List() -> JObject;
    };
    }   // namespace ejson
    
  2. 所有想要暴露的普通成员函数均以 snack_case 风格命名,如下:

    namespace ejson {
    class JObject
    {
       auto type() const -> Type;
        
       auto at(const str_t &key) const -> ObjectRef;
        
       auto to_string(int indent = -1, char indent_char = ' ',
                                      bool is_esc = false) const -> string;
        
       void push_back(JObject item);
        
       void pop_back();
        
       auto has_key(const str_t &key) const -> bool;
    
       template <class T>
       auto cast() const -> T;
    };
    
    struct ObjectRef
    {
       template <class T>
       auto get_from(T const &src) -> ObjectRef &;
       template <class T>
       void get_to(T &src);
    };
    }   // namespace ejson
    
  3. 其余还有两个函数,如下:

    namespace ejson_literals {
    
    auto operator""_json(const char *json, size_t len) -> JObject;
        
    auto float_d(int d) -> int;
    
    }   // namespace ejson_literals
    

宏定义

利用宏定义可以方便且迅速的让自定义类型支持 FromJSONToJSON 系列函数。

实际上自定义类型在使用 FromJSON 时,只需要定义对应的 from_json 函数,使用 ToJSON 时,只需定义对应的 to_json 函数。

下列是 from_json 和 to_json 的函数签名:

void from_json(const ejson::JObject& ejson_j, T& ejson_t);

void to_json(ejson::JObject& ejson_j, const T& ejson_t);

你可以像下面这样自己实现上面这两个函数来让自定义类型支持 FromJSONToJSON 函数。

struct student
{
   int         id;
   int         score;
   std::string name;
};

void to_json(ejson::JObject& ejson_j, const student& ejson_t)
{
   ejson_j.at("id").get_from(ejson_t.id);
   ejson_j.at("score").get_from(ejson_t.score);
   ejson_j.at("name").get_from(ejson_t.name);
}

void from_json(const ejson::JObject& ejson_j, student& ejson_t)
{
   ejson_j.at("id").get_to(ejson_t.id);
   ejson_j.at("score").get_to(ejson_t.score);
   ejson_j.at("name").get_to(ejson_t.name);
}

如果属性是 private 的,那么可以像下面这样侵入式的定义:

struct student
{
   friend void to_json(ejson::JObject& ejson_j, const student& ejson_t)
   {
      ejson_j.at("id").get_from(ejson_t.id);
      ejson_j.at("score").get_from(ejson_t.score);
      ejson_j.at("name").get_from(ejson_t.name);
   }
private:
   int         id;
   int         score;
   std::string name;
};

FROM_JSON_FUNC&FROM_JSON_FRIEND_FUNC

用于简化 from_json 函数定义的书写,例如前面对于 strudent 类型的 from_json 函数可以这样写:

struct student
{
   int         id;
   int         score;
   std::string name;
};
//非侵入式
FROM_JSON_FUNC(student, ejson_j, ejson_t) {
   ejson_j.at("id").get_to(ejson_t.id);
   ejson_j.at("score").get_to(ejson_t.score);
   ejson_j.at("name").get_to(ejson_t.name);
}

struct student
{
    //侵入式
   FROM_JSON_FRIEND_FUNC(student,ejson_j,ejson_t)
   {
      ejson_j.at("id").get_to(ejson_t.id);
      ejson_j.at("score").get_to(ejson_t.score);
      ejson_j.at("name").get_to(ejson_t.name);
   }
private:
   int         id;
   int         score;
   std::string name;
};

TO_JSON_FUNC&TO_JSON_FRIEND_FUNC

用于简化 to_json 函数定义的书写,例如前面对于 strudent 类型的 to_json 函数可以这样写:

struct student
{
   int         id;
   int         score;
   std::string name;
};
//非侵入式
TO_JSON_FUNC(student, ejson_j, ejson_t) {
   ejson_j.at("id").get_from(ejson_t.id);
   ejson_j.at("score").get_from(ejson_t.score);
   ejson_j.at("name").get_from(ejson_t.name);
}

struct student
{
    //侵入式
   TO_JSON_FRIEND_FUNC(student,ejson_j,ejson_t)
   {
      ejson_j.at("id").get_from(ejson_t.id);
      ejson_j.at("score").get_from(ejson_t.score);
      ejson_j.at("name").get_from(ejson_t.name);
   }
private:
   int         id;
   int         score;
   std::string name;
};

AUTO_GEN_NON_INTRUSIVE&AUTO_GEN_INTRUSIVE

这两个宏可以帮助你一键生成之前例子中的 to_jsonfrom_json 函数。

前面的代码可以替换为:

struct student
{
   int         id;
   int         score;
   std::string name;
};
//非侵入式
AUTO_GEN_NON_INTRUSIVE(student,id,score,name)
    
struct student
{
//侵入式
AUTO_GEN_INTRUSIVE(student,id,score,name)
    
private:
   int         id;
   int         score;
   std::string name;
};

ENABLE_JSON_COUT

自动生成对应类型的 operator<<(ostream&,T) 运算符重载,用于将对应类型支持 cout 打印出json格式。该宏可以为多个类型生成。

struct student
{
   int         id;
   int         score;
   std::string name;
};

struct info
{
   int         id;
   std::string msg;
};
//让对应类型支持json格式化
AUTO_GEN_NON_INTRUSIVE(student,id,score,name)
AUTO_GEN_NON_INTRUSIVE(info,id,msg)
    
//支持json格式cout打印
ENABLE_JSON_COUT(student,info)

FromJSON系列函数

参数说明

static JObject Parser::FromJSON(const str_t &content, bool skip_comment = false);

根据json字符串内容反序列化为JObject结构。

参数说明:

  • content:需要解析的json资源,这是一个string_view类型的参数,支持C风格字符串和 std::string
  • skip_comment:是否需要支持跳过注释,默认为false,未开启。

返回值:

  • 返回解析完的 JObject 类型。

template <class T>
static void Parser::FromJSON(string_view const &src, T &dst,bool skip_comment = false)

根据json字符串内容反序列化数据到 dst

参数说明:

  • src:需要解析的json资源,这是一个string_view类型的参数,支持C风格字符串和 std::string
  • dst:需要初始化的变量,可以是自定义类型。
  • skip_comment:是否需要支持跳过注释,默认为false,未开启。

static JObject& Parser::FromFile(string_view const &filename, bool skip_comment = false);

根据文件中的 json 数据获取 JObject&,这个JObject是thread_local变量,也就是每个线程共用一个JObject。所以请注意,当您调用此函数时,将更新这个共用的 JObject 的值。

参数说明:

  • filename :json文件路径。
  • skip_comment :是否需要支持跳过注释,默认为false,未开启。

返回值:

  • 同一个线程共用的JObject&。

static void Parser::FromFile(string_view const &filename, T &dst,bool skip_comment = false);

根据文件中的json数据设置 dst 的值。

参数说明:

  • filename :json文件路径。
  • dst :需要初始化的变量。
  • skip_comment :是否需要支持跳过注释,默认为false,未开启。

使用示例

#include <ejson/parser.h>
#include <iostream>
using namespace ejson;

struct Score
{
   double p;
};

struct student
{
   int         id{};
   std::string name;
   Score       score{};
};
//为Score类型自动生成to_json和from_json函数
AUTO_GEN_NON_INTRUSIVE(Score, p)
//为student类型自动生成to_json和from_json函数
AUTO_GEN_NON_INTRUSIVE(student, id, name, score)
//重载方便cout打印数据
ENABLE_JSON_COUT(Score,student)
int main(){
   const char *json1 =
     R"({"id":324,"name":"刘xx","score":{"p":2342343243242.124}})";
    student stu;
    //使用FromJSON初始化stu变量
       Parser::FromJSON(json1,stu);
    //使用FromFile初始化stu变量
    Parser::FromFile("json文件路径",stu);
    std::cout<<stu;
}

ToJSON系列函数

参数说明

template <class T>
static auto Parser::ToJSON(T &&src,const int indent = -1,
                        const char indent_char = ' ', bool is_esc = false) -> std::string;

将任意类型序列化为json字符串返回。

参数说明:

  • src :需要序列化为json字符串的数据。
  • indent :是否需要美化json输出,小于0表示美化,其余情况为美化时的缩进长度,默认不美化。
  • indent_char :美化时填入缩进的字符,默认为 ' '
  • is_esc :是否需要识别转义字符,默认不识别。

返回值:

  • json字符串。

template <class T>
static void ToFile(string_view const &filename, T const &src,
                   const int indent = -1, const char indent_char = ' ',
                      bool is_esc = false);

根据 src 中的数据序列化为json数据到文件中。

参数说明:

  • filename :需要写入的文件路径。
  • src :需要序列化的变量。
  • indent :是否需要美化json输出,小于0表示美化,其余情况为美化时的缩进长度,默认不美化。
  • indent_char :美化时填入缩进的字符,默认为 ' '
  • is_esc :是否需要识别转义字符,默认不识别。

static void ToFile(string_view const &filename, JObject const &src
                   const int indent = -1, const char indent_char = ' ',
                      bool is_esc = false)

将JObject中的数据转为json写入到文件中。

参数说明:

  • filename :需要写入的文件路径。
  • src :JObject变量。
  • indent :是否需要美化json输出,小于0表示美化,其余情况为美化时的缩进长度,默认不美化。
  • indent_char :美化时填入缩进的字符,默认为 ' '
  • is_esc :是否需要识别转义字符,默认不识别。

使用示例

#include <ejson/parser.h>
#include <iostream>
using namespace ejson;

struct Score
{
   double p;
};

struct student
{
   int         id{};
   std::string name;
   Score       score{};
};
//为Score类型自动生成to_json和from_json函数
AUTO_GEN_NON_INTRUSIVE(Score, p)
//为student类型自动生成to_json和from_json函数
AUTO_GEN_NON_INTRUSIVE(student, id, name, score)
//重载方便cout打印数据
ENABLE_JSON_COUT(Score,student)
int main(){
    student stu;
    stu.id = 324;
    stu.name = "刘xx";
    stu.score.p = 2342343243242.124;
    //使用ToJSON进行序列化
       auto json_data = Parser::ToJSON(stu);
    //使用ToFile将数据序列化到文件
    Parser::ToFile("文件路径",stu);
    std::cout<<stu;
}

JObject系列函数

JObject的构造函数

只需要清楚以下几点:

  1. JObject有无参构造,但是无参构造产生的JObject为null类型,无法进行正常使用。
  2. JObject的构造函数可以直接接受大部分类型,且包括自定义类型和部分stl容器。
  3. JObject本身不支持拷贝构造,只支持移动构造。

前面的 ToJSON API完全可以用下列方式替代,因为所有的序列化过程其实都是构造JObject来进行:

#include <ejson/parser.h>
#include <iostream>
using namespace ejson;

struct Score
{
   double p;
};

struct student
{
   int         id{};
   std::string name;
   Score       score{};
};
//为Score类型自动生成to_json和from_json函数
AUTO_GEN_NON_INTRUSIVE(Score, p)
//为student类型自动生成to_json和from_json函数
AUTO_GEN_NON_INTRUSIVE(student, id, name, score)
//重载方便cout打印数据
ENABLE_JSON_COUT(Score,student)
int main(){
    student stu;
    stu.id = 324;
    stu.name = "刘xx";
    stu.score.p = 2342343243242.124;
    //构造JObject并使用其成员函数
       auto json_data = JObject(stu).to_string();
    std::cout<<stu;
}

JObject的成员函数

Type JObject::type() const

返回当前JObject对象的类型,具体的类型有下列情况:

  • kNull :值为null类型
  • kBool:值为bool类型
  • kInt:值为整数类型
  • kDouble:值为浮点类型
  • kStr:值为字符串类型
  • kList:值为列表类型
  • kDict:值为字典类型(或叫做对象类型)

kDict

当你的JObject的类型为 kDict 时,下列成员函数可供使用:

bool JObject::has_key(const str_t& key)    const
  • 判断 JObject 中是否存在包含 key 的映射。
ObjectRef JObject::at(const str_t& key)    const
  • 根据key取出对应映射的value,value以 ObjectRef 类型的方式提供。

    而ObjectRef类型有这两个关键的成员函数:

    ObjectRef& JObject::ObjectRef::get_from(T&& src)

    
      从 `src` 中获取数据填充到 `JObject` 中,若 `src` 为自定义类型需要自定义 `to_json` 方法。
    
    * ```cpp
    ObjectRef& JObject::ObjectRef::get_from(T& dst)
    
    从 `JObject` 中获取数据填充到 `dst` 中,若 `dst` 为自定义类型需要自定义 `from_json` 方法。
    

kList

当JObject类型为 kList 时,下列成员函数可用:

void JObject::push_back(JObject item);
  • 插入一个值到 JObject 列表的尾部,可以插入任意类型,但都需要显式的转化为 JObject 类型,如 JObject(324234)
void JObject::pop_back()
  • 删除列表中末尾的值。

to_string
string JObject::to_string(
int    indent = -1,
char   indent_char = ' ',
bool   is_esc = false 
)const

序列化最终调用的API,将JObject序列化为json字符串返回。

参数说明:

  • indent :用于判断json解析是否需要美化,如果需要美化,则该值为缩进的长度。该值小于0时不进行美化,默认值为-1。
  • indent_char:缩进填入的字符,默认为 ' '
  • is_esc:是否需要对转义字符进行处理,默认不开启。

返回值:

  • 序列化后端json字符串。

static

为了方便快速创建 dict_t 类型和 list_t 类型的 JObject ,定义了下列静态函数:


static JObject JObject::Dict()

使用如下:

#include <ejson/parser.h>
#include <iostream>
using namespace ejson;

int main(){
    //构造JObject并使用其成员函数
       auto json = JObject::Dict();
    json.at("a").get_from("bc");
    json.at("d").get_from("ef");
    std::cout<<json.to_string();
}

static JObject JObject::List()

使用如下:

#include <ejson/parser.h>
#include <iostream>
#include <vector>
using namespace ejson;
struct custom_type{
    int id;
    std::vector<int> data;
};

//自动生成to_json和from_json函数
AUTO_GEN_NON_INTRUSIVE(custom_type, id,data)

int main(){
    //构造JObject并使用其成员函数
       auto json = JObject::List();
    json.push_back(JObject("abc"));
    custom_type v{1,{2,3,3}};
       json.push_back(JObject(v));
    std::cout<<json.to_string();
}

注意事项

在使用本库时需要注意几点:

  1. 本库不对你使用的字符编码进行验证,加入你使用 gbk 编码的json数据进行解析得到还是gbk编码的,所以这点需要注意,建议都使用utrf8编码。
  2. 本库只支持对 `` 和 " 的转义,其他的诸如 \u \b 等功能暂时不支持,我还是建议在json数据中不要存储二进制文件,如果需要存储二进制文件,后续的版本中可能会增base64编码的支持。
  3. 本库对所有字符串解析到 JObject 中的过程都是浅拷贝(只拷贝了指针),故如果需要使用原生 JObject 进行数据的存储需要额外的注意内存的所有权和生命周期,建议使用 JObject 进行短期的解析而不是长期的存储,在大多数情况下直接使用函数是最好的选择,后续可能会出一个深拷贝的 JObject 那样的结构才适合存储。
  4. 本库所有的错误均以异常抛出,好处在于可以模拟递归调用栈的栈信息进行打印,坏处当然是要写 try catch

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

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

相关文章

WireShark从入门到精通

一、什么是WireShark&#xff1f;Wireshark 是一个开源抓包工具或者叫网络嗅探器&#xff0c;用于分析网络流量和分析数据包。其实WireShark以前的名字不叫WireShark&#xff0c;以前都叫做Ethereal&#xff0c;于1998 年首次开发&#xff0c;直到 2006 年才改为 Wireshark。Wi…

powerdns 系列之二 PowerDNS Authoritative Server

PowerDNS Authoritative Server 权威服务器&#xff0c;直接查询数据库去尝试解析&#xff0c;数据库中若不存在此记录&#xff0c;则直接返回空结果。 os: centos 7.8.2003 pnds: 4.7.3 安装 pdns 安装 pdns auth yum install epel-release yum-plugin-priorities curl -o…

Alibaba微服务组件Nacos注册中心学习笔记

1. 什么是 Nacos 什么是 Nacos官方网站:什么是 Nacos 官方&#xff1a;一个更易于构建云原生应用的动态服务发现(Nacos Discovery )、服务配置(Nacos Config)和服务管理平台。 集 注册中心配置中心服务管理 平台 Nacos 的关键特性包括: 服务发现和服务健康监测 动态配置服…

Vue前端项目【尚品汇】

Vue前端项目【尚品汇】1. 说明2. 对项目创建3.结构4. 项目运行起来时&#xff0c;浏览器自动打开5.关闭ESLint校验功能1. 说明 学习完了Vue后并没有一个整体的项目&#xff0c;让我明白前端的工程化开发。因此在B站找了一个开源的项目进行练手&#xff0c;并在这里进行记录。 …

深度学习理论介绍

在机器学习理论介绍中说过&#xff0c;深度学习是机器学习的一种技术&#xff0c;下面我们具体来看下。 1、 定义 深度学习首先是一种机器学习。深度学习的基础&#xff0c;叫做神经网络&#xff0c;这本身就是一种机器学习算法。 深度神经网络&#xff08;Deep Neural Netwo…

产品小白如何从模仿开始学习Axure

​有一些小伙伴有这样的疑问&#xff0c;如果想学习产品经理的知识&#xff0c;但是从来对产品经理完全没有认识&#xff0c;该怎样去开始学习呢&#xff1f; 对于这种情况的小伙伴&#xff0c;有个建议是可以先从axure的学习入手。 那么问题又来了&#xff0c;应该如何开始axu…

只靠石墨烯电池,撑不起两轮电动车的“高端梦”

文|智能相对论作者|陈明涛在两轮电动车上&#xff0c;“石墨烯”电池已经成为品牌标配。雅迪、爱玛、台铃等都有搭载石墨烯电池的车型&#xff0c;雅迪的TTFAR石墨烯电池已经迭代到第4代&#xff0c;而第3代Plus电池搭载在冠能3 E9 PRO上完成量产上市&#xff1b;爱玛、台铃都和…

(侯捷C++)1.1面向对象高级编程(上)

一.C编程简介 1.C历史 B语言 → C语言 → C语言&#xff08;new C → C with Class → C&#xff09; 面向对象的语言&#xff1a;C、Java、C# 2.C演化 C 98(1.0) → C 03 → C 11(2.0) → C 14 Class两个经典分类&#xff1a;有指针&#xff08;Complex类为例&#xff0…

python经典例题

目录标题1&#xff0c;计算数字的阶乘2&#xff0c;区间内的所有素数3&#xff0c;求前n个数字的平方和4&#xff0c;移除列表中的多个元素5&#xff0c;列表去重6&#xff0c;对简单列表元素排序7&#xff0c;对复杂列表元素排序8&#xff0c;读取成绩问卷并排序-读取文件&…

在MySQL5基础上安装MySQL8解压版安装服务指向MySQL5的问题

Windows10在MySQL5解压版基础上安装MySQL8解压版后服务启动失败 1. 遇到的问题&#xff1a; MySQL8目录下没有生成data文件夹启动服务失败&#xff0c;右键服务属性&#xff0c;可执行文件的路径竟然是MySQL目录下的日志文件在MySQL5目录下&#xff0c;日志内容会提示读取了M…

【C语言】动态内存实现通讯录(malloc,calloc,realloc)

目录 一、为什么要存在动态内存开辟 二、动态内存函数 2.1 malloc 2.1.1 函数介绍 2.1.2 函数的使用 2.1.3 free 2.2 calloc 2.2.1 函数介绍 2.2.2 函数的使用 2.3 realloc 2.3.1 函数的介绍 2.3.2 函数的使用 三、改进通讯录 3.1 初始化 3.2 增加信息 3.3 排序&am…

R语言贝叶斯方法在生态环境领域中的高阶技术应用

贝叶斯统计已经被广泛应用到物理学、生态学、心理学、计算机、哲学等各个学术领域&#xff0c;其火爆程度已经跨越了学术圈&#xff0c;如促使其自成统计江湖一派的贝叶斯定理在热播美剧《The Big Bang Theory》中都要秀一把。贝叶斯统计学即贝叶斯学派是一门基本思想与传统基于…

从正则表达式到NFA

一、正则表达式到NFA的基本结构 1. 我们先回顾一下 RE 的三个基本操作&#xff1a; 可选&#xff08;alternative&#xff09;&#xff1a;对于给定的两个正则表达式 M 和 N&#xff0c;选择操作符&#xff08; | &#xff09;形成一个新的正则表达式 M|N &#xff0c;如果一…

28.Isaac教程--使用Sight的远程操纵杆

使用Sight的远程操纵杆 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录使用Sight的远程操纵杆设置 Isaac 应用程序以使用虚拟游戏手柄小部件关于虚拟游戏手柄小部件使用虚拟游戏手柄小部件运作模式手柄模式鼠标模式键盘模式控制机器人运…

设计模式_行为型模式 -《责任链模式》

设计模式_行为型模式 -《责任链模式》 笔记整理自 黑马程序员Java设计模式详解&#xff0c; 23种Java设计模式&#xff08;图解框架源码分析实战&#xff09; 概述 在现实生活中&#xff0c;常常会出现这样的事例&#xff1a;一个请求有多个对象可以处理&#xff0c;但每个对象…

windows最小化安装mysql8

第一步&#xff1a;下载 从官网下载&#xff0c;https://dev.mysql.com/downloads/mysql 第二步&#xff1a;安装 下载后解压到目录即可。 我这里解压到D:\MYSQL\mysql-8.0.32-winx64\mysql-8.0.32-winx64 第三步&#xff1a;初始化配置 1、添加系统变量 在系统变量PATH后…

【C++】从0到1入门C++编程学习笔记 - 基础入门篇:数组

文章目录一、概述二、一维数组2.1 一维数组定义方式2.2 一维数组数组名2.3 C 实现冒泡排序三、二维数组3.1 二维数组定义方式3.2 二维数组数组名3.3 二维数组应用案例一、概述 所谓数组&#xff0c;就是一个集合&#xff0c;里面存放了相同类型的数据元素 特点1&#xff1a;数…

(15)go-micro微服务main.go开发

文章目录一 导包二 配置中心三 注册中心四 zap日志初始化五 初始化Mysql数据库六 初始化Redis连接七 注册服务八 初始化服务九 注册 handle十 启动服务十一 main.go全部代码十二 最后一 导包 import (micro2 "account/common/micro""account/config/logger&quo…

day17|530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#xff1a;1 示例 2&#xff1…

Redis持久化——AOF机制详解

在运行情况下&#xff0c;Redis 以数据结构的形式将数据维持在内存中&#xff0c;为了让这些数据在 Redis 重启之后仍然可用&#xff0c;需要将数据写入持久存储 持久化是指将数据写入持久存储&#xff0c;例如固态磁盘(SSD) Redis 提供了一系列持久化选项。这些包括&#xff1…