ProtoBuf-反射原理与使用

2023-11-15

前言

所谓反射机制,就是能够在运行时知道任意类的所有属性方法,能够调用任意对象的任意方法和属性。这种动态获取的信息以及动态调用对象方向的功能称为反射机制。
如果用一句话来总结反射实现的关键,可概括为获取系统元信息

元信息:即系统自描述信息,用于描述系统本身。举例来讲,即系统有哪些类?类中有哪些字段、哪些方法?字段属于什么类型、方法又有怎样的参数和返回值?…

不像Jave,python等语言,C++本身没有反射机制,但使用protobuf时,通过proto文件产生响应的messageservice,可以提供反射机制,运行时可以通过proto获取任意message和任意service的属性和方法,并调用、修改和设置。

相关应用场景

Protobuf是一种常见的数据序列化方式,常常用于后台微服务之间传递数据。
在处理ProtoBuf Message数据时,经常需要根据一个输入的字符串,读取Message中对应属性取值,并修改取值。;或者给定一个ProtoBuf对象,如何自动遍历该对象的所有字段,将其转换为json格式,或者在Bigtable中根据pb对象的字段自动写填写列名和对应的value。

在写代码时,经常会遇到一些丑陋的、圈复杂度较高、较难维护的关于 PB 的使用代码:

  1. 对字段的必填校验硬编码在代码中:如果需要变更校验规则,则需要修改代码
  2. 一个字段一个 if 校验,复杂度较高:对传进来的字段每个字段都进行多种规则校验,例如长度,XSS,正则校验等。
  3. 想要获取 PB 中所有的非空字段,形成一个 map<string,string>,需要大量的 if 判断和重复代码;
  4. 后台服务间传递数据,由于模块由不同的人开发,导致相同字段的命名不一样,从一个 PB 中挑选一部分内容到另外一个 PB 中,需要大量的 GET 和 SET 代码。

以上都痛点可以通过反射机制解决。


一、ProtoBuf 反射原理概述

  1. 通过Message获取单个字段的FieldDescriptor
  2. 通过Message获取其Reflection
  3. 通过Reflection来操作FieldDescriptor,从而动态获取或修改单个字段

1、获取message和service的属性和方法

protobuf通过Descriptor获取任意message或service的属性和方法,Descriptor主要包括了一下几种类型:

描述符 方法
FileDescriptor 获取Proto文件中的DescriptorServiceDescriptor
Descriptor 获取类message属性和方法,包括FieldDescriptorEnumDescriptor
FieldDescriptor 获取message中各个字段的类型、标签、名称
EnumDescriptor 获取Enum中的各个字段名称、值等
ServiceDescriptor 获取service中的MethodDescriptor
MethodDescriptor 获取各个RPC中的request、response名称

在类 Descriptor 中 可以获取自身信息的函数

const std::string & name() const; // 获取message自身名字
int field_count() const; // 获取该message中有多少字段
const FileDescriptor* file() const; // The .proto file in which this message type was defined. Never nullptr.

在类 Descriptor 中,可以通过如下方法获取类 FieldDescriptor

const FieldDescriptor* field(int index) const; // 根据定义顺序索引获取,即从0开始到最大定义的条目
const FieldDescriptor* FindFieldByNumber(int number) const; // 根据定义的message里面的顺序值获取(option string name=3,3即为number)
const FieldDescriptor* FindFieldByName(const string& name) const; // 根据field name获取

也就是说,如果能够获取到proto文件的FileDescriptor,就能获取proto文件中的所有的内容。那么如何获取proto文件的FileDescriptor呢?protobuf提供多种方法。

1.1 使用protoc将proto文件生成.h和.cc文件

这种方法直接根据生成的类来获取响应的FileDescriptor,比如现在有test.proto文件,那么可以通过DescriptorPool::generated_pool()获取到其FileDescriptor

const FileDescriptor* fileDescriptor = DescriptorPool::generated_pool()->FindFileByName(file);

并且对于任意的message和service都可以根据其名称,通过DescriptorPool对应的Descriptor和ServiceDescriptor

