18.Netty源码之ByteBuf 详解
highlight: arduino-light
ByteBuf 是 Netty 的数据容器,所有网络通信中字节流的传输都是通过 ByteBuf 完成的。
然而 JDK NIO 包中已经提供了类似的 ByteBuffer 类,为什么 Netty 还要去重复造轮子呢?本节课我会详细地讲解 ByteBuf。
JDK NIO的ByteBuffer
我们首先介绍下 JDK NIO 的 ByteBuffer,才能知道 ByteBuffer 有哪些缺陷和痛点。下图展示了 ByteBuffer 的内部结构:
从图中可知,ByteBuffer 包含以下四个基本属性:
- mark:为某个读取过的关键位置做标记,方便回退到该位置;
- position:当前读取的位置;
- limit:buffer 中有效的数据长度大小;
- capacity:初始化时的空间容量。
以上四个基本属性的关系是:mark <= position <= limit <= capacity。结合 ByteBuffer 的基本属性,不难理解它在使用上的一些缺陷。
第一,ByteBuffer 分配的长度是固定的,无法动态扩缩容,所以很难控制需要分配多大的容量。如果分配太大容量,容易造成内存浪费;如果分配太小,存放太大的数据会抛出 BufferOverflowException 异常。在使用 ByteBuffer 时,为了避免容量不足问题,你必须每次在存放数据的时候对容量大小做校验,如果超出 ByteBuffer 最大容量,那么需要重新开辟一个更大容量的 ByteBuffer,将已有的数据迁移过去。整个过程相对烦琐,对开发者而言是非常不友好的。
第二,ByteBuffer 只能通过 position 获取当前可操作的位置,因为读写共用的 position 指针,所以需要频繁调用 flip、rewind 方法切换读写状态,开发者必须很小心处理 ByteBuffer 的数据读写,稍不留意就会出错。
ByteBuffer 作为网络通信中高频使用的数据载体,显然不能够满足 Netty 的需求,Netty 重新实现了一个性能更高、易用性更强的 ByteBuf,相比于 ByteBuffer 它提供了很多非常酷的特性:
- 容量可以按需动态扩展,类似于 StringBuffer;
- 读写采用了不同的指针,读写模式可以随意切换,不需要调用 flip 方法;
- 通过内置的复合缓冲类型可以实现零拷贝;
- 支持引用计数;
- 支持缓存池。
这里我们只是对 ByteBuf 有一个简单的了解,接下来我们就一起看下 ByteBuf 是如何实现的吧。
痛点:
1.readIndex 和 writeIndex没有分开
2.需要调用flip和clear方法
3.api命名区分度低
Netty ByteBuf 内部结构
同样我们看下 ByteBuf 的内部结构,与 ByteBuffer 做一个对比。
从图中可以看出,ByteBuf 包含三个指针:读指针 readerIndex、写指针 writeIndex、最大容量 maxCapacity,根据指针的位置又可以将 ByteBuf 内部结构可以分为四个部分:
第一部分是废弃字节,表示已经丢弃的无效字节数据。
第二部分是可读字节,表示 ByteBuf 中可以被读取的字节内容,可以通过 writeIndex - readerIndex 计算得出。从 ByteBuf 读取 N 个字节,readerIndex 就会自增 N,readerIndex 不会大于 writeIndex,当 readerIndex == writeIndex 时,表示 ByteBuf 已经不可读。
第三部分是可写字节,向 ByteBuf 中写入数据都会存储到可写字节区域。向 ByteBuf 写入 N 字节数据,writeIndex 就会自增 N,当 writeIndex 超过 capacity,表示 ByteBuf 容量不足,需要扩容。
第四部分是可扩容字节,表示 ByteBuf 最多还可以扩容多少字节,当 writeIndex 超过 capacity 时,会触发 ByteBuf 扩容,最多扩容到 maxCapacity 为止,超过 maxCapacity 再写入就会出错。
由此可见,Netty 重新设计的 ByteBuf 有效地区分了可读、可写以及可扩容数据,解决了 ByteBuffer 无法扩容以及读写模式切换烦琐的缺陷。
接下来,我们一起学习下 ByteBuf 的核心 API,你可以把它当作 ByteBuffer 的替代品单独使用。
引用计数
ByteBuf 是基于引用计数设计的,它实现了 ReferenceCounted 接口,ByteBuf 的生命周期是由引用计数所管理。
只要引用计数大于 0,表示 ByteBuf 还在被使用;
当 ByteBuf 不再被其他对象所引用时,引用计数为 0,那么代表该对象可以被释放。
当新创建一个 ByteBuf 对象时,它的初始引用计数为 1,当 ByteBuf 调用 release() 后,引用计数减 1。
所以不要误以为调用了 release() 就会保证 ByteBuf 对象一定会被回收。因为可能计数是2。
你可以结合以下的代码示例做验证:
md ByteBuf buffer = ctx.alloc().directBuffer(); assert buffer.refCnt() == 1; buffer.release(); assert buffer.refCnt() == 0;
引用计数对于 Netty 设计缓存池化有非常大的帮助,当引用计数为 0,该 ByteBuf 可以被放入到对象池中,避免每次使用 ByteBuf 都重复创建,对于实现高性能的内存管理有着很大的意义。
此外 Netty 可以利用引用计数的特点实现内存泄漏检测工具。
JVM 并不知道 Netty 的引用计数是如何实现的,当 ByteBuf 对象不可达时,一样会被 GC 回收掉,但是如果此时 ByteBuf 的引用计数不为 0,那么该对象就不会释放或者被放入对象池,从而发生了内存泄漏。
Netty 会对分配的 ByteBuf 进行抽样分析,检测 ByteBuf 是否已经不可达且引用计数大于 0,判定内存泄漏的位置并输出到日志中,你需要关注日志中 LEAK 关键字。
ByteBuf 分类
ByteBuf 有多种实现类,每种都有不同的特性,下图是 ByteBuf 的家族图谱,可以划分为三个不同的维度:Heap/Direct、Pooled/Unpooled和Unsafe/非 Unsafe,我逐一介绍这三个维度的不同特性。
Heap/Direct
Heap/Direct 就是堆内和堆外内存。
Heap 指的是在 JVM 堆内分配,底层依赖的是字节数据;
Direct 则是堆外内存,不受 JVM 限制,分配方式依赖 JDK 底层的 ByteBuffer。
Pooled/Unpooled
Pooled/Unpooled 表示池化还是非池化内存。
Pooled 是从预先分配好的内存中取出,使用完可以放回 ByteBuf 内存池,等待下一次分配。
而 Unpooled 是直接调用系统 API 去申请内存,确保能够被 JVM GC 管理回收。
Unsafe/非 Unsafe
Unsafe/非 Unsafe 的区别在于操作方式是否安全。
Unsafe 表示每次调用 JDK 的 Unsafe 对象操作物理内存,依赖 offset + index 的方式操作数据。
非 Unsafe 则不需要依赖 JDK 的 Unsafe 对象,直接通过数组下标的方式操作数据。
ByteBuf 核心 API
我会分为指针操作、数据读写和内存管理三个方面介绍 ByteBuf 的核心 API。在开始讲解 API 的使用方法之前,先回顾下之前我们实现的自定义解码器,以便于加深对 ByteBuf API 的理解。
java public final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // 判断 ByteBuf 可读取字节 if (in.readableBytes() < 14) { return; } in.markReaderIndex(); // 标记 ByteBuf 读指针位置 in.skipBytes(2); // 跳过魔数 in.skipBytes(1); // 跳过协议版本号 //获取序列化方式 byte serializeType = in.readByte(); in.skipBytes(1); // 跳过报文类型 in.skipBytes(1); // 跳过状态字段 in.skipBytes(4); // 跳过保留字段 //获取数据长度 int dataLength = in.readInt(); if (in.readableBytes() < dataLength) { in.resetReaderIndex(); // 重置 ByteBuf 读指针位置 return; } //根据数据长度构造 byte[] byte[] data = new byte[dataLength]; in.readBytes(data); //根据序列化方式 反序列化 SerializeService serializeService = getSerializeServiceByType(serializeType); //反序列化为对象 Object obj = serializeService.deserialize(data); if (obj != null) { out.add(obj); } }
指针操作 API
readerIndex()
readerIndex() 返回的是当前的读指针的 readerIndex 位置
writeIndex()
writeIndex() 返回的当前写指针 writeIndex 位置。
markReaderIndex()
markReaderIndex() 用于保存当前readerIndex 的位置。
resetReaderIndex()
resetReaderIndex() 则将当前 readerIndex 重置为之前markReaderIndex保存的位置。
markReaderIndex和resetReaderIndex这对 API 在实现协议解码时最为常用,例如在上述自定义解码器的源码中,在读取协议内容长度字段之前,先使用 markReaderIndex() 保存了 readerIndex 的位置,如果 ByteBuf 中可读字节数小于长度字段的值,则表示 ByteBuf 还没有一个完整的数据包,此时直接使用 resetReaderIndex() 重置readerIndex 的位置。
此外对应的写指针操作还有 markWriterIndex() 和 resetWriterIndex(),与读指针的操作类似,我就不再一一赘述了。
数据读写 API
isReadable()
isReadable() 用于判断 ByteBuf 是否可读,如果 writerIndex 大于 readerIndex,那么 ByteBuf 是可读的,否则是不可读状态。
readableBytes()
readableBytes() 可以获取 ByteBuf 当前可读取的字节数,可以通过 writerIndex - readerIndex 计算得到
readBytes(byte[] dst)
writeBytes(byte[] src)
readBytes() 和 writeBytes() 是两个最为常用的方法。
readBytes() 是将 ByteBuf 的数据读取相应的字节到字节数组dst 中,readBytes() 经常结合 readableBytes() 一起使用,
dst 字节数组的大小通常等于 readableBytes() 的大小。
java //收到的消息 ByteBuf bytebuf = (ByteBuf) msg; //构建字节数组 byte[] req = new byte[bytebuf.readableBytes()]; //将ByteBuf中的数据读取到字节数组中 bytebuf.readBytes(req); String body = new String(req, "UTF-8");
readByte()
writeByte(int value)
readByte() 是从 ByteBuf 中读取一个字节,相应的 readerIndex + 1;同理 writeByte 是向 ByteBuf 写入一个字节,相应的 writerIndex + 1。
类似的 Netty 提供了 8 种基础数据类型的读取和写入,例如 readChar()、readShort()、readInt()、readLong()、writeChar()、writeShort()、writeInt()、writeLong() 等,在这里就不详细展开了。
getByte(int index)
setByte(int index, int value)
readByte() 是从 ByteBuf 中读取一个字节,相应的 readerIndex + 1;
同理 writeByte 是向 ByteBuf 写入一个字节,相应的 writerIndex + 1。
与 readByte() 和 writeByte() 相对应的还有 getByte() 和 setByte(),get/set 系列方法也提供了 8 种基础类型的读写,那么这两个系列的方法有什么区别呢?
read/write 方法在读写时会改变readerIndex 和 writerIndex 指针,而 get/set 方法则不会改变指针位置。
release() & retain()
之前已经介绍了引用计数的基本概念,每调用一次 release() 引用计数减 1,每调用一次 retain() 引用计数加 1。
slice()
返回ByteBuf可读字节的一部分。 修改返回的ByteBuf或当前ByteBuf会影响彼此的内容, 同时它们维护单独的索引和标记,此方法不会修改当前ByteBuf的readerIndex或writerIndex *另请注意,此方法不会调用{@link #retain()},因此不会增加引用计数
slice() 等同于 slice(buffer.readerIndex(), buffer.readableBytes()),默认截取 readerIndex 到 writerIndex 之间的数据,最大容量 maxCapacity 为原始 ByteBuf 的可读取字节数,底层分配的内存、引用计数都与原始的 ByteBuf 共享。
duplicate()
duplicate() 与 slice() 不同的是,duplicate()截取的是整个原始 ByteBuf 信息,底层分配的内存、引用计数也是共享的。如果向 duplicate() 分配出来的 ByteBuf 写入数据,那么都会影响到原始的 ByteBuf 底层数据。
返回共享当前ByteBuf信息的新ByteBuf,他们使用独立的readIndex writeIndex markIndex *修改返回的ByteBuf或当前ByteBuf会影响彼此的内容,同时它们维护单独的索引和标记, *此方法不会修改当前ByteBuf的readerIndex或writerIndex, 另请注意,此方法不会调用{@link #retain()},因此不会增加引用计数
copy()
会从原始的 ByteBuf 中拷贝所有信息,所有数据都是独立的,向 copy() 分配的 ByteBuf 中写数据不会影响原始的 ByteBuf。
返回ByteBuf的可读字节的拷贝。修改返回的ByteBuf内容与当前ByteBuf完全不会相互影响。
此方法不会修改当前ByteBuf的readerIndex或writerIndex
到底为止,ByteBuf 的核心 API 我们基本已经介绍完了,ByteBuf 读写指针分离的小设计,确实带来了很多实用和便利的功能,在开发的过程中不必再去想着 flip、rewind 这种头疼的操作了。
内存管理 API
未池化堆内存
java ByteBuf heapByteBuf = Unpooled.buffer(10);
未池化直接内存
java ByteBuf directByteBuf = Unpooled.directBuffer(10);
池化堆内存
java PooledByteBufAllocator allocator = new PooledByteBufAllocator(false); ByteBuf pHeapByteBuf = allocator.buffer();
池化直接内存
java PooledByteBufAllocator allocator2 = new PooledByteBufAllocator(true);
ByteBuf 实战演练
学习完 ByteBuf 的内部构造以及核心 API 之后,我们下面通过一个简单的示例演示一下 ByteBuf 应该如何使用,代码如下所示。
java public class ByteBufTest { public static void main(String[] args) { // static final ByteBufAllocator DEFAULT_ALLOCATOR; // 根据allocType创建不同的分配器 如果没有值默认使用池化技术 // alloc = UnpooledByteBufAllocator.DEFAULT; // alloc = PooledByteBufAllocator.DEFAULT; // 初始是6 最大是10 ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(6, 10); printByteBufInfo("ByteBufAllocator.buffer(5, 10)", buffer); buffer.writeBytes(new byte[]{1, 2}); printByteBufInfo("write 2 Bytes", buffer); buffer.writeInt(100); printByteBufInfo("write Int 100", buffer); buffer.writeBytes(new byte[]{3, 4, 5}); printByteBufInfo("write 3 Bytes", buffer); byte[] read = new byte[buffer.readableBytes()]; buffer.readBytes(read); printByteBufInfo("readBytes(" + buffer.readableBytes() + ")", buffer); printByteBufInfo("BeforeGetAndSet", buffer); System.out.println("getInt(2): " + buffer.getInt(2)); buffer.setByte(1, 0); System.out.println("getByte(1): " + buffer.getByte(1)); printByteBufInfo("AfterGetAndSet", buffer); } private static void printByteBufInfo(String step, ByteBuf buffer) { System.out.println("------" + step + "-----"); System.out.println("readerIndex(): " + buffer.readerIndex()); System.out.println("writerIndex(): " + buffer.writerIndex()); System.out.println("isReadable(): " + buffer.isReadable()); System.out.println("isWritable(): " + buffer.isWritable()); System.out.println("readableBytes(): " + buffer.readableBytes()); System.out.println("writableBytes(): " + buffer.writableBytes()); System.out.println("maxWritableBytes(): " + buffer.maxWritableBytes()); System.out.println("capacity(): " + buffer.capacity()); System.out.println("maxCapacity(): " + buffer.maxCapacity()); } }
java ------ByteBufAllocator.buffer(5, 10)----- readerIndex(): 0 writerIndex(): 0 isReadable(): false isWritable(): true readableBytes(): 0 writableBytes(): 6 maxWritableBytes(): 10 capacity(): 6 maxCapacity(): 10 ------write 2 Bytes----- readerIndex(): 0 writerIndex(): 2 isReadable(): true isWritable(): true readableBytes(): 2 writableBytes(): 4 maxWritableBytes(): 8 capacity(): 6 maxCapacity(): 10 ------write Int 100----- readerIndex(): 0 writerIndex(): 6 isReadable(): true isWritable(): false readableBytes(): 6 writableBytes(): 0 maxWritableBytes(): 4 capacity(): 6 maxCapacity(): 10 ------write 3 Bytes----- readerIndex(): 0 writerIndex(): 9 isReadable(): true isWritable(): true readableBytes(): 9 writableBytes(): 1 maxWritableBytes(): 1 capacity(): 10 maxCapacity(): 10 ------readBytes(0)----- readerIndex(): 9 writerIndex(): 9 isReadable(): false isWritable(): true readableBytes(): 0 writableBytes(): 1 maxWritableBytes(): 1 capacity(): 10 maxCapacity(): 10 ------BeforeGetAndSet----- readerIndex(): 9 writerIndex(): 9 isReadable(): false isWritable(): true readableBytes(): 0 writableBytes(): 1 maxWritableBytes(): 1 capacity(): 10 maxCapacity(): 10 getInt(2): 100 getByte(1): 0 ------AfterGetAndSet----- readerIndex(): 9 writerIndex(): 9 isReadable(): false isWritable(): true readableBytes(): 0 writableBytes(): 1 maxWritableBytes(): 1 capacity(): 10 maxCapacity(): 10
结合代码示例,我们总结一下 ByteBuf API 使用时的注意点:
- write 系列方法会改变 writerIndex 位置,当 writerIndex 等于 capacity 的时候,Buffer 置为不可写状态;
- 向不可写 Buffer 写入数据时,Buffer 会尝试扩容,但是扩容后 capacity 最大不能超过 maxCapacity,如果写入的数据超过 maxCapacity,程序会直接抛出异常;
- read 系列方法会改变 readerIndex 位置,get/set 系列方法不会改变 readerIndex/writerIndex 位置。
总结
Netty 强大的数据容器 ByteBuf,它不仅解决了 JDK NIO 中 ByteBuffer 的缺陷,而且提供了易用性更强的接口。很多开发者已经使用 ByteBuf 代替 ByteBuffer,即便他没有在写一个网络应用,也会单独使用 ByteBuf。ByteBuf 作为 Netty 中最基础的数据结构。
相关文章:
![](https://img-blog.csdnimg.cn/img_convert/8f32fbb06340d9e06a095ff8c1655f02.png?x-oss-process=image/watermark,image_bG9nby9jc2RuXzEucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLGhfMjM3,g_se,x_0,y_0,t_100)
18.Netty源码之ByteBuf 详解
highlight: arduino-light ByteBuf 是 Netty 的数据容器,所有网络通信中字节流的传输都是通过 ByteBuf 完成的。 然而 JDK NIO 包中已经提供了类似的 ByteBuffer 类,为什么 Netty 还要去重复造轮子呢?本节课我会详细地讲解 ByteBuf。 JDK NIO…...
![](https://img-blog.csdnimg.cn/img_convert/ebf4d06eb8a9ce27d7a8e123902b6271.png)
#P0999. [NOIP2008普及组] 排座椅
题目描述 上课的时候总会有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情。不过,班主任小雪发现了一些有趣的现象,当同学们的座次确定下来之后,只有有限的 DD 对同学上课时会交头接耳。 同学们在教室中坐…...
![](https://img-blog.csdnimg.cn/a5db2554242f46f0a60c5a0e49d38f3d.png)
Sentinel 容灾中心的使用
Sentinel 容灾中心的使用 往期文章 Nacos环境搭建Nacos注册中心的使用Nacos配置中心的使用 熔断/限流结果 Jar 生产者 spring-cloud-alibaba:2021.0.4.0 spring-boot:2.6.8 spring-cloud-loadbalancer:3.1.3 sentinel:2021.0…...
![](https://www.ngui.cc/images/no-images.jpg)
深度学习中简易FC和CNN搭建
TensorFlow是由谷歌开发的PyTorch是由Facebook人工智能研究院(Facebook AI Research)开发的 Torch和cuda版本的对应,手动安装较好 全连接FC(Batch*Num) 搭建建议网络: from torch import nnclass Mnist_NN(nn.Module):def __i…...
![](https://img-blog.csdnimg.cn/4dbc11d55cc44262aa33a76e4b01503f.png)
【多模态】20、OVR-CNN | 使用 caption 来实现开放词汇目标检测
文章目录 一、背景二、方法2.1 学习 视觉-语义 空间2.2 学习开放词汇目标检测 三、效果 论文:Open-Vocabulary Object Detection Using Captions 代码:https://github.com/alirezazareian/ovr-cnn 出处:CVPR2021 Oral 一、背景 目标检测数…...
![](https://img-blog.csdnimg.cn/0163133173b74d3bbd65730e0d5cca88.png)
网络编程 IO多路复用 [select版] (TCP网络聊天室)
//head.h 头文件 //TcpGrpSer.c 服务器端 //TcpGrpUsr.c 客户端 select函数 功能:阻塞函数,让内核去监测集合中的文件描述符是否准备就绪,若准备就绪则解除阻塞。 原型: #include <sys/select.…...
![](https://img-blog.csdnimg.cn/8e425d8eb07c4628899b6047061239ce.png)
数学建模学习(7):单目标和多目标规划
优化问题描述 优化 优化算法是指在满足一定条件下,在众多方案中或者参数中最优方案,或者参数值,以使得某个或者多个功能指标达到最优,或使得系统的某些性能指标达到最大值或者最小值 线性规划 线性规划是指目标函数和约束都是线性的情况 [x,fval]linprog(f,A,b,Aeq,Beq,LB,U…...
![](https://img-blog.csdnimg.cn/6d3c142ccb734d88898f017e83966431.png)
Element UI如何自定义样式
简介 Element UI是一套非常完善的前端组件库,但是如何个性化定制其中的组件样式呢?今天我们就来聊一聊这个 举例 就拿最常见的按钮el-button来举例,一般来说默认是蓝底白字。效果图如下 可是我们想个性化定制,让他成为粉底红字应…...
![](https://www.ngui.cc/images/no-images.jpg)
protobuf入门实践2
如何在proto中定义一个rpc服务? syntax "proto3"; //声明protobuf的版本package fixbug; //声明了代码所在的包 (对于C来说就是namespace)//下面的选项,表示生成service服务类和rpc方法描述, 默认是不生成的 option cc_generi…...
![](https://www.ngui.cc/images/no-images.jpg)
adb shell使用总结
文章目录 日志记录系统概览adb 使用方式 adb命令日志过滤按照告警等级进行过滤按照tag进行过滤根据告警等级和tag进行联合过滤屏蔽系统和其他App干扰,仅仅关注App自身日志 查看“当前页面”Activity文件传输截屏和录屏安装、卸载App启动activity其他 日志记录系统概…...
![](https://www.ngui.cc/images/no-images.jpg)
UG NX二次开发(C++)-Tag的含义、Tag类型与其他的转换
文章目录 1、前言2、Tag号的含义3、tag_t转换为int3、TaggedObject与Tag转换3.1 TaggedObject定义3.2 TaggedObject获取Tag3.3 根据Tag获取TaggedObject4.Tag与double类型的转换1、前言 在UG NX中,每个对象对应一个tag号,C++中,其类型是tag_t,一般是5位或者6位的int数字,…...
![](https://img-blog.csdnimg.cn/cbaac9dbb2744327a54b14079b940127.png#pic_center)
Informer 论文学习笔记
论文:《Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting》 代码:https://github.com/zhouhaoyi/Informer2020 地址:https://arxiv.org/abs/2012.07436v3 特点: 实现时间与空间复杂度为 O ( …...
![](https://img-blog.csdnimg.cn/65a55fb793f440b2b350d5935553ef05.png)
c语言位段知识详解
本篇文章带来位段相关知识详细讲解! 如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作的动力之源,让我们一起加油,一起奔跑,让我们顶峰相见!!! 目录 一.什么是…...
![](https://img-blog.csdnimg.cn/e21512a218df4fedbcfd4625ca911f13.png)
FFmpeg aresample_swr_opts的解析
ffmpeg option的解析 aresample_swr_opts是AVFilterGraph中的option。 static const AVOption filtergraph_options[] {{ "thread_type", "Allowed thread types", OFFSET(thread_type), AV_OPT_TYPE_FLAGS,{ .i64 AVFILTER_THREAD_SLICE }, 0, INT_MA…...
![](https://img-blog.csdnimg.cn/ee04fdbfc7544ed2bf2853523a966652.png)
CAN学习笔记3:STM32 CAN控制器介绍
STM32 CAN控制器 1 概述 STM32 CAN控制器(bxCAN),支持CAN 2.0A 和 CAN 2.0B Active版本协议。CAN 2.0A 只能处理标准数据帧且扩展帧的内容会识别错误,而CAN 2.0B Active 可以处理标准数据帧和扩展数据帧。 2 bxCAN 特性 波特率…...
![](https://img-blog.csdnimg.cn/221634902fe24de286adb7360d4b90c4.png)
软工导论知识框架(二)结构化的需求分析
本章节涉及很多重要图表的制作,如ER图、数据流图、状态转换图、数据字典的书写等,对初学者来说比较生僻,本贴只介绍基础的轮廓,后面会有单独的帖子详解各图表如何绘制。 一.结构化的软件开发方法:结构化的分析、设计、…...
![](https://www.ngui.cc/images/no-images.jpg)
[SQL挖掘机] - 算术函数 - abs
介绍: 当谈到 SQL 中的 abs 函数时,它是一个用于计算数值的绝对值的函数。“abs” 代表 “absolute”(绝对),因此 abs 函数的作用是返回一个给定数值的非负值(即该数值的绝对值)。 abs 函数接受一个参数&a…...
![](https://www.ngui.cc/images/no-images.jpg)
vue拼接html点击事件不生效
vue使用ts,拼接html,点击事件不生效或者报 is not defined 点击事件要用onclick 不是click let data{name:测,id:123} let conHtml <div> "名称:" data.name "<br>" <p class"cursor blue&quo…...
![](https://www.ngui.cc/images/no-images.jpg)
【Spring】Spring之依赖注入源码解析
1 Spring注入方式 1.1 手动注入 xml中定义Bean,程序员手动给某个属性赋值。 set方式注入 <bean name"userService" class"com.firechou.service.UserService"><property name"orderService" ref"orderService"…...
![](https://img-blog.csdnimg.cn/1fa0db9e419045efa43314747e28ec93.png)
【微软知识】微软相关技术知识分享
微软技术领域 一、微软操作系统: 微软的操作系统主要是 Windows 系列,包括 Windows 10、Windows Server 等。了解 Windows 操作系统的基本使用、配置和故障排除是非常重要的。微软操作系统(Microsoft System)是美国微软开发的Wi…...
![](https://www.ngui.cc/images/no-images.jpg)
12.python设计模式【观察者模式】
内容:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖于它的对象得到通知并被自动更新。观者者模式又称为“发布-订阅”模式。比如天气预报,气象局分发气象数据。 角色: 抽象主题…...
![](https://img-blog.csdnimg.cn/b56c463b171a4bdc91b52079f64d954e.png)
重生之我要学C++第五天
这篇文章主要内容是构造函数的初始化列表以及运算符重载在顺序表中的简单应用,运算符重载实现自定义类型的流插入流提取。希望对大家有所帮助,点赞收藏评论,支持一下吧! 目录 构造函数进阶理解 1.内置类型成员在参数列表中的定义 …...
![](https://img-blog.csdnimg.cn/9b03c8afdef54f4cbe622bc51aa6708c.png)
复习之linux高级存储管理
一、lvm----逻辑卷管理 1.lvm定义 LVM是 Logical Volume Manager(逻辑卷管理)的简写,它是Linux环境下对磁盘分区进行管理的一种机制。 逻辑卷管理器(LogicalVolumeManager)本质上是一个虚拟设备驱动,是在内核中块设备和物理设备…...
![](https://img-blog.csdnimg.cn/67a87e3d031843f08d306cbe693da372.png)
HuggingGPT Solving AI Tasks with ChatGPT and its Friends in Hugging Face
总述 HuggingGPT 让LLM发挥向路由器一样的作用,让LLM来选择调用那个专业的模型来执行任务。HuggingGPT搭建LLM和专业AI模型的桥梁。Language is a generic interface for LLMs to connect AI models 四个阶段 Task Planning: 将复杂的任务分解。但是这里…...
![](https://www.ngui.cc/images/no-images.jpg)
java工程重写jar包中class类覆盖问题
结论:直接在程序中复写jar中的类即可 原因:一般我java工程是运行在tomcat容器中,tomcat容易在加载我们工程类和jar包是的优先级为: 我们工程的class 先于 我们工程lib下的jar 重复的类只加载一次,加载我们复写后的类后…...
![](https://www.ngui.cc/images/no-images.jpg)
Mybatis基于注解与XML开发
文章目录 1 关于SpringBoot2 关于MyBatis2.1 MyBatis概述2.2 MyBatis核心思想2.3 MyBatis使用流程3 MyBatis配置SQL方式3.1 基于注解方式3.1.1 说明3.1.2 使用流程3.1.3 常用注解 3.2 基于XML方式3.2.1 相比注解优势3.2.2 使用流程3.2.3 常用标签 1 关于SpringBoot SpringBoot…...
![](https://img-blog.csdnimg.cn/img_convert/ace1d5697b6a1e7ac748291ab22098dd.jpeg)
数字化转型导师坚鹏:数字化时代扩大内需的8大具体建议
在日新月异的数字化时代、复杂多变的国际化环境下,扩大内需成为推动经济发展的国家战略,如何真正地扩大内需?结合本人15年的管理咨询经验及目前实际情况的深入研究,提出以下8大具体建议: 1、制定国民收入倍增计划。结…...
![](https://img-blog.csdnimg.cn/84023e7db82946c4b9c48605b98719cc.png)
M1/M2 通过VM Fusion安装Win11 ARM,解决联网和文件传输
前言 最近新入了Macmini M2,但是以前的老电脑的虚拟机运行不起来了。😅,实际上用过K8S的时候,会发现部分镜像也跑不起来,X86的架构和ARM实际上还是有很多隐形兼容问题。所以只能重新安装ARM Win11,幸好微软…...
![](https://www.ngui.cc/images/no-images.jpg)
Linux中显示系统正在运行的进程的命令
2023年7月29日,周六上午 在Linux中,ps命令用于显示当前系统中正在运行的进程, ps应该是processes snapshot(进程快照)的缩写。 以下是ps命令的常见用法和示例: 显示当前用户的所有进程:ps 显示…...
![](https://img-blog.csdnimg.cn/683bf56bb89446dc92989163534b93f3.png)
vite中安装less
使用vite创建的项目,默认是没有安装less的 如果直接在style中书写less 会报下图错误: 解决方案: npm install --save less 在package.json中查看是否安装成功 安装完成刷新页面,问题解决...
![](https://img-blog.csdnimg.cn/img_convert/a36003175252b610fc39a2a0e1a809d1.png)
如何用api做网站/百度网页版入口链接
说到地图面积大的游戏,《无人深空》肯定榜上有名。不过这款游戏有几分投机取巧的意味,它是一款以太空探索为主题的冒险生存游戏。它拥有1844亿亿颗星球,就算玩家一秒钟就逛完了一颗星球那只要也要5850亿年的时间。只不过它的游戏地图是随机生…...
![](/images/no-images.jpg)
数字营销证书/高级seo
因为看到很多公司招聘需要有linux平台下的开发经验,所以今天在笔记本上装了一个Kubuntu作为以后学习的平台。本文采用的是win7Kubuntu双系统的模式,尽管最后安装成功了,但是中间有很多波折在此记下来以备以后再次发生同样的事情。首先&#x…...
![](/images/no-images.jpg)
品牌网站建设精湛磐石网络/市场调研与分析
笔记本或者显示器 默认设置125%或者150%缩放,导致布局错乱的解决方法 现在很多14寸的笔记本,出厂默认就是150%的显示。导致很多时候我们的项目,自己开发的时候都是按照100%比例来开发的,上线了就会发现这个问题 解决方案 vue项目u…...
![](/images/no-images.jpg)
莆田哪里有做网站的/如何进行搜索引擎优化
不敢发首页,放在家里纯属观摩 上回:博客园的故事-愤怒时的呐喊 上回说到,摩登女郎并不安分,偶尔制造“门事件”扩大影响,一时和尚门看到也颇感新鲜,竞相传阅,一时不亦乐乎…...
![](https://img-my.csdn.net/uploads/201205/05/1336185963_4905.jpg)
镇江网站建设远航科技/市场策划方案
NUMA与SMP SMP(Symmetric Multi-Processor),即对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同。其主要特征是共享,包含对CPU,内存,I/O等进行共享。SMP的优点是能够保证内存一…...
![](/images/no-images.jpg)
兰州市城乡建设局网站公布的信息/万能导航网
Given an array where elements are sorted in ascending order, convert it to a height balanced BST. Subscribe to see which companies asked this question 要点就是找到中心点,然后分别递归构造左边的数和右边的数 TreeNode* sortedArrayToBST(vector<int…...