Netty基础—4.NIO的使用简介一
大纲
1.Buffer缓冲区
2.Channel通道
3.BIO编程
4.伪异步IO编程
5.改造程序以支持长连接
6.NIO三大核心组件
7.NIO服务端的创建流程
8.NIO客户端的创建流程
9.NIO优点总结
10.NIO问题总结
1.Buffer缓冲区
(1)Buffer缓冲区的作用
(2)Buffer缓冲区的4个核心概念
(3)使用Direct模式创建的Buffer缓冲区
(4)如何分配和读写一个Buffer缓冲区
(5)如何操作一个分配好的Buffer缓冲区
(1)Buffer缓冲区的作用
在NIO中,所有的数据都是通过使用Buffer缓冲区来处理的。如果要通过NIO,将数据写到文件和网络或从文件和网络中读取数据,那么就需要使用Buffer缓冲区来进行处理。
(2)Buffer缓冲区的4个核心概念
Buffer缓冲区本质上是一个数组。通常它是一个字节数组ByteBuffer,当然还有其他基本类型的数组如CharBuffer。Buffer缓冲区不仅仅是一个数组,还可以对数据进行访问和对读写位置进行维护。所以Buffer缓冲区引入了4个核心概念:capacity、limit、position、mark。
一.capacity表示缓冲区的容量大小
也就是Buffer缓冲区里包含的数据的大小。如下所示,用字节数组封装了一个ByteBuffer,可以看其capacity是多少。
byte[] data = new byte[]{1, 2, 3};
ByteBuffer buffer = ByteBuffer.wrap(data);
System.out.println(buffer.capacity());
//输出显示ByteBuffer的capacity是3,因为字节数组data的大小是3
二.limit表示对Buffer缓冲区使用的一个限制
limit可以限制最多可以读取多少容量为capacity的缓冲区的数据。默认limit = capacity,limit后的位置不能读写数据。比如上述例子的3个数据,可用如下代码限制只能读取到index = 1的位置。
buffer.limit(1);
System.out.println(buffer.limit());
三.position表示数组中可以读写的位置
position不能大于limit。它会随着读写一直自动推进,直到跟limit一样才不能读写。如果手动设置的position大于limit,那么自动把limit设置为position。此外,remaining表示position到limit之间的距离。
四.mark表示对当前position位置的标记
在某个position的时候,设置一下mark,此时就可以设置一个标记。后续调用reset()方法可以把position复位到当时设置的那个mark位置上。把position或limit调整为小于mark的值时,就会丢弃这个mark。
总结:Buffer缓冲区就是一个数据缓冲区,可以支持不同的数据类型。比如ByteBuffer、CharBuffer、LongBuffer、DoubleBuffer、FloatBuffer、IntBuffer,这些不同的Buffer里面就可以包裹不同基本类型的数组。每个Buffer类都是Buffer接口的子实例,大多数标准IO操作都使用ByteBuffer。
Buffer缓冲区还引入了capacity、limit、position、mark等概念,通过这些概念可以对Buffer缓冲区里的数据进行访问以及维护读写位置等信息。比如可通过limit、position、mark来限制能读那些数据,从哪里开始读。
下面是一个使用Buffer缓冲区的示例:
public class BufferDemo {public static void main(String[] args) throws Exception {byte[] data = new byte[] {55, 56, 57, 58, 59};ByteBuffer buffer = ByteBuffer.wrap(data);System.out.println(buffer.capacity()); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.get());//把当前position所在位置的数据读取一位出来System.out.println(buffer.position()); buffer.mark();//在position = 1的时候打的mark,标记//buffer.position(3); //buffer.limit(4);buffer.position(3);System.out.println(buffer.get()); System.out.println(buffer.position()); buffer.reset();System.out.println(buffer.position()); }
}
(3)使用Direct模式创建的Buffer缓冲区
有个缓冲区是比较特殊的,叫做Direct模式的缓冲区,它的整体性能比普通模式的缓冲区要高些。
//适用于从磁盘文件读数据出来,或者从网络里读数据进来
ByteBuffer buffer1 = ByteBuffer.allocate(10);
//如果用direct模式分配Buffer,整体性能可以比普通模式稍微高些
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
(4)如何分配和读写一个Buffer缓冲区
一.ByteBuffer.allocateDirect(100)
分配一个Direct缓冲区,效率更高。二.ByteBuffer.wrap(byte[] array)
把一个字节数组作为数据放到缓冲区。三.ByteBuffer.put(byte b) 和 ByteBuffer.get()
对当前position位置放入一个字节数据,或者读取一个字节数据。四.ByteBuffer.put(byte[] src, int offset, int length) 和 ByteBuffer.get(byte[] dst, int offset, int length)
把指定src数组里的一段数据写入缓冲区,或者是从缓冲区里读取数据到数组中。五.ByteBuffer.put(byte[] src) 和 ByteBuffer.get(byte[] dst)
把数组全部写入缓冲区,以及从缓冲区读取全部数据到数组里去。
下面是这些API方法的使用示例:
public class BufferDemo2 { public static void main(String[] args) throws Exception {ByteBuffer buffer = ByteBuffer.allocateDirect(100);System.out.println("position=" + buffer.position()); System.out.println("capacity=" + buffer.position()); System.out.println("limit=" + buffer.limit()); byte[] src = new byte[] {1, 2, 3, 4, 5};buffer.put(src);System.out.println("position=" + buffer.position());//position = 0~4,都填充了数据 //此时position = 5,如果直接读取数据,是读不到的,会发现是空的、没有数据//所以需要手动操作一下position,调整到position = 0的位置,才能读到数据大小为5的字节数组srcbuffer.position(0);byte[] dst = new byte[5];buffer.get(dst); System.out.println("position=" + buffer.position());System.out.print("dst=[");for (int i = 0; i < dst.length; i++) {System.out.print(i);if (i < dst.length - 1) {System.out.print(","); }}System.out.print("]");}
}
(5)如何操作一个分配好的Buffer缓冲区
一.ByteBuffer.clear()
还原缓冲区的状态:position设置为0、limit设置为capacity、丢弃mark。
但是本质并不是删除数据,只是还原了那些标记位而已。
还原之后就可以复用缓冲区里的空间,覆盖老的数据了。二.ByteBuffer.flip()
准备读取刚写入的数据,就是将limit设置为当前position、将position设置为0、丢弃mark。
一般在写入完数据到缓冲区后,准备从位置=0开始读这段数据时,就可以使用flip。三.ByteBuffer.rewind()
将position设置为0、并且丢弃mark。
一般在读取了一遍数据后,还想要再次重新读取一遍数据时,就可以使用rewind,此时limit是不变的。
一般比较少会遇到直接操作Buffer的position、limit和mark的场景,通常都会使用上述操作Buffer缓冲区的几个方法:首先执行put()方法往缓冲区里写入数据,然后打算复用时,就执行clear()方法再重新写数据。如果写了一段数据打算要读取数据了,就执行flip()方法。如果希望重新读取一遍数据,就执行rewind()方法。
2.Channel通道
(1)NIO编程中的Channel是什么
(2)NIO编程中Buffer和Channel的关系
(3)基于FileChannel将数据写入磁盘文件
(4)利用FileChannel实现顺序写和随机写
(5)FileChannel是多线程并发安全的
(6)从磁盘文件读取数据到Buffer缓冲区
(7)对文件上共享锁限制文件只读
(8)FileChannel的强制刷盘
(9)FileChannle的position
(1)NIO编程中的Channel是什么
Channel是一个通道,网络数据通过Channel读取和写入。通道可以用于读、写或者二者同时进行。通道与流的区别在于:通道是双向的,流是单向的,一个流必须是InputStream和OutputStream的子类。
因为Channel是全双工的,所以它可以比流更好映射底层操作系统的API。在UNIX网络编程中,底层操作系统的通道都是全双工的,同时支持读写操作。
Channel可以分为两大类:用于网络读写的SelectableChannel和用于文件操作的FileChannel,其中ServerSocketChannel和SocketChannel都是SelectableChannel的子类。
(2)NIO编程中Buffer和Channel的关系
(3)基于FileChannel将数据写入磁盘文件
public class FileChannelDemo {public static void main(String[] args) throws Exception {//构造一个传统的文件输出流FileOutputStream out = new FileOutputStream("/Users/demo/Downloads/hello.txt"); //通过文件输出流获取到对应的FileChannel,以NIO的方式来写文件FileChannel channel = out.getChannel();ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes());channel.write(buffer);channel.close();out.close();}
}
(4)利用FileChannel实现顺序写和随机写
顺序写磁盘文件,就是不停地在文件末尾追加数据。
一.顺序写的例子
public class FileChannelDemo {public static void main(String[] args) throws Exception {//构造一个传统的文件输出流FileOutputStream out = new FileOutputStream("/Users/demo/Downloads/hello.txt");//通过文件输出流获取到对应的FileChannel,以NIO的方式来写文件FileChannel channel = out.getChannel();ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes());channel.write(buffer);//channel必然会从buffer的position = 0的位置开始读起,一直读到limit,limit = 字符串字节数组的长度//buffer的position此时就已经变成了跟limit一样了System.out.println(buffer.position());//输出11System.out.println(channel.position());//输出11,当前写到了文件的哪一个位置//继续往磁盘文件里写,就是从FileChannel的position = 11开始写,相当于文件末尾追加//如果想再次将buffer里的数据通过channel写入磁盘文件末尾,就是顺序写buffer.rewind();//position = 0,重新读一遍channel.write(buffer);//在文件末尾追加写的方式,顺序写,写完后文件内容就是"hello worldhello world" channel.close();out.close();}
}
二.随机写的例子
public class FileChannelDemo {public static void main(String[] args) throws Exception {//构造一个传统的文件输出流FileOutputStream out = new FileOutputStream("/Users/demo/Downloads/hello.txt");//通过文件输出流获取到对应的FileChannel,以NIO的方式来写文件FileChannel channel = out.getChannel();ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes());channel.write(buffer);//channel必然会从buffer的position = 0的位置开始读起,一直读到limit,limit = 字符串字节数组的长度//buffer的position此时就已经变成了跟limit一样了System.out.println(buffer.position());//输出11System.out.println(channel.position());//输出11,当前写到了文件的哪一个位置//如果继续往磁盘文件里写,就是从FileChannel的position = 11开始写,相当于文件末尾追加//现在已经在文件写入hello world,要继续在hello和world中间的那个空格地方,再写入一条数据,比如hello world //把这一段数据插入到磁盘文件的中间,就是磁盘随机写//在文件的随机的位置写入数据,肯定是要再次从buffer中读取数据,所以position必须复位buffer.rewind();//其次如果你要基于FileChannel随机写,可以调整FileChannel的position,这样文件内容为"hellohello world"channel.position(5);channel.write(buffer);channel.close();out.close();}
}
(5)FileChannel是多线程并发安全的
多线程并发写FileChannel是线程安全的。一个线程先执行完了写文件,下一个线程才能有机会去写,不可能多个线程同时基于一个FileChannel来写的。
public class FileChannelDemo2 {@SuppressWarnings("resource")public static void main(String[] args) throws Exception {//构造一个传统的文件输出流FileOutputStream out = new FileOutputStream("/Users/demo/Downloads/hello.txt"); //通过文件输出流获取到对应的FileChannel,以NIO的方式来写文件final FileChannel channel = out.getChannel();//输出文件的内容是10个顺序的hello worldfor (int i = 0; i < 10; i++) {new Thread() {public void run() {try {ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes());channel.write(buffer);} catch (Exception e) {e.printStackTrace();}};}.start();}}//当然这里的out并没有关闭
}
(6)从磁盘文件读取数据到Buffer缓冲区
通过FileChannel + FileInputStream可以实现从磁盘文件读取数据到缓冲区。
public class FileChannelDemo3 {public static void main(String[] args) throws Exception {FileInputStream in = new FileInputStream("/Users/demo/Downloads/hello.txt"); FileChannel channel = in.getChannel();ByteBuffer buffer = ByteBuffer.allocateDirect(16);channel.read(buffer);//读数据写入buffer,所以写完以后,buffer的position = 16buffer.flip();//position = 0,limit = 16byte[] data = new byte[16];buffer.get(data);System.out.println(new String(data));channel.close();in.close();}
}
(7)对文件上共享锁限制文件只读
JVM内的多个线程对同一个FileChannel进行写,是线程安全的。但如果是多个JVM对同一个FileChannel进行写,则不是线程安全。
为此,FileChannel提供了文件锁功能,可以对文件上锁。FileChannel的文件锁分为共享锁和独占锁。
有线程先获取了共享锁读文件,其他JVM里的线程就不能获取独占锁写文件。有线程先获取了独占锁写文件,其他JVM里的线程就不能获取共享锁读文件。
如果对文件上共享锁,不同JVM的不同线程都可以同时获取共享锁读文件,除非已有某个JVM的某线程获取到了独占锁写文件。
一.先运行如下的FileLockDemo1程序启动一个JVM对文件加共享锁
public class FileLockDemo1 {public static void main(String[] args) throws Exception {//新建一个输入流FileInputStream in = new FileInputStream("/Users/demo/Downloads/hello.txt"); FileChannel channel = in.getChannel();//注意输入流是不能加独占锁的,否则会报错channel.lock(0, Integer.MAX_VALUE, true);//加共享锁Thread.sleep(60 * 60 * 1000);channel.close();in.close();}
}
二.再运行如下的FileLockDemo2程序启动另一个JVM
由于该程序对文件加的是独占锁,所以此时会报错,只能加共享锁。
public class FileLockDemo2 {public static void main(String[] args) throws Exception {RandomAccessFile file = new RandomAccessFile("/Users/demo/Downloads/hello.txt", "rw"); FileChannel channel = file.getChannel();//加独占锁,阻塞住,会等待别人释放共享锁了,这里才能成功加独占锁channel.lock(0, Integer.MAX_VALUE, false);System.out.println("加锁成功");Thread.sleep(60 * 60 * 1000);channel.close();file.close();}
}
(8)FileChannel的强制刷盘
通过FileChannel写数据到磁盘文件时,不会立即将数据写到磁盘上,而会先将数据写到操作系统自己管理的一个内存区域OS Cache上。这样处理的好处是可以让磁盘写的性能比较高,但如果此时系统宕机,那么OS Cache里的数据可能就会丢失。
Kafka和ElasticSearch将数据写到磁盘时,都是先写入OS Cache的,操作系统会定时把OS Cache里的数据写入到磁盘上。
如果希望数据可以马上写入磁盘,就不能只用FileChannel.write()方法,还需要通过FileChannel.force()方法将数据从OS Cache强制刷入磁盘。当然,FileChannel将数据强制刷盘会让磁盘写的性能降低。
public class FileLockDemo1 {public static void main(String[] args) throws Exception {//新建一个输入流FileInputStream in = new FileInputStream("/Users/demo/Downloads/hello.txt"); FileChannel channel = in.getChannel();ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes());channel.write(buffer);channel.force(true);//强制数据从OS Cache刷入磁盘,避免停留在OS Cache中channel.close();in.close();}
}
(9)FileChannel的position
FileChannel有一个position的概念,它相当于一个指针,指向了当前文件的某个位置。
读数据是从FileChannel当前的position开始读的,每次刚开始初始化一个FileChannel后,position = 0。如果要随机读取文件里的某个位置,那么直接设置和定位FileChannel的position,就可以限定从什么位置开始读。
写数据也是从FileChannel当前的position开始写的,刚开始position = 0。如果直接写,那么可能会覆盖原来的老数据。读了多少字节的数据或写了多少字节的数据,FileChannel的position就会往前移动多少位。
3.BIO编程
(1)网络编程的Client/Server模型
(2)BIO服务端通信模型
(3)BIO网络编程的服务端
(4)BIO网络编程的客户端
(1)网络编程的Client/Server模型
网络编程的基本模型是Client/Server模型。客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接。如果连接建立成功,双方就可以通过网络套接字Socket进行通信。
在基于传统同步阻塞模型(BIO)开发中:ServerSocket负责绑定IP地址、启动监听端口,Socket负责发起连接操作。在连接成功后,双方通过输入和输出流进行同步阻塞通信。
(2)BIO服务端通信模型
通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求后会为每个客户端创建一个新的线程进行业务处理。业务处理完成后,通过输出流返回应答给客户端,然后销毁线程。这是典型的一请求一应答通信模型。
(3)BIO网络编程的服务端
//服务端(为简洁省略try catch)
public class SocketServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(9000);Socket socket = serverSocket.accept();//在这里会阻塞住,一直等待客户端建立连接//如果有个客户端要和当前服务端尝试建立TCP连接,并基于TCP协议来传输数据,发送一个一个的TCP包//那么客户端必须先和当前服务端的端口号建立连接//客户端Socket和服务端ServerSocket互相传递3次握手的TCP包,TCP连接就建立了//当客户端向当前服务端发起TCP三次握手建立一个连接后,这里就会构建出一个Socket//这个Socket就代表了当前服务端和某个客户端的一个TCP连接(Socket连接)//当建立了TCP连接后,客户端就会通过IO流的方式发送数据过来//由于IO流是无限的流,所以底层的TCP协议会把流式的数据拆分为一个一个的TCP包//然后将TCP包包裹在IP包里,IP包又会被包裹在以太网包里//最后通过底层的网络硬件设备以及以太网的协议,发送以太网包的数据//获取Socket的输入流InputStreamReader in = new InputStreamReader(socket.getInputStream());//获取Socket的输出流OutputStream out = socket.getOutputStream();char[] buf = new char[1024 * 1024];//JVM的一个缓冲数组int len = in.read(buf);//Socket的输入流,相当于不停读取客户端通过TCP协议发送过的一个一个的TCP包//然后把TCP包里的数据通过IO输入流的方式提供给服务端//这样服务端就可以通过IO输入流读取的方式,把TCP包里的数据读出来,然后放入JVM内存的一个缓冲数组中 while (len != -1) {String request = new String(buf, 0, len);System.out.println("服务端接收到了请求:" + request);//输出流的意思是,服务端会通过IO流发送响应数据回客户端//此时在底层会把服务端的响应数据拆分为一个一个的TCP包,回传给客户端//这样客户端就可以接收到服务端发送的TCP包了out.write("收到,收到".getBytes());//in.read会阻塞在这里尝试读取数据,所以out.write要放在前面len = in.read(buf);//为什么需要通过while循环反复去读取Socket流传输过来的数据?//因为客户端是不停地用流的方式发送数据给服务端的,所以服务端需要不停地读取//此外,由于buf才1KB,如果客户端发送过来的数据是几十KB,//那么当服务端读取完1KB的数据后,还需要继续读取几十KB的数据,//因此才需要服务端不停的读取}//释放资源out.close();in.close();//通过TCP四次挥手断开连接socket.close();serverSocket.close();}
}
(4)BIO网络编程的客户端
//客户端(为简洁省略try catch)
public class SocketClient {public static void main(String[] args) throws Exception {//此处应该是会找DNS服务查找域名对应的IP地址Socket socket = new Socket("localhost", 9000);//接下来需要跟某个IP地址上的9000端口的服务器程序进行TCP三次握手,然后建立连接//这时就会构造一个三次握手中的第一次握手的TCP包,在这个TCP包里放入三次握手需要的数据//然后把这个TCP包封装在IP包里,IP包里是有对应的目标的IP地址,IP包再封装在以太网包里//接着通过底层的硬件设备和以太网协议把以太网包发送出去//经过路由器时,会通过IP地址查找路由表来确定下一个路由器的位置//查找到下一个路由器的mac地址后将其写入到以太网包头,继续通过下一个子网广播出去//通过这种方式层层转发,一直到对应的服务器上去//服务端接收到三次握手的第一次握手的TCP包后//服务端就会回传第二次握手的TCP包给这个客户端的程序,客户端会再次发送第三次握手的TCP包过去 //这样三次握手成功,TCP连接建立起来了InputStreamReader in = new InputStreamReader(socket.getInputStream());OutputStream out = socket.getOutputStream();//发送数据流,底层拆分为一个一个的TCP包发到服务端去out.write("你好".getBytes());char[] buf = new char[1024 * 1024];int len = in.read(buf);while (len != -1) {String response = new String(buf, 0, len);System.out.println("客户端接收到了响应:" + response);len = in.read(buf);}//释放资源in.close();out.close();//通过TCP四次挥手断开连接socket.close();}
}
(5)BIO网络编程存在的问题
缺乏弹性伸缩能力,服务端的线程个数和客户端的并发访问数呈1 : 1的正比关系。当客户端并发访问量增加后,可能会耗尽服务端的线程资源。
相关文章:
Netty基础—4.NIO的使用简介一
大纲 1.Buffer缓冲区 2.Channel通道 3.BIO编程 4.伪异步IO编程 5.改造程序以支持长连接 6.NIO三大核心组件 7.NIO服务端的创建流程 8.NIO客户端的创建流程 9.NIO优点总结 10.NIO问题总结 1.Buffer缓冲区 (1)Buffer缓冲区的作用 (2)Buffer缓冲区的4个核心概念 (3)使…...
贪心算法简介(greed)
前言: 贪心算法(Greedy Algorithm)是一种在每个决策阶段都选择当前最优解的算法策略,通过局部最优的累积来寻求全局最优解。其本质是"短视"策略,不回溯已做选择。 什么是贪心、如何来理解贪心(个人对贪心的…...
驻场运维服务方案书(Word文件)
目 录 第一章 背景分析 1.1. 项目背景 1.2. 项目目标 1.3. 系统现状 1.3.1. 网络系统 1.3.2. 设备清单梳理 1.3.3. 应用系统 第二章 需求分析及理解 2.1. 在重要日期能保障信息系统安全 2.2. 信息系统可长期安全、持续、稳定的运行 2.3. 提升发现安全问题、解决安全…...
嵌入式硬件--开发工具-AD使用常用操作
ad16.1.12 1.如何显示/隐藏其他图层 在pcb界面点击L--试图界面中找到“视图选项”--单层模式选择 not in single layer mode 在pcb界面点击L--试图界面中找到“视图选项”--单层模式选择 gray scale other layers 【Altium】AD如何只显示一层,隐藏其他层显示&…...
在 Ubuntu 上安装和配置 Docker 的完整指南
Docker 是一个开源的平台,旨在简化应用程序的开发、部署和运行。通过将应用程序及其依赖项打包到容器中,Docker 确保应用程序可以在任何环境中一致地运行。 目录 前言安装前的准备安装 Docker 步骤 1:更新包索引步骤 2:安装必要…...
微服务全局ID方案汇总
自增id 对于大多数系统来说,使用mysql的自增id当作主键再最合适不过了。在数据库层面就可以获取一个顺序的、唯一的、空间占用少的id。 自增id需要是 int、bigint这些整数类型,uint 支持 40 亿的数据量,bigint unsign(0 &#x…...
实验5 逻辑回归
实验5 逻辑回归 【实验目的】掌握逻辑回归算法 【实验内容】处理样本,使用逻辑回归算法进行参数估计,并画出分类边界 【实验要求】写明实验步骤,必要时补充截图 1、参照“2.1梯度下降法实现线性逻辑回归.ipynb”和“2.2 sklearn实现线性逻辑…...
【原创】在高性能服务器上,使用受限用户运行Nginx,充当反向代理服务器[未完待续]
1 起因 在公共高性能服务器上运行OllamaDeepSeek,如果按照默认配置启动Ollama程序,则自己在远程无法连接你启动的Ollama服务。 如果修改掉默认的配置,则会遇到你的Ollama被他人完全控制的安全风险。 不过,我们可以使用一个方向…...
Linux 下 MySQL 8 搭建教程
一、下载 你可以从 MySQL 官方下载地址 下载所需的 MySQL 安装包。 二、环境准备 1. 查看 MySQL 是否存在 使用以下命令查看系统中是否已经安装了 MySQL: rpm -qa|grep -i mysql2. 清空 /etc/ 目录下的 my.cnf 执行以下命令删除 my.cnf 文件: [roo…...
vue 仿deepseek前端开发一个对话界面
后端:调用deepseek的api,所以返回数据格式和deepseek相同 {"model": "DeepSeek-R1-Distill-Qwen-1.5B", "choices": [{"index": 0, "delta": {"role": "assistant", "cont…...
MinIO问题总结(持续更新)
目录 Q: 之前使用正常,突然使用空间为0B,上传文件也是0B(部署在k8s中)Q: 无法上传大文件参考yaml Q: 之前使用正常,突然使用空间为0B,上传文件也是0B(部署在k8s中) A: 1、检查pod状态…...
STM32配套程序接线图
1 工程模板 2 LED闪烁 3LED流水灯 4蜂鸣器 5按键控制LED 6光敏传感器控制蜂鸣器 7OLED显示屏 8对射式红外传感器计次 9旋转编码器计次 10 定时器定时中断 11定时器外部时钟 12PWM驱动LED呼吸灯 13 PWM驱动舵机 14 PWM驱动直流电机 15输入捕获模式测频率 16PWMI模式测频率占空…...
深入理解Linux网络随笔(七):容器网络虚拟化--Veth设备对
深入理解Linux网络随笔(七):容器网络虚拟化 微服务架构中服务被拆分成多个独立的容器,docker网络虚拟化的核心技术为:Veth设备对、Network Namespace、Bridg。 Veth设备对 veth设备是一种 成对 出现的虚拟网络接口&…...
实战指南:鸿蒙ArkTS中实现列表下拉刷新与触底加载的完整解析
前言: 在移动应用开发中,下拉刷新和触底加载更多是提升用户体验的核心功能。鸿蒙ArkUI框架通过Refresh组件和List组件的onReachEnd事件,为开发者提供了简洁高效的实现方案。本文将通过代码示例,详解如何利用ArkTS实现这两个功能。…...
【栈数据结构应用解析:常见算法题详细解答】—— Leetcode
文章目录 栈的模拟实现删除字符串中的所有相邻重复项比较含退格的字符串基本计算器||字符串解码验证栈序列 栈的模拟实现 #include <iostream>using namespace std;const int N 1e5 10;// 创建栈 int stk[N], n;// 进栈 - 本质就是顺序表里面的尾插 void push(int x) …...
Git常用操作之GitLab
Git常用操作之GitLab 小薛博客官网:小薛博客Git常用操作之GitLab官方地址 1、GitLab安装 https://gitlab.cn/install/ 1、Docker安装GitLab https://docs.gitlab.cn/jh/install/docker.html 1、设置卷位置 在设置其他所有内容之前,请配置一个新的…...
2025探索短剧行业新可能报告40+份汇总解读|附PDF下载
原文链接:https://tecdat.cn/?p41043 近年来,短剧以其紧凑的剧情、碎片化的观看体验,迅速吸引了大量用户。百度作为互联网巨头,在短剧领域积极布局。从早期建立行业专属模型冷启动,到如今构建完整的商业生态…...
各省水资源平台 水资源遥测终端机都用什么协议
各个省水资源平台 水资源遥测终端机 的建设大部分从2012年开始启动,经过多年建设,基本都已经形成了稳定的通讯要求;河北瑾航科技 遥测终端机,兼容了大部分省市的通讯协议,如果需要,可以咨询和互相学习&…...
C#+EF+SqlServer性能优化笔记
文章目录 前言一、C#EF 代码优化1.接口代码改异步2.查询异步,只查询需要的数据3.查询数据判断时4.直接使用sql查询 二、数据库优化1.减少关联表,一些基础数据,字典表可以考虑放到redis中,在代码中映射2.增加索引,删除无…...
列表动态列处理
1、在initialize()方法里,获取列表控件,添加CreateListColumnsListener监听 public void initialize(){ BillList billlist(BillList)this.getControl("billlistap"); billlist.addCreateListColumnsListener(this::beforeCreateListColumns)…...
电机控制常见面试问题(十二)
文章目录 一.电机锁相环1.理解锁相环2.电机控制中的锁相环应用3.数字锁相环(DPLL) vs 模拟锁相环(APLL)4.锁相环设计的关键技术挑战5.总结 二、磁链观测1.什么是磁链?2.为什么要观测磁链?3.怎么观测磁链&am…...
芯驿电子 ALINX 亮相德国纽伦堡,Embedded World 2025 精彩回顾
2025年3月13日,全球规模最大的嵌入式行业盛会——德国纽伦堡国际嵌入式展(embedded world 2025)圆满落幕。 在这场汇聚全球 950 家展商、3 万余专业观众的科技盛宴中,芯驿电子 ALINX 展位人头攒动,多款尖端产品吸引客户…...
西门子S7-1200 PLC远程上下载程序方案
西门子S7-1200 PLC远程上下载程序方案(巨控GRM552YW-C模块) 三步完成配置 | 全球适用 | 稳定高效 三步快速完成远程配置 硬件部署 准备巨控GRM552YW-CHE模块1台,通过网口连接西门子S7-1200 PLC以太网口。 模块支持4G/5G/Wi-Fi/网线接入外网…...
MFC窗口的创建/消息映射机制
mfc.h #include<afxwin.h>//mfc头文件//应用程序类 class MyApp:public CWinApp //继承于应用程序类 { public://程序入口virtual BOOL InitInstance(); };//框架类 class MyFrame:public CFrameWnd { public:MyFrame();//声明宏 提供消息映射机制DECLARE_MESSAGE_MAP()…...
【每日学点HarmonyOS Next知识】tab对齐、相对布局、自定义弹窗全屏、动画集合、回到桌面
1、HarmonyOS Tabs 是否能支持 tabbar 居左对齐? 当前方案为自定义tabbar实现,示例demo: Entry Component struct TabsExample {State tabArray: Array<number> [0, 1,2]State focusIndex: number 0State pre: number 0State inde…...
如何在TikTok网页版切换地区设置
今天我们来聊聊如何在TikTok网页版上更改地区设置。TikTok作为全球知名的短视频社交应用,不仅仅局限于某个国家或地区。修改地区设置可以让你探索来自不同地方的内容,享受更为丰富的社交互动体验。那么,具体该如何操作呢?让我一步…...
redis工具类
前言 Redis 是一个高性能的键值存储系统,广泛应用于缓存、消息队列、实时分析等场景。为了更高效地操作 Redis,许多开发者会选择使用 Redisson 客户端库。 依赖配置 首先确保您的项目中已经包含了 Redisson 的最新版本(如 3.44.0ÿ…...
【Python办公】Excel通用匹配工具(双表互匹)
目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——>Python办公自动化专…...
安徽省青少年信息学奥林匹克竞赛初中组第1题LuoguP762
先放题目: 【题目背景】.你 .可 .以 .选 .择 .跳 .过 .背 .景 .部 .分。初春的一天,正是乍暖还寒时候,狂风乍起。小可可裹紧了单薄的外衣,往小雪家中赶去。“今天真不是个出门的时候啊!”小可可感叹道。“但是我还有东西要买………...
AVL树的平衡算法的简化问题
AVL树是一种紧凑的二叉查找树。它的每个结点,都有左右子树高度相等,或者只相差1这样的特性。文章https://blog.csdn.net/aaasssdddd96/article/details/106291144给出了一个例子。 为了便于讨论,这里对AVL树的结点平衡情况定义2个名称&#…...
NFS实验配置笔记
NFS NFS服务 nfs,最早是Sun这家公司所发展出来的,它最大的功能就是可以透过网络,让不同的机器,不同的操作系统,进行实现文档的共享。所以你可以简单的将他看做是文件服务器。 实验准备 ①先准备一个服务器端的操作…...
C盘清理技巧分享:释放空间,提升电脑性能
目录 1. 引言 2. C盘空间不足的影响 3. C盘清理的必要性 4. C盘清理的具体技巧 4.1 删除临时文件 4.2 清理系统还原点 4.3 卸载不必要的程序 4.4 清理下载文件夹 4.5 移动大文件到其他盘 4.6 清理系统缓存 4.7 使用磁盘清理工具 4.8 清理Windows更新文件 4.9 禁用…...
【云馨AI-大模型】RAGFlow功能预览:Dify接入外部知识库RAGFlow指南
介绍 Dify介绍 开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力,轻松构建和运营生成式 AI 原生应用。比 LangChain 更易用。官网:https://dify.ai/zh RAGFlow介绍 RAGFlow 是一款基于深度文档理解构建的…...
大模型学习笔记------Llama 3模型架构之旋转编码(RoPE)
大模型学习笔记------Llama 3模型架构之旋转编码(RoPE) 1、位置编码简介1.1 绝对位置编码1.2 相对位置编码 2、旋转编码(RoPE)2.1 基本概念---旋转矩阵2.2 RoPE计算原理2.2.1 绝对位置编码2.2.2 相对位置编码 3、旋转编码…...
Anthropic 的模型
Anthropic 的模型(特别是 Claude 系列)之所以在性能和推理能力上表现强劲,可以从技术设计、研究理念、训练方法以及应用优化等多个方面进行详细分析。以下是基于当前信息(截至 2025 年 3 月 13 日)和行业趋势的深入剖析…...
初探大模型开发:使用 LangChain 和 DeepSeek 构建简单 Demo
最近,我开始接触大模型开发,并尝试使用 LangChain 和 DeepSeek 构建了一个简单的 Demo。通过这个 Demo,我不仅加深了对大模型的理解,还体验到了 LangChain 和 DeepSeek 的强大功能。下面,我将分享我的开发过程以及一些…...
FPGA初级项目10——基于SPI的DAC芯片进行数模转换
FPGA初级项目10——基于SPI的DAC芯片进行数模转换 DAC芯片介绍 DAC 芯片(数字模拟转换器)是一种将数字信号转换为连续模拟信号(如电压或电流)的集成电路,广泛应用于电子系统中,连接数字世界与模拟世界。 …...
【论文解读】Contrastive Learning for Compact Single Image Dehazing(AECR-Net)
文章目录 问题创新网络主要贡献Autoencoder-like Dehazing NetworkAdaptive Mixup for Feature PreservingDynamic Feature Enhancement1. 可变形卷积的使用2. 扩展感受野3. 减少网格伪影4. 融合空间结构信息 Contrastive Regularization1. 核心思想2. 正样本对和负样本对的构建…...
unity基础——线段与拖尾
1、LineRenderer(线段渲染器) 为空物体加上组件添加材质 选择默认线段的材质 Default—Line Color:可以修改颜色Corner Vertices:角顶点 圆滑度 End Cap Vertices:边缘顶点 线段编辑 1、可以移动线段点的位置…...
【服务器知识】Nginx路由匹配规则说明
Nginx路由匹配规则说明 **一、Nginx路由匹配核心机制****二、匹配规则语法详解**1. **精确匹配 ()**2. **前缀匹配 (^~ 或 /)**3. **正则匹配 (~ 或 ~*)**4. **通配符匹配 (*)** **三、路由匹配优先级顺序****四、高级路由技巧**1. **条件判断 (if语句)**2. **路径重写 (rewrit…...
Python----数据可视化(Pyecharts三:绘图二:涟漪散点图,K线图,漏斗图,雷达图,词云图,地图,柱状图折线图组合,时间线轮廓图)
1、涟漪特效散点图 from pyecharts.globals import SymbolType from pyecharts.charts import EffectScatter from pyecharts.faker import Faker from pyecharts import options as opts from pyecharts.globals import ThemeType # 绘制图表 es (EffectScatter(init_optsop…...
机器学习中的梯度下降是什么意思?
梯度下降(Gradient Descent)是机器学习中一种常用的优化算法,用于最小化损失函数(Loss Function)。通过迭代调整模型参数,梯度下降帮助模型逐步逼近最优解,从而提升模型的性能。 1.核心思想 梯…...
C语言中的字符串与数组的关系
在C语言中,字符串和数组之间有着紧密的关系。理解它们的区别和联系对于编写高效且可靠的代码至关重要。在本篇博文中,我们将详细分析字符串和数组在C语言中的概念、它们的关系以及如何在编程中应用它们。 一、字符串与数组的基础知识 1.1 数组概念 在C语言中,数组是一组相…...
Ubuntu 18,04 LTS 通过APT安装mips64el的交叉编译器。
安装 g-5v的版本: sudo apt update sudo apt install g-5-mips64el-linux-gnuabi64 How to Install g-5-mips64el-linux-gnuabi64 in Ubuntu 18.04 安装 gcc/g-7v的版本: sudo apt-get install gcc-mips64el-linux-gnu* g-mips64el-linux-gnu* -y 安装…...
MySQL 衍生表(Derived Tables)
在SQL的查询语句select …. from …中,跟在from子句后面的通常是一张拥有定义的实体表,而有的时候我们会用子查询来扮演实体表的角色,这个在from子句中的子查询会返回一个结果集,这个结果集可以像普通的实体表一样查询、连接&…...
C++ vector 核心知识:常用操作与示例详解
在C编程中,vector 是标准模板库(STL)中最常用的容器之一。它以其动态数组的特性、高效的尾部操作和便捷的随机访问能力,成为处理动态数据的首选工具。无论是初学者还是经验丰富的开发者,掌握 vector 的使用方法和性能优…...
不同开发语言对字符串的操作
一、字符串的访问 Objective-C: 使用 characterAtIndex: 方法访问字符。 NSString *str "Hello, World!"; unichar character [str characterAtIndex:0]; // 访问第一个字符 H NSLog("%C", character); // 输出: H NSString 内部存储的是 UTF-16 编…...
Qt从入门到入土(十) -数据库操作--SQLITE
认识 数据库是用于存储、管理和检索数据的系统化集合。它是一种按照特定结构组织数据的存储方式,通过软件(数据库管理系统,DBMS)来实现数据的高效存储、查询、更新和管理。通过文件存储数据适用于少量的数据,而当拥有…...
硬件驱动——51单片机:独立按键、中断、定时器/计数器
目录 一、独立按键 1.原理 2.封装函数 3.按键控制点灯 数码管 二、中断 1.原理 2.步骤 3.中断寄存器IE 4.控制寄存器TCON 5.打开外部中断0和1 三、定时器/计数器 1.原理 2.控制寄存器TCON 3.工作模式寄存器TMOD 4.按键控制频率的动态闪烁 一、独立按键 1…...
pgsql创建新用户并赋只读权限
在 PostgreSQL 中,为新用户赋予只读权限的步骤如下: —### 1. 创建新用户首先,创建一个新用户(角色),并设置密码:sqlCREATE ROLE 用户名 WITH LOGIN PASSWORD 密码;例如:sqlCREATE R…...