【Java】Netty中ByteBuf学习笔记

news2024/10/6 22:25:35

文章目录

    • 1) ByteBuf创建与自动扩容
    • 2)直接内存 vs 堆内存
    • 3)池化 vs 非池化
    • 4)组成
    • 5)写入
    • 6)扩容
    • 7)读取
    • 8)retain & release
    • 9)slice
    • 10)duplicate
    • 11)copy
    • 12)CompositeByteBuf
    • 13)Unpooled
    • 💡 ByteBuf 优势

黑马程序员Netty ByteBuf部分的学习笔记,视频链接:https://www.bilibili.com/video/BV1py4y1E7oA?t=1.6&p=79

1) ByteBuf创建与自动扩容

ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(10);
log(buffer);

上面代码创建了一个默认的 ByteBuf(池化基于直接内存的 ByteBuf),初始容量是 10

输出

read index:0 write index:0 capacity:10

其中 log 方法(工具方法,直接拿来用即可)参考如下

private static void log(ByteBuf buffer) {
    int length = buffer.readableBytes();
    int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
    StringBuilder buf = new StringBuilder(rows * 80 * 2)
        .append("read index:").append(buffer.readerIndex())
        .append(" write index:").append(buffer.writerIndex())
        .append(" capacity:").append(buffer.capacity())
        .append(NEWLINE);
    appendPrettyHexDump(buf, buffer);
    System.out.println(buf.toString());
}

自动扩容示例:

        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();// 不指定容量则默认256,butebuf可以自动扩容
        System.out.println(buf);
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 300; i++) {
            stringBuilder.append("a");
        }
        buf.writeBytes(stringBuilder.toString().getBytes());
        System.out.println(buf);

输出:(由256扩容到512)
在这里插入图片描述
使用工具方法查看

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;

public class TestByteBuf {
    public static void main(String[] args) {

        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();// 不指定容量则默认256,butebuf可以自动扩容
        log(buf);
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 300; i++) {
            stringBuilder.append("a");
        }
        buf.writeBytes(stringBuilder.toString().getBytes());
        log(buf);
    }

    /**
     * ByteBuf调试工具方法,直接拿来用
     * @param buffer
     */
    public static void log(ByteBuf buffer) {
        int length = buffer.readableBytes();
        int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
        StringBuilder buf = new StringBuilder(rows * 80 * 2)
                .append("read index:").append(buffer.readerIndex())
                .append(" write index:").append(buffer.writerIndex())
                .append(" capacity:").append(buffer.capacity())
                .append(NEWLINE);
        appendPrettyHexDump(buf, buffer);
        System.out.println(buf.toString());
    }
}

在这里插入图片描述

2)直接内存 vs 堆内存

可以使用下面的代码来创建池化基于堆的 ByteBuf

ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);

也可以使用下面的代码来创建池化基于直接内存的 ByteBuf

ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
  • 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
  • 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放

3)池化 vs 非池化

池化的最大意义在于可以重用 ByteBuf,优点有

  • 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
  • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
  • 高并发时,池化功能更节约内存,减少内存溢出的可能

池化功能是否开启,可以通过下面的系统环境变量来设置

-Dio.netty.allocator.type={unpooled|pooled}
  • 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现
  • 4.1 之前,池化功能还不成熟,默认是非池化实现
public class TestByteBuf {
    public static void main(String[] args) {
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();// 不指定容量则默认256,butebuf可以自动扩容
        System.out.println(buf.getClass());
    }
}

在这里插入图片描述
可以看到使用了池化,直接内存。

如何调整为非池化?
在Run/Debug Configurations 添加VM options参数:
在这里插入图片描述
参数如下:
在这里插入图片描述
再次运行:

在这里插入图片描述

4)组成

ByteBuf 由四部分组成

最开始读写指针都在 0 位置

5)写入

方法列表,省略一些不重要的方法