1.2 只使用proto文件,不使用protoc进行编译

这种情况需要手动解析proto文件,再获取FileDescriptor。protobuf提供了响应的解析器compiler,通过compoiler可以方便的获取proto文件的FileDescriptor

const FileDescriptor* GetFileDescriptorFromProtoFile(const std::string &protoRoot, const std::string &protoFile){
    compiler::DeskSourceTree sourceTree;
    sourceTree.MapPath("", protoRoot);
    FileErrorCollector errorCollector;
    compiler::Importer importer(&sourceTree, &errorCollector);
    return importer.Import(protoFile);
}

1.3 非 .proto 文件 ,转换成.proto

  1. 可以从远程读取,如将数据与数据元信息一同进行 protobuf 编码并传输:
message Req {
  optional string proto_file = 1;
  optional string data = 2;
}
  1. 从 Json 或其它格式数据中转换而来

无论 .proto 文件来源于何处,我们都需要对其做进一步的处理和注册,将其解析成内存对象,并构建其与实例的映射,同时也要计算每个字段的内存偏移。可总结出如下步骤:

  1. 提供 .proto (范指 ProtoBuf Message 语法描述的元信息)
  2. 解析 .proto 构建 FileDescriptorFieldDescriptor 等,即 .proto 对应的内存模型(对象)
  3. 之后每创建一个实例,就将其存到实例工厂相应的实例池
  4. 将 Descriptor 和 instance 的映射维护到表中备查
  5. 通过 Descriptor 可查到相应的 instance,又由于了解 instance 中字段类型(FieldDescriptor),所以知道字段的内存偏移,那么就可以访问或修改字段的值

2、调用message的属性和方法

如下是通过字符串type_name调用message的属性和方法的流程图;
Person是自定义的pb类型,继承自Message. MessageLite作为Message基类,更加轻量级一些。
通过Descriptor能获得所需消息的Message* 指针。Message class 定义了 New() 虚函数,用来返回本对象的一份新实体,具体流程如下:

  1. 通过 DescriptorPool 的 FindMessageTypeByName 获得了元信息 Descriptor
  2. 根据 MessageFactory 实例工厂Descriptor获得了Message的默认实例Message*指针 default instance
  3. 通过new()构造一个可用消息对象

2.1根据type name反射自动创建实例

通过字符串"person" 创建新的 person message对象。线程安全

 // 先获得类型的Descriptor .
auto descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("Person");
//利用Descriptor拿到类型注册的instance. 这个是不可修改的.
auto prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
 // 构造一个可用的消息.
auto instance = prototype->New(); //创建新的 person message对象。

第一步我们通过 DescriptorPool 的 FindMessageTypeByName 获得了元信息 Descriptor。

DescriptorPool 为元信息池,对外提供了诸如 FindServiceByNameFindMessageTypeByName 等各类接口以便外部查询所需的Service或者Message元信息。当 DescriptorPool 不存在时需要查询的元信息时,将进一步到 DescriptorDatabase 中去查找。 DescriptorDatabase 可从 硬编码磁盘中查询对应名称的 .proto 文件内容,解析后返回查询需要的元信息。

不难看出,DescriptorPool 和 DescriptorDatabase 通过缓存机制提高了反射运行效率。DescriptorDatabase 从磁盘中读取 .proto 内容并解析成 Descriptor 并不常用,实际上我们在使用 protoc 生成 xxx.pb.cc 和 xxx.pb.h 文件时,其中不仅仅包含了读写数据的接口,还包含了 .proto 文件内容。阅读任意一个 xxx.pb.cc 的内容,你可以看到如下类似代码

static void AddDescriptorsImpl() {
  InitDefaults();

  // .proto 内容
  static const char descriptor[] GOOGLE_PROTOBUF_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = {
      "\n\022single_int32.proto\"\035\n\010Example1\022\021\n\010int3"
      "2Val\030\232\005 \001(\005\" \n\010Example2\022\024\n\010int32Val\030\377\377\377\377"
      "\001 \003(\005b\006proto3"
  };

  // 注册 descriptor
  ::google::protobuf::DescriptorPool::InternalAddGeneratedFile(
      descriptor, 93);

  // 注册 instance
  ::google::protobuf::MessageFactory::InternalRegisterGeneratedFile(
    "single_int32.proto", &protobuf_RegisterTypes);
}

