8.4.tensorRT高级(3)封装系列-infer推理封装,输入输出tensor的关联

目录

    • 前言
    • 1. infer封装
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习 tensorRT 高级-infer推理封装,输入输出tensor的关联

课程大纲可看下面的思维导图

在这里插入图片描述

1. infer封装

这节我们学习对 infer 的封装

对 infer 进行封装,有了基本组件,可以拼接一个完整的推理器,而且该推理器的思想可以应用到很多框架作为底层,并不只限制于 tensorRT,还可以是 rknn、openvino 等

我们先来看代码

trt-infer.hpp

#ifndef TRT_INFER_HPP
#define TRT_INFER_HPP#include <string>
#include <memory>
#include <vector>
#include <map>
#include "trt-tensor.hpp"namespace TRT {class Infer {public:virtual void     forward(bool sync = true) = 0;virtual int      get_max_batch_size() = 0;virtual void     set_stream(CUStream stream) = 0;virtual CUStream get_stream() = 0;virtual void     synchronize() = 0;virtual size_t   get_device_memory_size() = 0;virtual std::shared_ptr<MixMemory> get_workspace() = 0;virtual std::shared_ptr<Tensor>    input (int index = 0) = 0;virtual std::shared_ptr<Tensor>    output(int index = 0) = 0;virtual std::shared_ptr<Tensor>    tensor(const std::string& name) = 0;virtual std::string get_input_name (int index = 0) = 0;virtual std::string get_output_name(int index = 0) = 0;virtual bool is_output_name(const std::string& name) = 0;virtual bool is_input_name (const std::string& name) = 0;virtual int  num_output() = 0;virtual int  num_input() = 0;virtual void print() = 0;virtual int  device() = 0;virtual void set_input (int index, std::shared_ptr<Tensor> tensor) = 0;virtual void set_output(int index, std::shared_ptr<Tensor> tensor) = 0;virtual std::shared_ptr<std::vector<uint8_t>> serial_engine() = 0;};int get_device_count();int get_device();void set_device(int device_id);std::shared_ptr<Infer> load_infer_from_memory(const void* pdata, size_t size);std::shared_ptr<Infer> load_infer(const std::string& file);bool init_nv_plugins();};	//TRTInfer#endif //TRT_INFER_HPP

rtr-infer.cpp