方法签名含义备注
writeBoolean(boolean value)写入 boolean 值用一字节 01|00 代表 true|false
writeByte(int value)写入 byte 值
writeShort(int value)写入 short 值
writeInt(int value)写入 int 值Big Endian(大端写入),即 0x250,写入后 00 00 02 50
writeIntLE(int value)写入 int 值Little Endian(小端写入),即 0x250,写入后 50 02 00 00
writeLong(long value)写入 long 值
writeChar(int value)写入 char 值
writeFloat(float value)写入 float 值
writeDouble(double value)写入 double 值
writeBytes(ByteBuf src)写入 netty 的 ByteBuf
writeBytes(byte[] src)写入 byte[]
writeBytes(ByteBuffer src)写入 nio 的 ByteBuffer
int writeCharSequence(CharSequence sequence, Charset charset)写入字符串

注意

  • 这些方法的未指明返回值的,其返回值都是 ByteBuf,意味着可以链式调用
  • 网络传输,默认习惯是 Big Endian

先写入 4 个字节

buffer.writeBytes(new byte[]{1, 2, 3, 4});
log(buffer);

结果是

read index:0 write index:4 capacity:10
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04                                     |....            |
+--------+-------------------------------------------------+----------------+

再写入一个 int 整数,也是 4 个字节

buffer.writeInt(5);
log(buffer);

结果是

read index:0 write index:8 capacity:10
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 00 00 00 05                         |........        |
+--------+-------------------------------------------------+----------------+

还有一类方法是 set 开头的一系列方法,也可以写入数据,但不会改变写指针位置

6)扩容

再写入一个 int 整数时,容量不够了(初始容量是 10),这时会引发扩容

buffer.writeInt(6);
log(buffer);

扩容规则是

  • 如何写入后数据大小未超过 512,则选择下一个 16 的整数倍,例如写入后大小为 12 ,则扩容后 capacity 是 16
  • 如果写入后数据大小超过 512,则选择下一个2^n
    例如写入后大小为 513,则扩容后 capacity 是 2^10=1024(2^9=512 已经不够了)
  • 扩容不能超过 max capacity 会报错

结果是

read index:0 write index:12 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 00 00 00 05 00 00 00 06             |............    |
+--------+-------------------------------------------------+----------------+

7)读取

紧接着上面的内容,进行读取:

例如读了 4 次,每次一个字节

System.out.println(buffer.readByte());
System.out.println(buffer.readByte());
System.out.println(buffer.readByte());
System.out.println(buffer.readByte());
log(buffer);

读过的内容,就属于废弃部分了,再读只能读那些尚未读取的部分。

1
2
3
4
read index:4 write index:12 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 05 00 00 00 06                         |........        |
+--------+-------------------------------------------------+----------------+

如果需要重复读取 int 整数 5,怎么办?

可以在 read 前先做个标记 mark

buffer.markReaderIndex();
System.out.println(buffer.readInt());
log(buffer);

结果

5
read index:8 write index:12 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 06                                     |....            |
+--------+-------------------------------------------------+----------------+

这时要重复读取的话,重置到标记位置 reset

buffer.resetReaderIndex();
log(buffer);

这时

read index:4 write index:12 capacity:16
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 05 00 00 00 06                         |........        |
+--------+-------------------------------------------------+----------------+

还有种办法是采用 get 开头的一系列方法,这些方法不会改变 read index

8)retain & release

由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。

  • UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
  • UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
  • PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存

回收内存的源码实现,请关注下面方法的不同实现

protected abstract void deallocate()

Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted接口

  • 每个 ByteBuf 对象的初始计数为 1。
  • 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收。
  • 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收。
  • 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用。

谁来负责 release 呢?

不是我们想象的(一般情况下)

ByteBuf buf = ...
try {
    ...
} finally {
    buf.release();
}

请思考,因为 pipeline 的存在,一般需要将 ByteBuf 传递给下一个 ChannelHandler,如果在 finally 中 release 了,就失去了传递性(当然,如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命,那么便无须再传递)

