LevelDB 源码阅读:如何优雅地合并写入和删除操作
LevelDB 支持写入单个键值对和批量写入多个键值对,这两种操作的处理流程本质上是相同的,都会被封装进一个 WriteBatch 对象中,这样就可以提高写操作的效率。
在 LevelDB 中,WriteBatch 是通过一个简单的数据结构实现的,其中包含了一系列的写入操作。这些操作被序列化(转换为字节流)并存储在内部的一个字符串中。每个操作都包括操作类型(如插入或删除),键和值(对于插入操作)。
当 WriteBatch 被提交给数据库时,其内容被解析并应用到 WAL 日志和 memtable 中。不管 WriteBatch 中包含多少操作,它们都将作为一个整体进行处理和日志记录。
WriteBatch 的实现主要涉及到 4 个文件,接下来一起看看。
- include/leveldb/write_batch.h:对外暴露的接口文件,定义了 WriteBatch 类的接口。
- db/write_batch_internal.h:内部实现文件,定义了 WriteBatchInternal 类,提供了一些操作 WriteBatch 的方法。
- db/write_batch.cc:WriteBatch 类的实现文件,实现了 WriteBatch 类。
- db/write_batch_test.cc:WriteBatch 类的测试文件,用于测试 WriteBatch 的功能。
WriteBatch 接口设计
我们先来看 write_batch.h 文件,这里定义了 WriteBatch 类对外暴露的一些接口。 LevelDB 代码中的注释十分清晰,不过这里先省略注释:
class LEVELDB_EXPORT WriteBatch {public:class LEVELDB_EXPORT Handler {public:virtual ~Handler();virtual void Put(const Slice& key, const Slice& value) = 0;virtual void Delete(const Slice& key) = 0;};WriteBatch();// Intentionally copyable.WriteBatch(const WriteBatch&) = default;WriteBatch& operator=(const WriteBatch&) = default;~WriteBatch();void Put(const Slice& key, const Slice& value);void Delete(const Slice& key);void Clear();size_t ApproximateSize() const;void Append(const WriteBatch& source);Status Iterate(Handler* handler) const;private:friend class WriteBatchInternal;std::string rep_; // See comment in write_batch.cc for the format of rep_
};
其中 WriteBatch::Handler 是一个抽象基类,定义了处理键值对操作的接口,只包括 Put 和 Delete 方法。这样的设计允许 WriteBatch 类实现与具体存储操作解耦,使得 WriteBatch 不必直接知道如何将操作应用到底层存储(如 MemTable)。
通过继承 Handler 类,可以创建多种处理器,它们可以以不同的方式实现这些方法。比如:
- MemTableInserter: 定义在 db/write_batch.cc 中,将键值操作存储到 MemTable 中。
- WriteBatchItemPrinter:定义在 db/dumpfile.cc 中,将键值操作打印到文件中,可以用来测试。
另外还有一个 friend class WriteBatchInternal
作为 WriteBatch 的友元类,能够访问其私有和受保护成员。WriteBatchInternal 主要用来封装一些内部操作,这些方法不需要对外暴露,只在内部用到。通过将内部操作方法隐藏在 WriteBatchInternal 中,保持了对象的接口清晰,可以自由地修改内部实现而不影响到使用这些对象的代码。
WriteBatch 使用方法
在应用层,我们可以通过 WriteBatch 来批量写入多个键值对,然后通过 DB::Write
方法将 WriteBatch 写入到数据库中。
这里 WriteBatch 支持 Put 和 Delete 操作,可以合并多个 WriteBatch。如下使用示例:
WriteBatch batch;
batch.Put("key1", "value1");
batch.Put("key2", "value2");
batch.Delete("key3");// 合并另一个批次
WriteBatch another_batch;
another_batch.Put("key4", "value4");
batch.Append(another_batch);// 写入数据库
db->Write(writeOptions, &batch);
WriteBatch 实现细节
那么 WriteBatch 是怎么实现的呢?关键在 db/write_batch.cc,该类中有一个 private 成员 std::string rep_
来存储序列化后的键值操作。我们先来看看这里的存储数据协议:
+---------------+---------------+----------------------------------------+
| Sequence | Count | Data |
| (8 bytes) | (4 bytes) | |
+---------------+---------------+----------------------------------------+| | |v v v+-------+ +-------+ +-------+|Record1| |Record2| ... |RecordN|+-------+ +-------+ +-------+| |v v+-----------------+ +-----------------+| kTypeValue | | kTypeDeletion || Varstring Key | | Varstring Key || Varstring Value | | |+-----------------+ +-----------------+Varstring (可变长度字符串):
+-------------+-----------------------+
| Length (varint32) | Data (uint8[]) |
+-------------+-----------------------+
该字符串前 12 个字节是头部元数据部分,包括 8 个字节的序列号和 4 个字节的 count 数。接下来是一个或多个操作记录,每个记录包含一个操作类型和键值对。操作类型是一个字节,可以是 Put 或者 Delete 操作。键和值都是可变长度的字符串,格式为 varstring。
LevelDB 的序列号机制
rep_ 头部 8 个字节代表64位的数字 sequence(序列号),WriteBatchInternal 友元类提供了两个方法来获取和设置 sequence number,内部是用 EncodeFixed64 和 DecodeFixed64 方法来编解码 64 位的序列号。
SequenceNumber WriteBatchInternal::Sequence(const WriteBatch* b) {return SequenceNumber(DecodeFixed64(b->rep_.data()));
}void WriteBatchInternal::SetSequence(WriteBatch* b, SequenceNumber seq) {EncodeFixed64(&b->rep_[0], seq);
}
序列号是 LevelDB 中的全局递增标识符,用于实现版本控制和操作排序。每个 WriteBatch 在执行时会获得一段连续的序列号,批次内的每个操作(Put/Delete)都会分配到其中的一个序列号。序列号在 LevelDB 中有三个核心作用:
- 版本控制:LevelDB 中的每个 key 可以有多个版本,每个版本都对应一个序列号。在读取时,通过比较序列号来确定应该返回哪个版本的值。较大的序列号表示更新的版本。
- 多版本并发控制(MVCC):写操作获取新的序列号,创建 key 的新版本。读操作可以指定序列号,访问该序列号时间点的数据快照。这种机制使得读写操作可以并发执行,无需互相阻塞。
- 故障恢复:WAL(预写日志)中记录了操作的序列号。系统重启时,通过序列号可以准确重建崩溃时的数据状态,避免重复应用已持久化的操作。
这种设计让 LevelDB 既保证了数据一致性,又实现了高效的并发控制。
设置序列号的逻辑在 DBImpl::Write 方法中,首先获取当前最大序列号,然后为 WriteBatch 分配一个新的序列号。
Status DBImpl::Write(const WriteOptions& options, WriteBatch* updates) {// ...uint64_t last_sequence = versions_->LastSequence();// ...if (status.ok() && updates != nullptr) { // nullptr batch is for compactionsWriteBatch* write_batch = BuildBatchGroup(&last_writer);WriteBatchInternal::SetSequence(write_batch, last_sequence + 1);last_sequence += WriteBatchInternal::Count(write_batch);// ...}
}
如果 WriteBatch 包含多个操作,那么这些操作会连续地分配序列号。在写入 WAL 日志时,会将 WriteBatch 的序列号写入到日志中,这样在恢复时可以根据序列号来恢复操作的顺序。写入 memtable 之后,会更新当前最大序列号,以便下次分配。
count 记录操作数
头部还有 4 个字节的 count,用于记录 WriteBatch 中包含的操作数。这里每次 put 或者 delete 操作都会增加 count 的值。如下示例:
WriteBatch batch;
batch.Put("key1", "value1"); // count = 1
batch.Put("key2", "value2"); // count = 2
batch.Delete("key3"); // count = 3
int num_ops = WriteBatchInternal::Count(&batch); // = 3
在合并两个 WriteBatch 的时候,也会累计两部分的 count 的值,如下 WriteBatchInternal::Append 方法:
void WriteBatchInternal::Append(WriteBatch* dst, const WriteBatch* src) {SetCount(dst, Count(dst) + Count(src));assert(src->rep_.size() >= kHeader);dst->rep_.append(src->rep_.data() + kHeader, src->rep_.size() - kHeader);
}
使用 count 的地方主要有两个,一个是在迭代这里每个记录的时候,会用 count 来做完整性检查,确保没有遗漏操作。
Status WriteBatch::Iterate(Handler* handler) const {Slice input(rep_);...if (found != WriteBatchInternal::Count(this)) {return Status::Corruption("WriteBatch has wrong count");} else {return Status::OK();}
}
另一个是在 db 写入的时候,根据 count 可以预先知道需要分配多少序列号,保证序列号连续性。如下 DBImpl::Write:
WriteBatchInternal::SetSequence(write_batch, last_sequence + 1);
last_sequence += WriteBatchInternal::Count(write_batch);
支持的各种操作
在头部的 sequence 和 count 之后,rep_ 紧跟着的是一系列的记录,每个记录包含一个操作类型和键值。这里记录可以通过 Put 和 Delete 方法来添加,其中 Put 方法的实现如下:
void WriteBatch::Put(const Slice& key, const Slice& value) {WriteBatchInternal::SetCount(this, WriteBatchInternal::Count(this) + 1);rep_.push_back(static_cast<char>(kTypeValue));PutLengthPrefixedSlice(&rep_, key);PutLengthPrefixedSlice(&rep_, value);
}
这里更新了 count,然后添加了 kTypeValue 操作类型,接着是添加 key 和 value。Delete 操作类似,count 计数也是要加 1,然后操作类型是 kTypeDeletion,最后只用添加 key 即可。
void WriteBatch::Delete(const Slice& key) {WriteBatchInternal::SetCount(this, WriteBatchInternal::Count(this) + 1);rep_.push_back(static_cast<char>(kTypeDeletion));PutLengthPrefixedSlice(&rep_, key);
}
上面是往 rep_ 中添加记录,那么如何从 rep_ 中解析出这些记录呢?这里 WriteBatch 类中提供了一个 Iterate 方法,该方法遍历 rep_ 中的每条记录,然后通过传入的 Handler 接口来灵活处理这些记录。
此外该方法的实现中还有数据格式验证,会检查头部大小、操作类型、操作数量是否匹配。可以返回 Corruption 错误,表示数据格式不正确等。Iterate 核心代码如下:
Status WriteBatch::Iterate(Handler* handler) const {Slice input(rep_);if (input.size() < kHeader) {return Status::Corruption("malformed WriteBatch (too small)");}input.remove_prefix(kHeader);Slice key, value;int found = 0;while (!input.empty()) {found++;char tag = input[0];input.remove_prefix(1);switch (tag) {case kTypeValue:if (GetLengthPrefixedSlice(&input, &key) &&GetLengthPrefixedSlice(&input, &value)) {handler->Put(key, value);} else {return Status::Corruption("bad WriteBatch Put");}break;case kTypeDeletion:if (GetLengthPrefixedSlice(&input, &key)) {handler->Delete(key);} else {return Status::Corruption("bad WriteBatch Delete");}break;default:return Status::Corruption("unknown WriteBatch tag");}}// ...
}
前面提过 Handler 是 WriteBatch 的抽象基类,可以传入不同的实现。在 LevelDB 写数据的时候,这里传入的是 MemTableInserter 类,该类将操作数据存储到 MemTable 中。具体可以调用这里的实现:
Status WriteBatchInternal::InsertInto(const WriteBatch* b, MemTable* memtable) {MemTableInserter inserter;inserter.sequence_ = WriteBatchInternal::Sequence(b);inserter.mem_ = memtable;return b->Iterate(&inserter);
}
整体上看 WriteBatch 负责存储键值操作的数据,进行编码解码等,而 Handler 负责具体处理里面的每条数据。这样 WriteBatch 的操作就可以被灵活地应用到不同场景中,方便扩展。
测试用例分析
最后再来看看 write_batch_test.cc,这里提供了一些测试用例,用于测试 WriteBatch 的功能。
首先定义了一个 PrintContents 函数,用来输出 WriteBatch 中的所有操作记录。这里用 MemTableInserter 将 WriteBatch 中的操作记录存储到 MemTable 中,然后通过 MemTable 的迭代器遍历所有记录,并保存到字符串中。
这里测试用例覆盖了下面这些情况:
- Empty:测试空的 WriteBatch 是否正常;
- Multiple:测试多个 Put 和 Delete 操作,验证总的 count 数目和每个操作的序列号是否正确;
- Corruption:先写进去数据,然后故意截断部分记录,测试能读取尽量多的正常数据;
- Append:测试合并两个 WriteBatch,验证合并后序列号的正确性,以及合并空 WriteBatch;
- ApproximateSize:测试 ApproximateSize 方法,计算 WriteBatch 的近似大小;
这里通过测试用例,基本就能知道怎么使用 WriteBatch 了。比较有意思的是,前面在看 Append 代码的时候,没太留意到合并后这里序列号是用谁的。这里结合测试用例,才发现取的目标 WriteBatch 的序列号。
TEST(WriteBatchTest, Append) {WriteBatch b1, b2;WriteBatchInternal::SetSequence(&b1, 200);WriteBatchInternal::SetSequence(&b2, 300);b1.Append(b2);ASSERT_EQ("", PrintContents(&b1));b2.Put("a", "va");b1.Append(b2);ASSERT_EQ("Put(a, va)@200", PrintContents(&b1));// ...
}
总结
通过深入分析 LevelDB 的 WriteBatch 实现,我们可以清晰地看到其设计精妙之处。WriteBatch 通过将多个写入和删除操作封装在一起,不仅提高了写操作的效率,还简化了并发控制和故障恢复的实现。有几个亮点值得借鉴:
- 批量操作:WriteBatch 允许将多个 Put 和 Delete 操作合并为一个批次,减少了频繁的 I/O 操作,提升了写入性能。
- 序列号机制:通过全局递增的序列号,LevelDB 实现了多版本并发控制(MVCC),确保了读写操作的一致性。
- Handler 抽象:通过 Handler 接口,WriteBatch 将操作的具体实现与存储逻辑解耦,使得代码更加灵活和可扩展。
- 数据格式验证:在解析 WriteBatch 时,LevelDB 会进行严格的数据格式验证,确保数据的完整性和正确性。
当然本篇只是分析 WriteBatch 的实现,并没有串起 LevelDB 的整个写入流程,后续文章我们会继续分析,写入一个 key 的完整流程。
相关文章:
LevelDB 源码阅读:如何优雅地合并写入和删除操作
LevelDB 支持写入单个键值对和批量写入多个键值对,这两种操作的处理流程本质上是相同的,都会被封装进一个 WriteBatch 对象中,这样就可以提高写操作的效率。 在 LevelDB 中,WriteBatch 是通过一个简单的数据结构实现的࿰…...
Vue.js 组件的生命周期钩子
Vue.js 组件的生命周期钩子 在 Vue.js 中,组件的生命周期是指组件从创建到销毁的整个过程。在这个过程中,Vue 提供了多个 生命周期钩子,让我们可以在不同的阶段执行特定的逻辑。掌握这些钩子非常重要,它们能帮助我们灵活控制组件…...
VD:生成a2l文件
目录 前言Simulink合并地址 ASAP2 editor 前言 我之前的方法都是通过Simulink模型生成代码的过程中顺便就把a2l文件生成出来了,这时的a2l文件还没有地址,所以紧接着会去通过elf文件更新地址,一直以为这是固定的流程和方法,今天无…...
【C++ 类和对象 进阶篇】—— 逻辑森林的灵动精灵,舞动类与对象的奇幻圆舞曲
欢迎来到ZyyOvO的博客✨,一个关于探索技术的角落,记录学习的点滴📖,分享实用的技巧🛠️,偶尔还有一些奇思妙想💡 本文由ZyyOvO原创✍️,感谢支持❤️!请尊重原创…...
【Hive】海量数据存储利器之Hive库原理初探
文章目录 一、背景二、数据仓库2.1 数据仓库概念2.2 数据仓库分层架构2.2.1 数仓分层思想和标准2.2.2 阿里巴巴数仓3层架构2.2.3 ETL和ELT2.2.4 为什么要分层 2.3 数据仓库特征2.3.1 面向主题性2.3.2 集成性2.3.3 非易失性2.3.4 时变性 三、hive库3.1 hive概述3.2 hive架构3.2.…...
前端web
学习笔记: 基本属性 color: 设置文本的颜色。代码:color: red;background-color: 设置元素的背景颜色。background-color: blue;font-size: 设置文本的大小font-size: 16px;font-family: 设置文本的字体font-family: Arial, sans-serif;text-align: 设…...
如何通过 Nginx 实现 CouchDB 集群的负载均衡并监控请求分发
在现代分布式系统中,负载均衡是确保高可用性和性能的关键组件。CouchDB 是一个强大的分布式数据库,而 Nginx 是一个高性能的反向代理和负载均衡器。本文将详细介绍如何通过 Nginx 实现 CouchDB 集群的负载均衡,并监控请求被分发到哪一台 Couc…...
基于代理的RAG实现-Agentic RAG
基于代理的RAG实现-Agentic RAG Agentic RAG 体系结构中,不再被动地响应查询请求,而是主动地分析初步检索到的信息,并基于对任务复杂性的评估,战略性地选择最为合适的工具和方法进行进一步的数据检索和处理。这种多步骤推理和决策…...
ZIP怎么加密?
想要禁止他人随意解压zip压缩包,我们可以加密zip文件,那么zip设置密码的方法有哪些?今天分享三个加密方法 工具:WinRAR,这里需要注意,WinRAR默认压缩格式是.rar,所以我们想要加密zip文件&#…...
森林网络部署,工业4G路由器实现林区组网远程监控
在广袤无垠的林区,每一片树叶的摇曳、每一丝空气的流动,都关乎着生态的平衡与安宁。林区监控正以强大的力量,为这片绿色家园筑起一道坚固的防线。 工业 4G 路由器作为林区监控组网的守护者,凭借着卓越的通讯性能,突破…...
汽车网络信息安全-ISO/SAE 21434解析(上)
目录 概述 第四章-概述 1. 研究对象和范围 2. 风险管理 第五章-组织级网络安全管理 1. 网络安全治理(cybersecurity governance) 2. 网络安全文化(cybersecurity culture) 3. 信息共享(Information Sharing) 4. 管理体系…...
一个方法被多个线程同时调用,确保同样参数的调用只能有一个线程执行,不同参数的调用则可以多个线程同时执行
我们知道通过lock一个固定静态object给代码段加同步锁,可以让多个线程的同时调用以同步执行,因此可以利用字典来给不同参数分配不同的静态对象,方法中不同的参数调用锁住各自不同的静态对象即可实现不同参数不加锁,相同参数才加锁…...
军用通信设备通用规范GJB367A-2001试验
军用通信设备通用规范GJB367A-2001通常适用于地面、舰载和机载设备的型式试验验收标准,地面设备分为便携式设备、固定式设备和车载式设备。 GJB367A-2001军用通信设备通用规范规定了军用通信设备或系统的通用要求和检验验收规则以及试验方法。 GJB367A-2001军用通…...
AWS云计算概览(自用留存)
目录 一、云概念概览 (1)云服务模型 (2)云计算6大优势 (3)web服务 (4)AWS云采用框架(AWS CAF) 二、云经济学 & 账单 (1)定…...
AWS云平台上生成式AI通过项目文档内容分析获知项目风险
要在AWS云平台上设计和实施高性能系统,同时使用生成式人工智能识别项目风险来分析项目文档内容。 利用生成式AI分析项目文档并协助风险管理可以显著提高识别和解决AWS上托管的复杂项目中的风险的速度、准确性和效率。通过将AI的功能与传统的项目管理最佳实践相结合&…...
搜广推日常实习面经一
写在前面:除了校招的面经,实习的面经我也会更新,毕竟俺后续可能还要找一段实习。从八股来看,实习的八股更加的八股一点。和校招的面经有点不一样,所以还是可以学习了解一下。总之一句话:面向工作学习&#…...
分布式CAP理论介绍
分布式CAP理论是分布式系统设计中的一个核心概念,由加州大学伯克利分校的Eric Brewer教授在2000年的ACM研讨会上首次提出,随后在2002年由Seth Gilbert和Nancy Lynch从理论上证明。以下是对分布式CAP理论的详细剖析: 文章目录 一、CAP理论的基本概念二、CAP理论的取舍策略三、…...
网格参数化,Mesh parameterization processing
目录 前言1.Barycentric mappingMapping 步骤实例 2.Laplace mapping3.Laplace improvement4.Coding 前言 多边形网格的类型多种多样。本文所实现的网格多边形参数化是指三角多边形。 不同的表示被用来编码三维物体的几何形状。选择一种表示方式取决于在上游的获取过程和下游的…...
路由环路的产生原因与解决方法(1)
路由环路 路由环路就是数据包不断在这个网络传输,始终到达不了目的地,导致掉线或者网络瘫痪。 TTL (生存时间):数据包每经过一个路由器的转发,其数值减1,当一个数据包的TTL值为0是,路…...
编程工具箱(免费,离线可用)
https://www.yuque.com/huanmin-4bkaa/ii1hx1?# 《工具箱》 常用的大部分工具都有, 比如mysql可视化 redis可视化, json编辑器, 加解密等(免费,离线可用) 后续也会慢慢的集成...
从Arrays源码学习定义工具类
背景 在日常编码中,一个比较好的实践是:我们把一些业务无关的、可复用的一些通用逻辑,封装成工具类、甚至jar包。这样一方面方便通用代码抽取、代码复用,同时也隔离经常变动的业务代码和不变的通用代码。那如何定义好一个工具类呢…...
Unity中实现倒计时结束后干一些事情
问题描述:如果我们想实现在一个倒计时结束后可以执行某个方法,比如挑战成功或者挑战失败,或者其他什么的比如生成boss之类的功能,而且你又不想每次都把代码复制一遍,那么就可以用下面这种方法。 结构 实现步骤 创建一…...
FPGA随记——时钟时序一些基本知识
原文链接:跨时钟域设计-CSDN博客 前言 CDC(clock domain crossing)检查(跨时钟域的检查)是对电路设计中同步电路设计的检查。非同步时钟没有固定的相位关系,这样Setup/Hold不满足而产生了亚稳态是无法避免…...
【微服务】SpringBoot 通用异常处理方案使用详解
目录 一、前言 二、SpringBoot 异常介绍 2.1 SpringBoot 中异常定义 2.1.1 SpringBoot 异常处理机制的重要性 2.2 常用的异常分类 2.3 常用的异常处理解决方案 三、springboot 异常处理操作实践 3.1 springboot自适应错误处理机制 3.1.1 使用默认错误页面 3.1.2 自定义…...
初识MyBatis(详细)
目录 回顾 前提回忆下web应用程序的三层架构: 编辑 一.什么是MyBatis? 二.Mybatis操作数据库的步骤: 2.1准备工作 2.2配置Mybatis(数据库连接信息) 2.3写持久层代码 2.4测试 三.Mybatis配置文件 3.1打印日志 3.2参数传递…...
elementui表单验证,数据层级过深验证失效
先看示例代码,代码为模拟动态获取表单数据,然后动态添加rules验证规则,示例表单内输入框绑定form内第四层: <template><el-form :model"form" :rules"rules" ref"ruleForm" label-width&…...
HTTPS与HTTP:区别及安全性对比
目录 一、基础概念 二、安全性对比 1. 加密传输 2. 身份验证 3. 数据完整性 4. 端口 5. 浏览器展示方式 三、使用场景与性能 1. 使用场景 2. 性能开销 四、成本与维护 五、搜索引擎优化(SEO) 六、案例分析 七、隐私保护与中间人攻击 八、…...
中国石油大学(华东)自动评教工具(涵盖爬虫的基础知识,适合练手)
我开发了一个用于自动评教的工具,大家可以试着用用,下面是链接。 https://github.com/restrain11/auto_teachingEvaluate 可以点个星吗,感谢!🫡 以下是我在开发过程中学到的知识 以及 碰到的部分问题 目录 动态爬虫和静…...
蓝桥杯备考:二叉树详解
二叉树的概念和相关术语 二叉树的定义:每个结点度至多为2的树,叫二叉树 二叉树的子树有左右之分不可以随意颠倒顺序,也就是说二叉树是有序树 二叉树根结点左子树右子树 满二叉树:就是把每一层的结点都铺满 满二叉树的性质…...
大模型微调介绍-Prompt-Tuning
提示微调入门 NLP四范式 第一范式 基于「传统机器学习模型」的范式,如TF-IDF特征朴素贝叶斯等机器算法. 第二范式 基于「深度学习模型」的范式,如word2vec特征LSTM等深度学习算法,相比于第一范式,模型准确有所提高,…...
《机器学习》——PCA降维
文章目录 PCA降维简介什么是主成分分析? 主成分的选择与维度确定降维的数学过程PCA降维求解步骤降维后的效果和应用场景中的优势PCA模型API参数Attributes属性PCA对象的方法 PCA降维实例导入所需库导入数据集对数据进行处理创建PCA模型并训练查看训练结果对降维数据…...
【Rust练习】28.use and pub
练习题来自:https://practice-zh.course.rs/crate-module/use-pub.html 1 使用 use 可以将两个同名类型引入到当前作用域中,但是别忘了 as 关键字. use std::fmt::Result; use std::io::Result;fn main() {}利用as可以将重名的内容取别名:…...
VUE学习笔记1__创建VUE实例
核心步骤 <div id"app"><!-- 这里存放渲染逻辑代码 --><h1>{{ msg }}</h1><a href"#">{{count}}</a> </div><!-- 引入在线的开发版本核心包 --> <!-- 引入核心包后全局可使用VUE构造函数 --> <…...
不用PLC和板卡,一台电脑就可以控制伺服
1、前言 大家好!我是付工。 EtherCAT是运动控制领域使用最广泛的总线通信协议之一。 如果我们只有一台电脑,能不能直接控制EtherCAT总线伺服呢? 这个是完全可以的。 我们可以在电脑上安装实时运行环境,从而实现对伺服电机的总…...
vue2制作长方形容器,正方形网格散点图,并且等比缩放拖动
需求:有个长方形的容器,但是需要正方形的网格线,网格线是等比缩放的并且可以无线拖动的,并且添加自适应缩放和动态切换,工具是plotly.js,已完成功能如下 1.正方形网格 2.散点分组 3.自定义悬浮框的数据 4.根据窗口大小…...
鸿蒙-页面和自定义组件生命周期
页面生命周期,即被Entry装饰的组件生命周期,提供以下生命周期接口: onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。onPageHide:页面每次隐藏时触发一次,包括路由过程、…...
AD域学习
AD域学习 AD域一、什么是AD域二、概念三、疑问四、内容4.1、AD域的功能 五、应用实践 AD域 一、什么是AD域 AD域(Active Directory Domain)是微软Windows网络中的一个概念,它是一种计算机网络的形式,其中所有用户账户、计算机、…...
leetcode 3066. 超过阈值的最少操作数 II 中等
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 一次操作中,你将执行: 选择 nums 中最小的两个整数 x 和 y 。将 x 和 y 从 nums 中删除。将 min(x, y) * 2 max(x, y) 添加到数组中的任意位置。 注意,只有当 nums 至少包含两个元…...
使用 WPF 和 C# 绘制覆盖网格的 3D 表面
此示例展示了如何使用 C# 代码和 XAML 绘制覆盖有网格的 3D 表面。示例使用 WPF 和 C# 将纹理应用于三角形展示了如何将纹理应用于三角形。此示例只是使用该技术将包含大网格的位图应用于表面。 在类级别,程序使用以下代码来定义将点的 X 和 Z 坐标映射到 0.0 - 1.…...
大数据学习(34)-mapreduce详解
&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦ᾑ…...
源码编译安装httpd 2.4
方法一: 1、下载 Apache 源代码: wget https://archive.apache.org/dist/httpd/httpd-2.4.54.tar 2、解压源代码: tar -zxvf httpd-2.4.54.tar.gz 3、进入源代码目录: cd httpd-2.4.54 4、安装开发工具组: yum…...
1.15寒假作业
web:nss靶场ez_ez_php 打开环境,理解代码 使用个体传参的方法,首先代码会检查file参数的前三个字符是不是php,如果是就输出nice,然后用include函数包含file,绕过不是则输出hacker,如果没有file…...
Java中private和static同时使用会出现什么情况?
引言 这几天在学习单例设计模式(后面会出一期包含23种设计模式介绍的博客)的时候发现了一段代码 private static Single single; 当时我就在想,这个private和static一起用的话外界想要访问这个成员变量到底是能不能访问到呢?当…...
vue倒计时组件封装,根据每个循环项的倒计时是否结束添加新类名。
1.创建countdown.vue文件: <template><p style"font-size: 10px">{{time}}</p> </template> <script>export default{data () {return {time : ,flag : false}},mounted () {let time setInterval(() > {if (this.fla…...
nvim 打造成可用的IDE(2)
上一个 文章写的太长了, 后来再写东西 就一卡一卡的,所以新开一个。 主要是关于 bufferline的。 之前我的界面是这样的。 这个图标很不舒服有。 后来发现是在这里进行配置。 我也不知道,这个配置 我是从哪 抄过来的。 测试结果࿱…...
Spring Boot教程之五十五:Spring Boot Kafka 消费者示例
Spring Boot Kafka 消费者示例 Spring Boot 是 Java 编程语言中最流行和使用最多的框架之一。它是一个基于微服务的框架,使用 Spring Boot 制作生产就绪的应用程序只需很少的时间。Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,您可…...
目标检测跟踪中的Siamese孪生网络与普通卷积网络(VGG、ResNet)有什么区别?
1、什么是Siamese网络? Siamese网络又叫孪生网络,是一种特殊的神经网络架构,由一对(或多对)共享参数的子网络组成,用于学习输入样本之间的相似性或关系。最早在 1994 年由 Bromley 等人提出,最…...
AIGC时代 | 探索AI Agent的奥秘:四种设计模式引领未来智能趋势
AIGC时代 | 探索AI Agent的奥秘:四种设计模式引领未来智能趋势 引言 随着人工智能(AI)技术的飞速发展,AI Agent作为新一代的智能代理,正在引领工作流程的革新。AI Agent,即人工智能代理,是一种…...
PyTorch框架——基于深度学习YOLOv5神经网络水果蔬菜检测识别系统
基于深度学习YOLOv5神经网络水果蔬菜检测识别系统,其能识别的水果蔬菜有15种,# 水果的种类 names: [黑葡萄, 绿葡萄, 樱桃, 西瓜, 龙眼, 香蕉, 芒果, 菠萝, 柚子, 草莓, 苹果, 柑橘, 火龙果, 梨子, 花生, 黄瓜, 土豆, 大蒜, 茄子, 白萝卜, 辣椒, 胡萝卜,…...
【redis】redis-cli命令行工具的使用
redis-cli命令行工具是一个功能强大的Redis客户端,它允许用户与Redis数据库进行交互和管理。 以下是一些常用参数的使用说明: 基本连接参数 -h, --host <hostname>:指定要连接的Redis服务器的主机名或IP地址。如果未指定,…...