其中 descriptor 数组存储的便是 .proto 内容。这里当然不是简单的存储原始文本字符串,而是经过了 SerializeToString 序列化处理,而后将结果以 硬编码的形式保存在 xxx.pb.cc 中,真是充分利用了自己的高效编码能力。

硬编码的 .proto 元信息内容将以懒加载的方式(被调用时才触发)被 DescriptorDatabase 加载、解析,并 缓存到 DescriptorPool 中。

3、通过实例instance 的反射接口reflection对其pb字段读写

Reflection主要提供了动态读写 message对象字段的接口,对message对象的自动读写主要通过该类完成。

每种数据类型,Reflection都提供了一个单独的接口Get、Set)用于读写字段对应的值
例如对 int32、int64的读操作:输入参数为MessageFieldDescriptor*

  virtual int32  GetInt32 (const Message& message,const FieldDescriptor* field) const = 0;
  virtual int64  GetInt64 (const Message& message, const FieldDescriptor* field) const = 0;

特殊的,对于枚举和嵌套的message:

virtual const EnumValueDescriptor* GetEnum( const Message& message, const FieldDescriptor* field) const = 0;
virtual const Message& GetMessage(const Message& message,
                                    const FieldDescriptor* field,
                                    MessageFactory* factory = NULL) const = 0;

对于写操作也是类似的接口,例如SetInt32/SetInt64/SetEnum

写单个字段的函数如下:

void SetInt32(Message * message, const FieldDescriptor * field, int32 value) const
 
void SetString(Message * message, const FieldDescriptor * field, std::string value) const

获取重复字段的函数如下:

int32 GetRepeatedInt32(const Message & message, const FieldDescriptor * field, int index) const

std::string GetRepeatedString(const Message & message, const FieldDescriptor * field, int index) const

const Message & GetRepeatedMessage(const Message & message, const FieldDescriptor * field, int index) const

获取反射Reflection接口,并且值写入的代码如下:线程安全

auto reflecter = instance.GetReflection();//2.1通过字符串"Person"取得的实例instance
auto field = descriptor->FindFieldByName("name"); // Person这个Message 中有name字段
reflecter->SetString(&instance, field, "小明") ; //反射来设置name字段

反射机制

二、反射 例子

1、serialize_message

serialize_message遍历提取message中所有字段以及对应的值,序列化到string中。主要思路就是:

  1. Descriptor得到每个字段的描述符FieldDescriptor:字段名、字段的cpp类型。
  2. 通过ReflectionGetXXX接口获取对应的value

FieldDescriptor 介绍
类 FieldDescriptor 的作用主要是对 Message 中单个字段进行描述,包括字段名、字段属性、原始的 field 字段等。

其获取获取自身信息的函数:

const std::string & name() const; // Name of this field within the message.
const std::string & lowercase_name() const; // Same as name() except converted to lower-case.
const std::string & camelcase_name() const; // Same as name() except converted to camel-case.
CppType cpp_type() const; //C++ type of this field.

其中cpp_type()函数是来获取该字段什么类型的,在 protobuf 中,类型的类目如下:

enum FieldDescriptor::Type {
  TYPE_DOUBLE = = 1,
  TYPE_FLOAT = = 2,
  TYPE_INT64 = = 3,
  TYPE_UINT64 = = 4,
  TYPE_INT32 = = 5,
  TYPE_FIXED64 = = 6,
  TYPE_FIXED32 = = 7,
  TYPE_BOOL = = 8,
  TYPE_STRING = = 9,
  TYPE_GROUP = = 10,
  TYPE_MESSAGE = = 11,
  TYPE_BYTES = = 12,
  TYPE_UINT32 = = 13,
  TYPE_ENUM = = 14,
  TYPE_SFIXED32 = = 15,
  TYPE_SFIXED64 = = 16,
  TYPE_SINT32 = = 17,
  TYPE_SINT64 = = 18,
  MAX_TYPE = = 18
}