基本规则是,谁是最后使用者,谁负责 release,详细分析如下

  • 起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))
  • 入站 ByteBuf 处理原则
    • 对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release
    • 将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release
    • 如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release
    • 注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release
    • 假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)
  • 出站 ByteBuf 处理原则
    • 出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release
  • 异常处理原则
    • 有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true

TailContext 释放未处理消息逻辑

// io.netty.channel.DefaultChannelPipeline#onUnhandledInboundMessage(java.lang.Object)
protected void onUnhandledInboundMessage(Object msg) {
    try {
        logger.debug(
            "Discarded inbound message {} that reached at the tail of the pipeline. " +
            "Please check your pipeline configuration.", msg);
    } finally {
        ReferenceCountUtil.release(msg);
    }
}

具体代码

// io.netty.util.ReferenceCountUtil#release(java.lang.Object)
public static boolean release(Object msg) {
    if (msg instanceof ReferenceCounted) {
        return ((ReferenceCounted) msg).release();
    }
    return false;
}

9)slice

【零拷贝】的体现之一,对原始 ByteBuf 进行切片成多个 ByteBuf,切片后的 ByteBuf 并没有发生内存复制,还是使用原始 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 read,write 指针

在这里插入图片描述

例,原始 ByteBuf 进行一些初始操作

ByteBuf origin = ByteBufAllocator.DEFAULT.buffer(10);
origin.writeBytes(new byte[]{1, 2, 3, 4});
origin.readByte();
System.out.println(ByteBufUtil.prettyHexDump(origin));

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 02 03 04                                        |...             |
+--------+-------------------------------------------------+----------------+

这时调用 slice 进行切片,无参 slice 是从原始 ByteBuf 的 read index 到 write index 之间的内容进行切片,切片后的 max capacity 被固定为这个区间的大小,因此不能追加 write

ByteBuf slice = origin.slice(); // 无参方法。有参方法为slice(index,length)起始位置和切片长度
System.out.println(ByteBufUtil.prettyHexDump(slice));
// slice.writeByte(5); 如果执行,会报 IndexOutOfBoundsException 异常

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 02 03 04                                        |...             |
+--------+-------------------------------------------------+----------------+

如果原始 ByteBuf 再次读操作(又读了一个字节)

origin.readByte();
System.out.println(ByteBufUtil.prettyHexDump(origin));

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 03 04                                           |..              |
+--------+-------------------------------------------------+----------------+

这时的 slice 不受影响,因为它有独立的读写指针

System.out.println(ByteBufUtil.prettyHexDump(slice));

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 02 03 04                                        |...             |
+--------+-------------------------------------------------+----------------+

如果 slice 的内容发生了更改

slice.setByte(2, 5); //设置第2个索引位置的值为5
System.out.println(ByteBufUtil.prettyHexDump(slice));

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 02 03 05                                        |...             |
+--------+-------------------------------------------------+----------------+

这时,原始 ByteBuf 也会受影响,因为底层都是同一块内存

System.out.println(ByteBufUtil.prettyHexDump(origin));

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 03 05                                           |..              |
+--------+-------------------------------------------------+----------------+

10)duplicate

【零拷贝】的体现之一,就好比截取了原始 ByteBuf 所有内容,并且没有 max capacity 的限制,也是与原始 ByteBuf 使用同一块底层内存,只是读写指针是独立的

11)copy

会将底层内存数据进行深拷贝,因此无论读写,都与原始 ByteBuf 无关

12)CompositeByteBuf

【零拷贝】的体现之一,可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免拷贝

有两个 ByteBuf 如下

ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);
buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(5);
buf2.writeBytes(new byte[]{6, 7, 8, 9, 10});
System.out.println(ByteBufUtil.prettyHexDump(buf1));
System.out.println(ByteBufUtil.prettyHexDump(buf2));

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 05                                  |.....           |
+--------+-------------------------------------------------+----------------+
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 06 07 08 09 0a                                  |.....           |
+--------+-------------------------------------------------+----------------+