#include "trt-infer.hpp"
#include <cuda_runtime.h>
#include <algorithm>
#include <fstream>
#include <NvInfer.h>
#include <NvInferPlugin.h>
#include "cuda-tools.hpp"
#include "simple-logger.hpp"using namespace nvinfer1;
using namespace std;class Logger : public ILogger {
public:virtual void log(Severity severity, const char* msg) noexcept override {if (severity == Severity::kINTERNAL_ERROR) {INFOE("NVInfer INTERNAL_ERROR: %s", msg);abort();}else if (severity == Severity::kERROR) {INFOE("NVInfer: %s", msg);}else  if (severity == Severity::kWARNING) {INFOW("NVInfer: %s", msg);}else  if (severity == Severity::kINFO) {INFOD("NVInfer: %s", msg);}else {INFOD("%s", msg);}}
};
static Logger gLogger;namespace TRT {template<typename _T>shared_ptr<_T> make_nvshared(_T* ptr){return shared_ptr<_T>(ptr, [](_T* p){p->destroy();});}static std::vector<uint8_t> load_file(const string& file){ifstream in(file, ios::in | ios::binary);if (!in.is_open())return {};in.seekg(0, ios::end);size_t length = in.tellg();std::vector<uint8_t> data;if (length > 0){in.seekg(0, ios::beg);data.resize(length);in.read((char*)&data[0], length);}in.close();return data;}class EngineContext {public:virtual ~EngineContext() { destroy(); }void set_stream(CUStream stream){if(owner_stream_){if (stream_) {cudaStreamDestroy(stream_);}owner_stream_ = false;}stream_ = stream;}bool build_model(const void* pdata, size_t size) {destroy();if(pdata == nullptr || size == 0)return false;owner_stream_ = true;checkRuntime(cudaStreamCreate(&stream_));if(stream_ == nullptr)return false;runtime_ = make_nvshared(createInferRuntime(gLogger));if (runtime_ == nullptr)return false;engine_ = make_nvshared(runtime_->deserializeCudaEngine(pdata, size, nullptr));if (engine_ == nullptr)return false;//runtime_->setDLACore(0);context_ = make_nvshared(engine_->createExecutionContext());return context_ != nullptr;}private:void destroy() {context_.reset();engine_.reset();runtime_.reset();if(owner_stream_){if (stream_) {cudaStreamDestroy(stream_);}}stream_ = nullptr;}public:cudaStream_t stream_ = nullptr;bool owner_stream_ = false;shared_ptr<IExecutionContext> context_;shared_ptr<ICudaEngine> engine_;shared_ptr<IRuntime> runtime_ = nullptr;};class InferImpl : public Infer {public:virtual ~InferImpl();virtual bool load(const std::string& file);virtual bool load_from_memory(const void* pdata, size_t size);virtual void destroy();virtual void forward(bool sync) override;virtual int get_max_batch_size() override;virtual CUStream get_stream() override;virtual void set_stream(CUStream stream) override;virtual void synchronize() override;virtual size_t get_device_memory_size() override;virtual std::shared_ptr<MixMemory> get_workspace() override;virtual std::shared_ptr<Tensor> input(int index = 0) override;virtual std::string get_input_name(int index = 0) override;virtual std::shared_ptr<Tensor> output(int index = 0) override;virtual std::string get_output_name(int index = 0) override;virtual std::shared_ptr<Tensor> tensor(const std::string& name) override;virtual bool is_output_name(const std::string& name) override;virtual bool is_input_name(const std::string& name) override;virtual void set_input (int index, std::shared_ptr<Tensor> tensor) override;virtual void set_output(int index, std::shared_ptr<Tensor> tensor) override;virtual std::shared_ptr<std::vector<uint8_t>> serial_engine() override;virtual void print() override;virtual int num_output();virtual int num_input();virtual int device() override;private:void build_engine_input_and_outputs_mapper();private:std::vector<std::shared_ptr<Tensor>> inputs_;std::vector<std::shared_ptr<Tensor>> outputs_;std::vector<int> inputs_map_to_ordered_index_;std::vector<int> outputs_map_to_ordered_index_;std::vector<std::string> inputs_name_;std::vector<std::string> outputs_name_;std::vector<std::shared_ptr<Tensor>> orderdBlobs_;std::map<std::string, int> blobsNameMapper_;std::shared_ptr<EngineContext> context_;std::vector<void*> bindingsPtr_;std::shared_ptr<MixMemory> workspace_;int device_ = 0;};InferImpl::~InferImpl(){destroy();}void InferImpl::destroy() {int old_device = 0;checkRuntime(cudaGetDevice(&old_device));checkRuntime(cudaSetDevice(device_));this->context_.reset();this->blobsNameMapper_.clear();this->outputs_.clear();this->inputs_.clear();this->inputs_name_.clear();this->outputs_name_.clear();checkRuntime(cudaSetDevice(old_device));}void InferImpl::print(){if(!context_){INFOW("Infer print, nullptr.");return;}INFO("Infer %p detail", this);INFO("\tBase device: %s", CUDATools::device_description().c_str());INFO("\tMax Batch Size: %d", this->get_max_batch_size());INFO("\tInputs: %d", inputs_.size());for(int i = 0; i < inputs_.size(); ++i){auto& tensor = inputs_[i];auto& name = inputs_name_[i];INFO("\t\t%d.%s : shape {%s}, %s", i, name.c_str(), tensor->shape_string(), data_type_string(tensor->type()));}INFO("\tOutputs: %d", outputs_.size());for(int i = 0; i < outputs_.size(); ++i){auto& tensor = outputs_[i];auto& name = outputs_name_[i];INFO("\t\t%d.%s : shape {%s}, %s", i, name.c_str(), tensor->shape_string(), data_type_string(tensor->type()));} }std::shared_ptr<std::vector<uint8_t>> InferImpl::serial_engine() {auto memory = this->context_->engine_->serialize();auto output = make_shared<std::vector<uint8_t>>((uint8_t*)memory->data(), (uint8_t*)memory->data()+memory->size());memory->destroy();return output;}bool InferImpl::load_from_memory(const void* pdata, size_t size) {if (pdata == nullptr || size == 0)return false;context_.reset(new EngineContext());//build modelif (!context_->build_model(pdata, size)) {context_.reset();return false;}workspace_.reset(new MixMemory());cudaGetDevice(&device_);build_engine_input_and_outputs_mapper();return true;}bool InferImpl::load(const std::string& file) {auto data = load_file(file);if (data.empty())return false;context_.reset(new EngineContext());//build modelif (!context_->build_model(data.data(), data.size())) {context_.reset();return false;}workspace_.reset(new MixMemory());cudaGetDevice(&device_);build_engine_input_and_outputs_mapper();return true;}size_t InferImpl::get_device_memory_size() {EngineContext* context = (EngineContext*)this->context_.get();return context->context_->getEngine().getDeviceMemorySize();}static TRT::DataType convert_trt_datatype(nvinfer1::DataType dt){switch(dt){case nvinfer1::DataType::kFLOAT: return TRT::DataType::Float;case nvinfer1::DataType::kHALF: return TRT::DataType::Float16;case nvinfer1::DataType::kINT32: return TRT::DataType::Int32;default:INFOE("Unsupport data type %d", dt);return TRT::DataType::Float;}}void InferImpl::build_engine_input_and_outputs_mapper() {EngineContext* context = (EngineContext*)this->context_.get();int nbBindings = context->engine_->getNbBindings();int max_batchsize = context->engine_->getMaxBatchSize();inputs_.clear();inputs_name_.clear();outputs_.clear();outputs_name_.clear();orderdBlobs_.clear();bindingsPtr_.clear();blobsNameMapper_.clear();for (int i = 0; i < nbBindings; ++i) {auto dims = context->engine_->getBindingDimensions(i);auto type = context->engine_->getBindingDataType(i);const char* bindingName = context->engine_->getBindingName(i);dims.d[0] = 1;auto newTensor = make_shared<Tensor>(dims.nbDims, dims.d, convert_trt_datatype(type));newTensor->set_stream(this->context_->stream_);newTensor->set_workspace(this->workspace_);if (context->engine_->bindingIsInput(i)) {//if is inputinputs_.push_back(newTensor);inputs_name_.push_back(bindingName);inputs_map_to_ordered_index_.push_back(orderdBlobs_.size());}else {//if is outputoutputs_.push_back(newTensor);outputs_name_.push_back(bindingName);outputs_map_to_ordered_index_.push_back(orderdBlobs_.size());}blobsNameMapper_[bindingName] = i;orderdBlobs_.push_back(newTensor);}bindingsPtr_.resize(orderdBlobs_.size());}void InferImpl::set_stream(CUStream stream){this->context_->set_stream(stream);for(auto& t : orderdBlobs_)t->set_stream(stream);}CUStream InferImpl::get_stream() {return this->context_->stream_;}int InferImpl::device() {return device_;}void InferImpl::synchronize() {checkRuntime(cudaStreamSynchronize(context_->stream_));}bool InferImpl::is_output_name(const std::string& name){return std::find(outputs_name_.begin(), outputs_name_.end(), name) != outputs_name_.end();}bool InferImpl::is_input_name(const std::string& name){return std::find(inputs_name_.begin(), inputs_name_.end(), name) != inputs_name_.end();}void InferImpl::forward(bool sync) {EngineContext* context = (EngineContext*)context_.get();int inputBatchSize = inputs_[0]->size(0);for(int i = 0; i < context->engine_->getNbBindings(); ++i){auto dims = context->engine_->getBindingDimensions(i);auto type = context->engine_->getBindingDataType(i);dims.d[0] = inputBatchSize;if(context->engine_->bindingIsInput(i)){context->context_->setBindingDimensions(i, dims);}}for (int i = 0; i < outputs_.size(); ++i) {outputs_[i]->resize_single_dim(0, inputBatchSize);outputs_[i]->to_gpu(false);}for (int i = 0; i < orderdBlobs_.size(); ++i)bindingsPtr_[i] = orderdBlobs_[i]->gpu();void** bindingsptr = bindingsPtr_.data();//bool execute_result = context->context_->enqueue(inputBatchSize, bindingsptr, context->stream_, nullptr);bool execute_result = context->context_->enqueueV2(bindingsptr, context->stream_, nullptr);if(!execute_result){auto code = cudaGetLastError();INFOF("execute fail, code %d[%s], message %s", code, cudaGetErrorName(code), cudaGetErrorString(code));}if (sync) {synchronize();}}std::shared_ptr<MixMemory> InferImpl::get_workspace() {return workspace_;}int InferImpl::num_input() {return static_cast<int>(this->inputs_.size());}int InferImpl::num_output() {return static_cast<int>(this->outputs_.size());}void InferImpl::set_input (int index, std::shared_ptr<Tensor> tensor){if(index < 0 || index >= inputs_.size()){INFOF("Input index[%d] out of range [size=%d]", index, inputs_.size());}this->inputs_[index] = tensor;int order_index = inputs_map_to_ordered_index_[index];this->orderdBlobs_[order_index] = tensor;}void InferImpl::set_output(int index, std::shared_ptr<Tensor> tensor){if(index < 0 || index >= outputs_.size()){INFOF("Output index[%d] out of range [size=%d]", index, outputs_.size());}this->outputs_[index] = tensor;int order_index = outputs_map_to_ordered_index_[index];this->orderdBlobs_[order_index] = tensor;}std::shared_ptr<Tensor> InferImpl::input(int index) {if(index < 0 || index >= inputs_.size()){INFOF("Input index[%d] out of range [size=%d]", index, inputs_.size());}return this->inputs_[index];}std::string InferImpl::get_input_name(int index){if(index < 0 || index >= inputs_name_.size()){INFOF("Input index[%d] out of range [size=%d]", index, inputs_name_.size());}return inputs_name_[index];}std::shared_ptr<Tensor> InferImpl::output(int index) {if(index < 0 || index >= outputs_.size()){INFOF("Output index[%d] out of range [size=%d]", index, outputs_.size());}return outputs_[index];}std::string InferImpl::get_output_name(int index){if(index < 0 || index >= outputs_name_.size()){INFOF("Output index[%d] out of range [size=%d]", index, outputs_name_.size());}return outputs_name_[index];}int InferImpl::get_max_batch_size() {assert(this->context_ != nullptr);return this->context_->engine_->getMaxBatchSize();}std::shared_ptr<Tensor> InferImpl::tensor(const std::string& name) {auto node = this->blobsNameMapper_.find(name);if(node == this->blobsNameMapper_.end()){INFOF("Could not found the input/output node '%s', please makesure your model", name.c_str());}return orderdBlobs_[node->second];}std::shared_ptr<Infer> load_infer_from_memory(const void* pdata, size_t size){std::shared_ptr<InferImpl> Infer(new InferImpl());if (!Infer->load_from_memory(pdata, size))Infer.reset();return Infer;}std::shared_ptr<Infer> load_infer(const string& file) {std::shared_ptr<InferImpl> Infer(new InferImpl());if (!Infer->load(file))Infer.reset();return Infer;}int get_device_count() {int count = 0;checkRuntime(cudaGetDeviceCount(&count));return count;}int get_device() {int device = 0;checkRuntime(cudaGetDevice(&device));return device;}void set_device(int device_id) {if (device_id == -1)return;checkRuntime(cudaSetDevice(device_id));}bool init_nv_plugins() {bool ok = initLibNvInferPlugins(&gLogger, "");if (!ok) {INFOE("init lib nvinfer plugins failed.");}return ok;}
};

这次对 infer 的封装我们使用了 RAII + 接口模式两个特性

在头文件中我们可以看到 Infer 推理类是一个纯虚类,它是一个接口类,其核心函数是 forward,其它的函数都是服务于 forward 的,我们通过 load_infer 函数来进行初始化,这里体现了 RAII

在 forward 的实现中,我们对 context 还做了一层封装,对于输入和输出我们直接使用的是上节课封装的 Tensor 来实现的,因此我们实际上只用操作 input 和 output,然后调用 forward 即可

infer 的封装为 TensorRT 推理提供了一个高级封装。这个封装使用了 RAII 和接口设计模式,确保了资源的正确和高效管理,并为用户提供了一个清晰、一致的接口。主要的类 InferImpl 实现了所有关于模型加载、执行推理、张量管理的核心功能,而外部 API 为用户提供了简单的方法来加载模型、设置 device、初始化插件等。此外,该封装还考虑了 CUDA 流的管理和同步,以及 TensorRT 的日志处理

我们再来看看 main.cpp 部分:

void inference(){auto engine = TRT::load_infer("engine.trtmodel");if(engine == nullptr){printf("Deserialize cuda engine failed.\n");return;}engine->print();auto input       = engine->input();auto output      = engine->output();int input_width  = input->width();int input_height = input->height();...engine->forward(true);
}

可以看到在推理部分直接 load_infer 加载推理引擎,然后准备好 input 和 output,随后直接执行 forward 就可以完成推理,非常方便。

可以看到我们的程序更简单,更简洁清晰,这其实是 RAII+接口模式+builder封装+memory封装+tensor封装+infer封装 最后实现的效果

具体细节还是得多去看代码才行😂

总结

本次课程我们学习了 infer 的封装,主要是采用我们之前提到的 RAII + 接口模式,Infer 类是一个接口类,具体实现类 InferImpl 被隐藏在 CPP 文件中,封装完后的推理过程非常简洁,直接创建推理引擎,然后准备好输入输出,最后执行 forward 就行。能做到如此简洁主要是靠 RAII+接口模式+builder封装+memory封装+tensor封装+infer封装 最终呈现的结果。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「UG/NX」Block UI 面收集器FaceCollector

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

分类预测 | MATLAB实现MTBO-CNN多输入分类预测

分类预测 | MATLAB实现MTBO-CNN多输入分类预测 目录 分类预测 | MATLAB实现MTBO-CNN多输入分类预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现MTBO-CNN多输入分类预测 2.代码说明&#xff1a;基于登山队优化算法&#xff08;MTBO&#xff09;、卷积神经…

使用本地电脑搭建可以远程访问的SFTP服务器

文章目录 1. 搭建SFTP服务器1.1 下载 freesshd 服务器软件1.3 启动SFTP服务1.4 添加用户1.5 保存所有配置 2. 安装SFTP客户端FileZilla测试2.1 配置一个本地SFTP站点2.2 内网连接测试成功 3. 使用cpolar内网穿透3.1 创建SFTP隧道3.2 查看在线隧道列表 4. 使用SFTP客户端&#x…

代码随想录打卡—day21—【二叉树】— 8.21

1 530. 二叉搜索树的最小绝对差 530. 二叉搜索树的最小绝对差 想法&#xff1a;先直接中序遍历&#xff08;升序的序列&#xff09;过程中相邻两个数的差值取min&#xff0c;自己写一次AC代码&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* …

Facebook 应用未启用:这款应用目前无法使用,应用开发者已得知这个问题。

错误&#xff1a;Facebook 应用未启用:这款应用目前无法使用&#xff0c;应用开发者已得知这个问题。应用重新启用后&#xff0c;你便能登录。 「应用未经过审核或未发布」&#xff1a; 如果一个应用还没有经过Facebook的审核或者开发者尚未将应用发布&#xff0c;那么它将无法…

【Mysql】MVCC版本机制的多并发

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…

iOS设计规范是什么?都有哪些具体规范

iOS设计规范是苹果为移动设备操作系统iOS制定的设计指南。iOS设计规范的制定保证了苹果应用在外观和操作上的一致性和可用性&#xff0c;从而提高了苹果界面设计的用户体验和应用程序的成功性。本文将从七个方面全面分析iOS设计规范。 1.iOS设计规范完整版分享 由「即时设计」…

【LeetCode75】第三十四题 叶子相似的树

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们两棵二叉树&#xff0c;让我们判断这两棵二叉树的从左到右的叶子节点组成的叶子序列是否一致&#xff0c;即从左到右的叶子节点的数…

Open3D 进阶(5)变分贝叶斯高斯混合点云聚类

目录 一、算法原理二、代码实现三、结果展示四、测试数据本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 系列文章(连载中。。。爬虫,你倒是爬个完整的呀?): Open3D 进阶(1) MeanShift点云聚类Open3D 进阶(2)DB…

Ajax介绍

1.与服务器进行数据交换&#xff1a;通过 Ajax 可以给服务器发送请求&#xff0c;并获取服务器响应的数据。 2.异步交互&#xff1a;可以在 不重新加载整个页面 的情况下&#xff0c;与服务器交换数据并 更新部分网页 的技术&#xff0c;如&#xff1a; 搜索联想、用户名是否可…

浅析Linux SCSI子系统:调试方法

文章目录 SCSI日志调试功能scsi_logging_level调整SCSI日志等级 SCSI trace events使能SCSI trace events方式一&#xff1a;通过set_event接口方式二&#xff1a;通过enable 跟踪trace信息 相关参考 SCSI日志调试功能 SCSI子系统支持内核选项CONFIG_SCSI_LOGGING配置日志调试…

Django学习笔记(2)

创建app 属于自动执行了python manage.py 直接在里面运行startapp app01就可以创建app01的项目了 之后在setting.py中注册app01 INSTALLED_APPS ["django.contrib.admin","django.contrib.auth","django.contrib.contenttypes","django.c…

Dockerfile制作Web应用系统nginx镜像

目录 1.所需实现的具体内容 2.编写Dockerfile Dockerfile文件内容&#xff1a; 默认网页内容&#xff1a; 3.构建镜像 4.现在我们运行一个容器&#xff0c;查看我们的网页是否可访问 5.现在再将我们的镜像打包并上传到镜像仓库 1.所需实现的具体内容 基于centos基础镜像…

Linux学习之ssh和scp

ls /etc/ssh可以看到这个目录下有一些文件&#xff0c;而/etc/ssh/ssh_config是客户端配置文件&#xff0c;/etc/ssh/sshd_config是服务端配置文件。 cat -n /etc/ssh/sshd_config | grep "Port "可以看一下sshd监听端口的配置信息&#xff0c;发现这个配置端口是22…

async和await

一&#xff0c;基本使用 其实就是之前学过的异步函数&#xff0c;异步编程在函数前写一个ansyc&#xff0c;就转化为异步函数&#xff0c;返回的是一个promise对象&#xff0c;于是就可以使用await关键字&#xff0c;可以把异步函数写成同步函数的形式&#xff0c;极大地提高代…

python之Numpy

ndarray数组对象 NumPy定义了一个n维数组对象&#xff0c;简称ndarray对象&#xff0c;它是一个一系列相同类型元素组成的数组集合。数组中的每个元素都占有大小相同的内存块 ndarray 对象采用了数组的索引机制&#xff0c;将数组中的每个元素映射到内存块上&#xff0c;并且按…

LeetCode 542. 01 Matrix【多源BFS】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

Java日常的String、Date、计算问题

一、String相关类 三者执行速度&#xff1a;StringBuilder > StringBuffer > String 1.1、String 每次对 String 类型改变的时&#xff0c;都会生成一个新的 String 对象&#xff0c;指针指向新的 String 对象。 适用于字符串不常变的&#xff0c;少量的数据场景中&am…

【数据分析入门】Jupyter Notebook

目录 一、保存/加载二、适用多种编程语言三、编写代码与文本3.1 编辑单元格3.2 插入单元格3.3 运行单元格3.4 查看单元格 四、Widgets五、帮助 Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发、文档编写、运行代码和展示结果。 …

未来公文的智能化进程

随着技术的飞速发展&#xff0c;公文——这个有着悠久历史的官方沟通方式&#xff0c;也正逐步走向智能化的未来。自动化、人工智能、区块链...这些现代科技正重塑我们的公文制度&#xff0c;让其变得更加高效、安全和智慧。 1.语义理解与自动生成 通过深度学习和NLP&#xff…