serialize_message遍历提取message中****所有字段以及对应的值代码如下:

void serialize_message(const google::protobuf::Message& message, std::string* serialized_string) {
    //获取描述Descriptor*和反射Reflection*
    const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
    const google::protobuf::Reflection* reflection = message.GetReflection();
     
    //遍历消息的所有字段  
    for (int i = 0; i < descriptor->field_count(); ++i) {
        // 获得单个字段的描述FieldDescriptor*
        const google::protobuf::FieldDescriptor* field = descriptor->field(i);
        bool has_field = reflection->HasField(message, field);
        
        if (has_field) {
            //arrays not supported
            assert(!field->is_repeated());
            switch (field->cpp_type()) {
            //宏定义 CASE_FIELD_TYPE(cpptype, method, valuetype) 写case
#define CASE_FIELD_TYPE(cpptype, method, valuetype)\
                case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
                    valuetype value = reflection->Get##method(message, field);\
                    int wsize = field->name().size();\
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
                    serialized_string->append(field->name().c_str(), field->name().size());\
                    wsize = sizeof(value);\
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
                    serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));\
                    break;\
                }

                // 利用宏定义 CASE_FIELD_TYPE 写所有简单数据类型的case
                CASE_FIELD_TYPE(INT32, Int32, int);
                CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
                CASE_FIELD_TYPE(FLOAT, Float, float);
                CASE_FIELD_TYPE(DOUBLE, Double, double);
                CASE_FIELD_TYPE(BOOL, Bool, bool);
                CASE_FIELD_TYPE(INT64, Int64, int64_t);
                CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
#undef CASE_FIELD_TYPE
                case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
                    int value = reflection->GetEnum(message, field)->number();
                    int wsize = field->name().size();
                    //写入name占用字节数
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    //写入name
                    serialized_string->append(field->name().c_str(), field->name().size());
                    wsize = sizeof(value);
                    //写入value占用字节数
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    //写入value
                    serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
                    break;
                }
                case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
                    std::string value = reflection->GetString(message, field);
                    int wsize = field->name().size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(field->name().c_str(), field->name().size());
                    wsize = value.size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(value.c_str(), value.size());
                    break;
                }
                //递归 序列化 嵌套Message
                case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                    std::string value;
                    int wsize = field->name().size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(field->name().c_str(), field->name().size());
                    const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
                    serialize_message(submessage, &value);
                    wsize = value.size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(value.c_str(), value.size());
                    break;
                }
            }
        }
    }
}

2、parse_message

parse_message通过读取field/value,还原message对象。
主要思路跟serialize_message很像,通过Descriptor得到每个字段的描述符FieldDescriptor,通过Reflection的SetX填充message。