现在需要一个新的 ByteBuf,内容来自于刚才的 buf1 和 buf2,如何实现?

方法1:

ByteBuf buf3 = ByteBufAllocator.DEFAULT.buffer(buf1.readableBytes()+buf2.readableBytes());
buf3.writeBytes(buf1);
buf3.writeBytes(buf2);
System.out.println(ByteBufUtil.prettyHexDump(buf3));

结果

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 05 06 07 08 09 0a                   |..........      |
+--------+-------------------------------------------------+----------------+

这种方法好不好?回答是不太好,因为进行了数据的内存复制操作

方法2:

CompositeByteBuf buf3 = ByteBufAllocator.DEFAULT.compositeBuffer();
// true 表示增加新的 ByteBuf 自动递增 write index, 否则 write index 会始终为 0
buf3.addComponents(true, buf1, buf2);

结果是一样的

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 05 06 07 08 09 0a                   |..........      |
+--------+-------------------------------------------------+----------------+

CompositeByteBuf 是一个组合的 ByteBuf,它内部维护了一个 Component 数组,每个 Component 管理一个 ByteBuf,记录了这个 ByteBuf 相对于整体偏移量等信息,代表着整体中某一段的数据。

  • 优点,对外是一个虚拟视图,组合这些 ByteBuf 不会产生内存复制
  • 缺点,复杂了很多,多次操作会带来性能的损耗

13)Unpooled

Unpooled 是一个工具类,类如其名,提供了非池化的 ByteBuf 创建、组合、复制等操作

这里仅介绍其跟【零拷贝】相关的 wrappedBuffer 方法,可以用来包装 ByteBuf

ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);
buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(5);
buf2.writeBytes(new byte[]{6, 7, 8, 9, 10});

// 当包装 ByteBuf 个数超过一个时, 底层使用了 CompositeByteBuf
ByteBuf buf3 = Unpooled.wrappedBuffer(buf1, buf2);
System.out.println(ByteBufUtil.prettyHexDump(buf3));

输出

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 05 06 07 08 09 0a                   |..........      |
+--------+-------------------------------------------------+----------------+

也可以用来包装普通字节数组,底层也不会有拷贝操作

ByteBuf buf4 = Unpooled.wrappedBuffer(new byte[]{1, 2, 3}, new byte[]{4, 5, 6});
System.out.println(buf4.getClass());
System.out.println(ByteBufUtil.prettyHexDump(buf4));

输出

class io.netty.buffer.CompositeByteBuf
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 05 06                               |......          |
+--------+-------------------------------------------------+----------------+

💡 ByteBuf 优势

  • 池化 - 可以重用池中 ByteBuf 实例,更节约内存,减少内存溢出的可能。
  • 读写指针分离,不需要像 ByteBuffer 一样切换读写模式。
  • 可以自动扩容。
  • 支持链式调用,使用更流畅。
  • 很多地方体现零拷贝,例如 slice、duplicate、CompositeByteBuf。

提问,什么是ByteBuf的读写指针分离?

Netty中,ByteBuf是一种用于在网络通信中存储字节数据的数据结构。它提供了读和写两个指针,分别用于读取和写入字节数据。
读写指针分离是指,ByteBuf维护了两个指针,分别是读指针和写指针。读指针表示下一次读取数据的位置,写指针表示下一次写入数据的位置。这两个指针可以独立地移动,实现了读写操作的分离。通过readerIndex()和writerIndex()方法这两个方法可以获取当前的读指针和写指针的位置。ByteBuf的读写指针分离设计提供了更好的并发性和灵活性,使得它可以高效地处理多线程环境下的读写操作。

参考:https://www.bilibili.com/video/BV1py4y1E7oA?t=1.6&p=79

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

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

相关文章

给LLM装上知识:从LangChain+LLM的本地知识库问答到LLM与知识图谱的结合

第一部分 基于LangChain ChatGLM-6B的本地知识库问答的应用实现 1.1 什么是LangChain&#xff1a;连接本地知识库与LLM的桥梁 作为一个 LLM 应用框架&#xff0c;LangChain 支持调用多种不同模型&#xff0c;提供相对统一、便捷的操作接口&#xff0c;让模型即插即用&#x…

java并发编程 6:java内存模型与volatile(重点)

目录 硬件内存模型Java 内存模型主内存工作内存内存交互的八个原子操作JMM作用 可见性退不出的循环volatile解决同步模式之 Balking 有序性指令重排解决指令重排 volatile 原理volatile如何保证可见性volatile如何保证有序性volatile 不能解决指令交错double-checked locking 问…

2023/7/5总结

JS BOM 是浏览器对象模型 window对象是一个全局对象&#xff0c;也是JavaScript的顶级对象 所以通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法 定时器-延时函数 setTimeout(回调函数&#xff0c;等待的毫秒数) 延时函数只会执行一次 清除延时函…

Java基础---String的长度限制

目录 典型回答 常量池限制 运行期限制 典型回答 String有长度限制&#xff0c;编译期和运行期不一样编译期需要用 CONSTANT_Utf8_info 结构用于表示字符串常量的值&#xff0c;而这个结构是有长度限制&#xff0c;他的限制是65535运行期&#xff0c;String的length参数是Int…

Jenkins 配置用户角色和权限

1、配置条件&#xff1a; 1&#xff09;已安装Jenkins&#xff0c;当前案例使用版本&#xff1a;2.319.3 2&#xff09;已成功进入Jenkins&#xff0c;并新建用户&#xff1a;dev_java 2、安装插件【系统管理-插件管理-搜索-可选插件】&#xff1a;Role-based Authoriz…

JavaScript 进阶 - 第4天

JavaScript 进阶 - 第4天笔记 文章目录 JavaScript 进阶 - 第4天笔记1 深浅拷贝1.1 浅拷贝1.2 深拷贝&#xff08;面试&#xff09;1.2.1 递归实现深拷贝1.2.2 js库lodash里面cloneDeep内部实现了深拷贝1.2.3 JSON序列化 2 异常处理2.1 throw 抛异常2.2 try ... catch 捕获异常…

Linux之Kafka保姆式详细安装教程

下载Kafka 《Kafka官网下载》 注意&#xff1a;下载的是二进制文件&#xff0c;不要下载源码&#xff01;这里可以采用第三方下载工具加速下载&#xff0c;如&#xff1a;迅雷等 上传到Linux服务器的/data/目录下进行解压 tar -zxvf是解压文件命令&#xff0c;-C表示把解压…

【C++学习笔记】1.6 引用

目录 &#x1f36f;1.6 引用 &#x1f95d;1. 引用的概念 &#x1f95d;2. 引用的特性 1、引用在定义时必须初始化 2、一个变量可以有多个引用 3、引用一旦引用一个实体&#xff0c;再不能引用其他实体 &#x1f95d;3. 常引用 1、取别名的原则&#xff1a;对原引用的…

数据查询大揭秘:收好几个模式化公式

欢迎来到数据查询大揭秘&#xff01;今天&#xff0c;我将与大家分享一些宝贵的秘诀和技巧&#xff0c;让你轻松应对数据查询的挑战。准备好了吗&#xff1f;收起你的笔记本和便签纸&#xff0c;因为我即将为你介绍几个模式化公式&#xff0c;让你事半功倍地完成数据查询任务&a…

湖北文理学院工程能力实训开班!

为深化校企合作&#xff0c;产教融合助力新工科建设&#xff0c;提升学生工程实践能力&#xff0c;电巢工程能力实训班按照不同岗位类别&#xff0c;匹配对应的企业岗位任职能力要求对学生开展分级培养&#xff0c;以产业需求为导向&#xff0c;培养创新型、应用型人才。 7月3…

open3D cmake+win10+vs2019编译