void parse_message(const std::string& serialized_string, google::protobuf::Message* message) {
    // 通过传入的Message* 获得 Descriptor* 和Reflection* 
    const google::protobuf::Descriptor* descriptor = message->GetDescriptor();
    const google::protobuf::Reflection* reflection = message->GetReflection();
    // 字段名字 和FieldDescriptor*字段的 映射
    std::map<std::string, const google::protobuf::FieldDescriptor*> field_map;
    
    for (int i = 0; i < descriptor->field_count(); ++i) {
        const google::protobuf::FieldDescriptor* field = descriptor->field(i);
        field_map[field->name()] = field;
    }
    const google::protobuf::FieldDescriptor* field = NULL;
    size_t pos = 0;
    //解析 字符串,填写字段值,
    while (pos < serialized_string.size()) {
        int name_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
        pos += sizeof(int);
        
        std::string name = serialized_string.substr(pos, name_size);
        pos += name_size;
        
        int value_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
        pos += sizeof(int);
   
        std::string value = serialized_string.substr(pos, value_size);
        pos += value_size;
        //通过name 从map中获取FieldDescriptor*
        std::map<std::string, const google::protobuf::FieldDescriptor*>::iterator iter =
            field_map.find(name);
        if (iter == field_map.end()) {
            fprintf(stderr, "no field found.\n");
            continue;
        } else {
            field = iter->second;
        }
        assert(!field->is_repeated());
        switch (field->cpp_type()) {
        // 利用宏定义 CASE_FIELD_TYPE 写所有简单数据类型的case
#define CASE_FIELD_TYPE(cpptype, method, valuetype)\
            case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: {\
                reflection->Set##method(\
                        message,\
                        field,\
                        *(reinterpret_cast<const valuetype*>(value.c_str())));\
                std::cout << field->name() << "\t" << *(reinterpret_cast<const valuetype*>(value.c_str())) << std::endl;\
                break;\
            }
            CASE_FIELD_TYPE(INT32, Int32, int);
            CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
            CASE_FIELD_TYPE(FLOAT, Float, float);
            CASE_FIELD_TYPE(DOUBLE, Double, double);
            CASE_FIELD_TYPE(BOOL, Bool, bool);
            CASE_FIELD_TYPE(INT64, Int64, int64_t);
            CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
#undef CASE_FIELD_TYPE

            case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
                const google::protobuf::EnumValueDescriptor* enum_value_descriptor =
                    field->enum_type()->FindValueByNumber(*(reinterpret_cast<const int*>(value.c_str())));
                reflection->SetEnum(message, field, enum_value_descriptor);
                std::cout << field->name() << "\t" << *(reinterpret_cast<const int*>(value.c_str())) << std::endl;
                break;
            }
            
            case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
                reflection->SetString(message, field, value);
                std::cout << field->name() << "\t" << value << std::endl;
                break;
            }
            //递归解析 嵌套message字符串
            case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                google::protobuf::Message* submessage = reflection->MutableMessage(message, field);
                parse_message(value, submessage);
                break;
            }
            default: {
                break;
            }
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ProtoBuf-反射原理与使用 的相关文章