已经采用python版open3D实现和验证了功能&#xff0c;但是在C迁移上却遇到了不少问题&#xff1a; 1、可能是与本地的编译器存在差异&#xff0c;在使用open3D git上的winows版本时&#xff0c;存在地址访问冲突和std::bad_alloc等问题。前者在适用IO读写时必现&#xff0c;后者…

【Git】Windows如何运行.sh脚本文件

在Windows系统中运行.sh脚本需要借助第三方工具&#xff0c;比如Git Bash、Cygwin或WSL&#xff08;Windows Subsystem for Linux&#xff09;等。 以下是使用Git Bash运行.sh脚本的步骤&#xff1a; 安装Git Bash&#xff1a;从Git官方网站&#xff08;https://git-scm.com/…

【Ubuntu学习MySQL——MySQL基本操作命令】

1.创建数据库 2.删除数据库 3.选择数据库 4. 创建数据表 5.删除数据表 6.往数据表中插入数据 7.从数据表中查询数据 SELECT column_name,column_name FROM table_name [WHERE Clause] [LIMIT N][ OFFSET M]""" 查询语句中你可以使用一个或者多个表&#xff0c…

SpringMVC源码-DispatcherServlet

一、SpringMVC请求处理流程 DispatcherServlet&#xff1a;DispatcherServlet是SpringMVC中的前端控制器&#xff0c;负责接收request并将request转发给对应的处理组件。HandlerMapping&#xff1a;HanlerMapping是SpringMVC中完成url到Controller映射的组件。Handler&#xff…

智能汽车时代,产业如何“软硬兼施”

摘要&#xff1a; 智能汽车时代&#xff0c;以车用芯片、基础软件为代表的卡脖子关键技术&#xff0c;牵动着国内整个汽车供应链的安全。“软硬兼施”正成为从企业到汽车全行业的共同重大行动。 汽车产业链、供应链安全问题近两年已经引起全行业前所未有的关注。进入智能汽车时…

互联网医院资质申请难吗|互联网医院+医药机构

互联网医院牌照申请的具体流程可能因国家和地区的法规和政策而有所不同。下面是一个一般性的流程介绍&#xff1a;   准备材料&#xff1a;根据当地的法规和政策要求&#xff0c;准备申请互联网医院牌照所需要的相关材料。这些材料可能包括但不限于&#xff1a;公司注册证明、…

【已解决】cc1plus: fatal error: cuda_runtime.h: No such file or directory

文章目录 前因解决方案后果 前因 我是在conda环境下创建stable diffusion的虚拟环境&#xff0c;虚拟环境下pytorch、cuda和cudnn的版本如下所示。服务器上的CUDA版本是11.2&#xff0c;GPU是P40&#xff0c;内存22G。 import torch >>> torch.__version__ 1.12.0 &…

JavaEE语法第二章之多线程(初阶四)

一、wait 和 notify 由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序. 球场上的每个运动员都是独立的 "执行流" , 可以认为是一个 "线程". 而完成一个具体的进攻得分…

【数据结构课程设计系列】农夫过河问题操作演示

农夫过河 1、题目要求 1.1设计目的 掌握广度优先搜索策略&#xff0c;并用队列求解农夫过河问题。 1.2设计内容 一个农夫带着一只狼、一只羊和一棵白菜&#xff0c;身处和的南岸&#xff0c;他要把这些东西全部运到北岸&#xff0c;遗憾的是他只有一只小船&#xff0c;小船只能…

从小白到大神之路之学习运维第53天--------tomcat-web应用——————供开发的商城框架

第三阶段基础 时 间&#xff1a;2023年7月5日 参加人&#xff1a;全班人员 内 容&#xff1a; Tomcat应用服务 WEB服务 目录 实验环境&#xff1a;&#xff08;四台服务器&#xff09; 安装tomcat服务&#xff1a; NginxTomcat 负载均衡集群部署&#xff1a; 安装ng…