随机推荐

  • 神州三层交换机IPV6各种路由配置

    一 基础配置 SW1 CS6200 28X EI gt ena CS6200 28X EI conf CS6200 28X EI config host SW1 SW1 config ipv6 enable SW1 config vlan
  • 关系模型知识点总结(3)—— 关系操作中的关系代数(含题目及详细分析)

    关系代数 一 前言 二 概述 三 传统的集合运算符 1 概述 2 并 3 交 4 差 5 笛卡儿积 四 专门的关系运算 1 概述 2 记号 3 象集 4 选择 5 投影 6 连接 等值连接 自然连接 7 悬浮元组 外连接 左外连接 右外连接
  • 托管和非托管的区别

    NET Framework 是一种新的计算平台 它简化了在高度分布式 Internet 环境中的应用程序开发 NET Framework 旨在实现下列目标 提供一个一致的面向对象的编程环境 而无论对象代码是在本地存储和执行 还是在本地执行但
  • AIX系统修改系统时间

    linux下用date s 20131215 09 02 25 把时间设为2013年12月15日9点2分25秒 而aix呢 它不认 s这个参数 date n mmddHHMMYY mm表示月分 dd表示日期 HH表示小时 MM表示分钟 YY
  • 关于最近面试的通过2个offer然后被刷

    一个是面试通关了 薪资也谈好了 然后发offer 时候跟我说跟别人名字搞错了 浪费我我的时间 还有一个人面试通过 也给我发了面试通过的 也给我发了offer工资也谈拢了 说需要一周的审批 然后后面问我工作年限 说我不够工作年限 要求之前多少
  • qq讨论组显示连接服务器异常,QQ讨论组出现大面积故障 腾讯回应:因服务器异常 已紧急修复...

    原标题 QQ讨论组出现大面积故障 腾讯回应 因服务器异常 已紧急修复 TechWeb报道 8月25日消息 今天上午 大量网友反映称QQ讨论组功能出现Bug 具体症状为 在讨论组内发送一条信息就会被自动创建一个新的讨论组 而且历史消息记录全部
  • 【满分】【华为OD机试真题2023B卷 JAVA&JS】最长公共后缀

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 最长公共后缀 知识点排序 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 编写一个函数来查找字符串数组中的最长公共后缀 如果不存在公共后缀 返回固定字符串 Zero 补
  • matplot 画条形图

    import matplotlib pyplot as plt import numpy as np import pandas as pd plt show America New York 1251 Unknown 521 Americ
  • 18724 二叉树的遍历运算

    先序遍历的顺序是 根左右 中序遍历的顺序是 左根右 l1表示先序遍历的左边界 r1表示先序遍历的右边界 l2表示中序遍历的左边界 r1表示中序遍历的右边界 下面以题目的输入样例为例做分析 输入样例 abcde bcade 当i到达最后落点时
  • 毕设—基于树莓派的家居环境智能监测系统设计与实践

    一 资料查找工具 英文文献 Sci Hub Academic Navigation Site To remove all barriers in the way of science 中文文献 VPN书童图书馆 知网免费下载知网免费入口论文
  • Dropdown 下拉框(el-dropdown-menu)内容过长显示‘...’ 鼠标悬浮显示全部

    问题描述 使用element的Dropdown 下拉框 el dropdown menu 内容过长显示 鼠标悬浮显示全部 有时候内容太长导致文字换行与其它文字折叠展示 如下图所示 解决方案 直接上代码
  • PVE虚拟化平台之安装iStoreOS软路由系统

    PVE虚拟化平台之安装iStoreOS软路由系统 一 iStoreOS介绍 二 登录PVE平台检查系统状态 三 创建虚拟机 1 虚拟机常规设置 2 操作系统配置 3 系统配置 4 磁盘配置 5 CPU配置 6 内存设置 7 网络设置 8 确
  • plt.subplots()

    首先一幅Matplotlib的图像组成部分介绍 在matplotlib中 整个图像为一个Figure对象 在Figure对象中可以包含一个或者多个Axes对象 每个Axes ax 对象都是一个拥有自己坐标系统的绘图区域 所属关系如下 sub
  • Python操作ElasticSearch条件查询

    一 基本操作 1 列表元素之一查询 如 terminalType pc mobile 正确用法 GET http 0 0 0 0 8200 amis action data data search size 10000 查询条件 Ps 数组
  • 【转】内存数据库、关系型数据库和非关系型数据库

    内存数据库 关系型数据库和非关系型数据库 一 内存数据库 关系型数据库和非关系型数据库 1 个人观点 二 内存数据库 Redis MongoDb SQLite Oracle等 三 Raft分布式协议 四 Redis出现宕机 如何保证数据不丢
  • python极限学习机ELM做一个简单的分类

    最近事太多 只能下班后挤时间学习 缓慢更新 华丽的分割线 极限学习机是我们实验室的元老了 是一种单隐层前馈神经网络 SLFN 学习算法 这种算法只需要设置网络的隐层节点个数 执行过程中不需要调整网络的输入权值以及隐元的偏置 并且产生唯一的最
  • 哈希表查找——成功和不成功时的平均查找长度

    哈希表查找 成功和不成功时的平均查找长度 以下求解过程是按照 计算机统考的计算方法 不同的老师 教材在 处理冲突 上可能会有不同的方法 所以最主要的是掌握原理即可 对于考研的朋友最好掌握统考真题的解题方法 题目 例子 2010年全国硕士研究
  • 13.Python列索引值

    1 基础Python vi 6csv reader column by index py encoding utf 8 usr bin env python3 import csv import sys input file sys arg
  • mmsegmentation V0.27.0环境搭建(一)

    1 官网 2 兼容的MMSegmentation和MMCV版本如下 请安装正确版本的MMCV 以避免安装问题 3 Installation Linux系统环境安装 1 创建虚拟环境并安装pytorch Step 1 Create a con
  • ProtoBuf-反射原理与使用

    文章目录 前言 相关应用场景 一 ProtoBuf 反射原理概述 1 获取message和service的属性和方法 1 1 使用protoc将proto文件生成 h和 cc文件 1 2 只使用proto文件 不使用protoc进行编译 1