Netty基础—4.NIO的使用简介二
大纲
1.Buffer缓冲区
2.Channel通道
3.BIO编程
4.伪异步IO编程
5.改造程序以支持长连接
6.NIO三大核心组件
7.NIO服务端的创建流程
8.NIO客户端的创建流程
9.NIO优点总结
10.NIO问题总结
4.伪异步IO编程
(1)BIO的主要问题
(2)BIO编程模型的改进
(3)伪异步IO编程
(4)伪异步IO的问题
(5)伪异步IO可能引起的级联故障
(1)BIO的主要问题
BIO的主要问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程来处理新接入的客户端链路,一个线程只能处理一个客户端连接。
比如在上述BIO编程的服务端程序中:SocketServer和一个客户端建立连接后,服务端会一直执行in.read(buf)进行阻塞等待,从而导致服务端只能和这个客户端进行通信。如果有一个新的客户端想和服务端建立连接,那么服务端是不能和这个新的客户端建立TCP连接的,因为服务端还在阻塞执行in.read(buf)而没能执行serverSocket.accept()。
(2)BIO编程模型的改进
为了改进一个线程处理一个连接的BIO模型,可以通过线程池或者消息队列,实现一个或者多个线程处理N个客户端的伪异步IO模型。
此时由于底层通信机制依然使用同步阻塞IO,所以被称为伪异步IO。服务端线程池的最大线程数N和客户端并发访问数M呈N : M的比例关系。改进后的BIO模型能有效防止海量并发接入导致线程资源耗尽。
(3)伪异步IO编程
当有新的客户端接入时,将客户端的Socket封装成一个任务Task(实现Runnable接口)投递到后端的线程池中进行处理。这个线程池中会维护一个消息队列和N个活跃的线程来对消息队列中的任务Task进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此线程资源的占用是可控的,不会导致线程耗尽而宕机。
//服务端(为简洁省略try catch)
public class SocketServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(9000);ServerHandlerExecutePool singleExcutor = new ServerHandlerExecutePool(50, 1000);while (true) {//在这里会阻塞住,一直等待客户端来建立连接Socket socket = serverSocket.accept();//获取客户端的连接后,将socket提交给线程池处理singleExcutor.execute(new ServerHandler(socket));}...}
}public class ServerHandlerExecutePool {private ExecutorService executor;public ServerHandlerExecutePool(int maxPoolSize, int queueSize) {executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxPoolSize,120L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));}public void execute(Runnable task) {executor.execute(task);}
}public class ServerHandler implements Runnable {private Socket socket;public ServerHandler(Socket socket) {this.socket = socket;}@Overridepublic void run() {InputStreamReader in = new InputStreamReader(socket.getInputStream());OutputStream out = socket.getOutputStream();char[] buf = new char[1024 * 1024];int len = in.read(buf);while (len != -1) {String request = new String(buf, 0, len);System.out.println("[" + Thread.currentThread().getName() + "]服务端接收到了请求:" + request);out.write("收到,收到".getBytes());len = in.read(buf);}//释放资源out.close();in.close();//通过TCP四次挥手断开连接socket.close();}
}
(4)伪异步IO的问题
一.当对Socket的输入流进行读操作时
也就是调用Java输入流InputStream的read()方法读输入流时,会一直阻塞下去直到可用数据已经读取完毕。这意味着当一方发送请求或者应答消息比较缓慢或者网络传输比较缓慢时,读取输入流的一方的通信线程将被长时间阻塞。如果发送方要60s才能够将数据发送完毕,读取方的IO线程在执行read()方法时会被同步阻塞60s。在此期间,其他的连接任务只能在消息队列中排队。
二.当对Socket的输出流进行写操作时
也就是调用Java输出流OutputStream的write()方法写输出流时,也会一直阻塞下去直到所有要发送的字节全部写入完毕。当消息的接收方处理缓慢时,将不能及时从TCP缓冲区中读取数据。这将会导致发送方的TCP Window Size不断减小,直到为0。最后消息发送方将不能再向TCP缓冲区写入消息,于是write()方法将被阻塞直到TCP Window Size大于0。所以输入流和输出流的读和写操作都是同步阻塞的,阻塞时间取决于对方IO线程的处理速度和网络IO的传输速度。
(5)伪异步IO可能引起的级联故障
一.服务端处理缓慢,返回应答消息耗费60s,平时只需10ms。
二.此时客户端在读取服务端响应,由于读取输入流是阻塞的,客户端将会被同步阻塞60s。
三.假如所有可用线程都被该服务端阻塞,那后续的所有IO消息都将在队列中排队。
四.由于线程池采用阻塞队列实现,当队列积满后,后续入队的操作将会被阻塞。
五.由于只有一个Acceptor线程接收客户端接入,线程池的阻塞队列满了之后,它也会被阻塞。于是新的客户端请求消息将会被拒绝,从而客户端发生大量的连接超时。
因此BIO模型显然无法支持百万并发请求,因为一个线程只能处理一个请求。改进BIO后的伪异步IO模型即便可以通过一个线程处理多个请求,也存在级联故障的问题。
5.改造程序以支持长连接
(1)什么是长连接和短连接
(2)长连接和短连接的代码
(1)什么是长连接和短连接
一.短连接
客户端每次建立一个连接,发送一个请求,获取一个响应,然后就断开连接,就是所谓的短连接。
二.长连接
客户端每次建立一个连接,可以发送很多个请求,一直持续维持这个TCP连接,不断开连接。客户端持续通过这个连接与服务端进行通信,不停地发送数据和请求。服务端也长期维持这个连接,不停地接受请求返回响应。这个就是所谓的长连接,连接存在的时间很长的。所以只要客户端不停地发送请求不释放连接,那么就是长连接了。
(2)长连接和短连接的代码
//客户端(短连接)(为简洁省略try catch)
public class SocketClient {public static void main(String[] args) throws Exception {for (int i = 0; i < 10; i++) {new Thread() {public void run() {Socket socket = new Socket("localhost", 9000);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);if (len != -1) {String response = new String(buf, 0, len);System.out.println("[" + Thread.currentThread().getName() + "]客户端接收到了响应:" + response);}in.close();out.close();socket.close();};}.start();}}
}//客户端(长连接)(为简洁省略try catch)
public class SocketClient {public static void main(String[] args) throws Exception {for (int i = 0; i < 10; i++) {new Thread() {public void run() {Socket socket = new Socket("localhost", 9000);InputStreamReader in = new InputStreamReader(socket.getInputStream());OutputStream out = socket.getOutputStream();char[] buf = new char[1024 * 1024];//客户端不停地每隔一秒发送请求,不释放就是长连接了while (true) {//发送数据流,底层拆分为一个一个的TCP包发过去out.write("你好".getBytes());int len = in.read(buf);if (len != -1) {String response = new String(buf, 0, len);System.out.println("[" + Thread.currentThread().getName() + "]客户端接收到了响应:" + response);}Thread.sleep(1000);}...};}.start();}}
}
6.NIO三大核心组件
(1)Buffer缓冲区
(2)Channel数据通道
(3)Selector多路复用器
(4)BIO和IO多路复用的理解
(5)一些概念补充
NIO的三大核心组件分别是:Buffer、Channel、Selector。
(1)Buffer缓冲区
Buffer缓冲区是用来封装数据的,也就是把数据写入到Buffer、或者从Buffer中读取数据。
(2)Channel数据通道
Channel就是一个数据管道,负责传输数据(封装好数据的Buffer),比如把数据写入到文件或网络、从文件或网络中读取数据。
(3)Selector多路复用器
Selector会不断地轮询注册在其上的Channel。如果某个Channel上发生读或写事件,那么这个Channel就处于就绪状态。然后就绪的Channel就会被Selector轮询出来,具体就是通过Selector的SelectionKey来获取就绪的Channel集合。获取到就绪的Channel后,就可以进行后续的IO操作了。
一个Selector多路复用器可以同时轮询多个Channel。由于JDK使用了epoll()代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这意味着只需要一个线程负责Selector多路复用器的轮询,就可以接入成千上万的客户端。
(4)BIO和IO多路复用的理解
由于TCP连接的建立需要经过三次握手,所以可理解为客户端向服务端发起的Socket连接就绪需要经过三次握手请求。服务端接收到客户端的第一次握手请求时,会创建Socket连接(即创建一个Channel)。服务端接收客户端的第三次握手请求,这个Socket连接才算准备好(Channel才算就绪)。
在BIO模型下,只有一次系统调用recvfrom。所以服务端从接收到客户端的第一次握手请求开始,就必须同步阻塞等待直到第三次握手请求的完成,这样才能获取准备好的Socket连接并读取请求数据。
在多路复用模型下,会有两次系统调用select和recvfrom。所以服务端接收到客户端的第一次握手请求后,不必创建线程然后通过阻塞来等待第三次握手请求的完成,而是可以直接通过轮询Selector(基于select系统调用),来获取所有已经完成第三次握手请求(已就绪)的客户端Socket连接,之后对这些Socket连接进行recvfrom系统调用时不需要阻塞也能马上读取到请求数据了。
(5)一些概念补充
一.异步非阻塞IO
NIO类库支持非阻塞的读和写操作。相比BIO同步阻塞的读和写操作,NIO是异步的,所以也称NIO为异步非阻塞IO。
二.多路复用Selector
NIO的实现关键是多路复用IO技术。多路复用的核心是通过Selector来轮询注册在其上的Channel,执行Selector.select()方法进行轮询时是同步阻塞的。当发现有Channel处于就绪状态后,Selector.select()方法就会从阻塞状态中返回,然后就可以通过Selector.selectedKeys()方法获取就绪的Channel进行IO操作。
三.伪异步IO
在通信线程和业务线程之间做一个缓冲区,这个缓冲区用于隔离IO线程和业务线程间的直接访问,这样业务线程就不会被IO线程阻塞了。
对于后端的业务侧来说,将消息或者Task放到线程池后就返回了,它不再直接访问IO线程或者进行IO读写,这样也就不会被同步阻塞。
7.NIO服务端的创建流程
步骤一:通过ServerSocketChannel的opne()方法打开ServerSocketChannel。
步骤二:设置ServerSocketChannel为非阻塞模式,绑定监听地址和端口。
步骤三:通过Selector的open()方法创建多路复用器Selector,将已打开的ServerSocketChannel注册到多路复用器Selector上以及监听ACCEPT事件。
步骤四:多路复用器Selector通过select()方法轮询准备就绪的SelectionKey。
步骤五:如果这个SelectionKey是acceptable,说明有客户端发起了连接请求。此时需要调用ServerSocketChannel的accept()方法来处理该接入请求,也就是完成TCP三次握手并建立物理链路以及得到该客户端连接SocketChannel。
步骤六:然后将新接入的客户端连接SocketChannel注册到多路复用器Selector上以及监听READ事件。
步骤七:如果这个SelectionKey是readable,说明有客户端发送了数据过来。此时需要调用SocketChannel的read()方法异步读取客户端发送的数据到ByteBuffer缓冲区,同时将客户端连接SocketChannel注册到多路复用器Selector上以及监听WRITE事件。
步骤八:接着对ByteBuffer缓冲区的数据进行decode解码处理并完成业务逻辑,然后再将处理结果对象encode编码放入ByteBuffer缓冲区,最后调用SocketChannel的write()方法异步发送给客户端,以及将客户端连接SocketChannel注册到多路复用器Selector上以及监听READ事件。
public class NIOServer {private static Selector selector;public static void main(String[] args) {init();listen();}private static void init() {//serverSocketChannel其实就是ServerSocket,负责跟各个客户端连接连接请求//SelectionKey.OP_ACCEPT的意思是仅仅关注ServerSocketChannel接收到的TCP连接请求,也就是监听ACCEPT事件//ServerSocketChannel是注册在Selector上面的ServerSocketChannel serverSocketChannel = null;try {selector = Selector.open();serverSocketChannel = ServerSocketChannel.open();//步骤一:打开ServerSocketChannelserverSocketChannel.configureBlocking(false);//步骤二:NIO就是支持非阻塞的serverSocketChannel.socket().bind(new InetSocketAddress(9000), 100);//步骤二serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//步骤三:注册到selector + 关注OP_ACCEPT事件 } catch (IOException e) {e.printStackTrace();}}private static void listen() {while(true) {try {//select()方法是阻塞的,看注册到Selector上的ServerSocketChannel是否接收到了请求selector.select();//步骤四Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();while(keysIterator.hasNext()) {SelectionKey key = (SelectionKey) keysIterator.next();//可以认为一个SelectionKey代表了一个请求keysIterator.remove();handleRequest(key);}} catch(Throwable t){t.printStackTrace();}}}private static void handleRequest(SelectionKey key) throws IOException, ClosedChannelException {//会有个线程池获取到了请求,下面的代码都应该在线程池的工作线程里处理SocketChannel channel = null;try {//如果这个SelectionKey是acceptable,也就是连接请求//那么注册对应的SocketChannel到selector上,并关注OP_READ事件if (key.isAcceptable()) {//步骤五System.out.println("[" + Thread.currentThread().getName() + "]接收到连接请求");//从SelectionKey中拿出ServerSocketChannelServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();//调用ServerSocketChannel的accept方法,就可以跟客户端进行TCP三次握手channel = serverSocketChannel.accept();System.out.println("[" + Thread.currentThread().getName() + "]建立连接时获取到的channel=" + channel);//如果三次握手成功了之后,就可以获取到一个建立好TCP连接的SocketChannel//这个SocketChannel大概可以理解为,底层有一个Socket是跟客户端进行连接的//这个SocketChannel就是联通到那个Socket上去,负责进行网络数据的读写的//下面配置成非阻塞的channel.configureBlocking(false);//将该SocketChannel注册到selector上,且仅仅关注READ请求,也就是关注发送数据过来的请求channel.register(selector, SelectionKey.OP_READ);//步骤六:注册到selector + 关注OP_READ事件} else if (key.isReadable()) {//步骤七//如果这个SelectionKey是readable,也就是发送了数据过来,此时需要读取客户端发送过来的数据channel = (SocketChannel) key.channel();//读取请求数据的buffer缓冲ByteBuffer buffer = ByteBuffer.allocate(1024);//通过底层的socket读取数据,写入buffer中,position可能就会变成比如21之类的//socket读取到了多少个字节,此时buffer的position就会变成多少int count = channel.read(buffer);//步骤七System.out.println("[" + Thread.currentThread().getName() + "]接收到请求");if (count > 0) {//设置position = 0,limit = 21,仅仅读取buffer中,0~21这段刚刚写入进去的数据buffer.flip();System.out.println("服务端接收请求:" + new String(buffer.array(), 0, count));channel.register(selector, SelectionKey.OP_WRITE);//步骤七:注册到selector + 关注OP_WRITE事件}} else if (key.isWritable()) {//步骤八ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.put("Server收到了".getBytes());buffer.flip();channel = (SocketChannel) key.channel();channel.write(buffer);//步骤八channel.register(selector, SelectionKey.OP_READ);//步骤八:注册到selector + 关注OP_READ事件}} catch(Throwable t) {t.printStackTrace();if (channel != null) {channel.close();}}}
}
8.NIO客户端的创建流程
步骤一:通过SocketChannel的open()方法打开SocketChannel。
步骤二:设置SocketChannel为非阻塞模式,同时设置TCP参数。
步骤三:调用SocketChannel的connect()方法异步连接服务端,完成TCP三次握手并建立物理链路。
步骤四:通过Selector的open()方法创建多路复用器Selector,并将已打开的SocketChannel注册到多路复用器Selector上以及监听CONNECT事件。
步骤五:多路复用器Selector通过select()方法轮询准备就绪的SelectionKey。
步骤六:如果这个SelectionKey是connectable,说明服务端接受了发起的连接请求,于是将SocketChannel注册到多路复用器Selector上以及监听READ事件。
步骤七:如果这个SelectionKey是readable,说明服务端返回了数据。于是调用SocketChannel的read()方法异步读取服务端返回的数据到ByteBuffer缓冲区,同时将客户端连接SocketChannel注册到多路复用器Selector上以及监听WRITE事件。
步骤八:接着对ByteBuffer缓冲区的数据进行decode解码处理并完成业务逻辑,然后再将处理结果对象encode编码放入ByteBuffer缓冲区,最后调用SocketChannel的write()方法异步发送给客户端,以及将客户端连接SocketChannel注册到多路复用器Selector上以及监听READ事件。
public class NIOClient {public static void main(String[] args) {//启动10个线程去和服务端建立通信for (int i = 0; i < 10; i++) {new Worker().start();}}static class Worker extends Thread {@Overridepublic void run() {SocketChannel channel = null;Selector selector = null;try {//SocketChannel底层就是封装了一个Socket//SocketChannel是通过底层的Socket网络连接上服务端的数据通道,负责基于网络读写数据channel = SocketChannel.open();//步骤一//配置成非阻塞的channel.configureBlocking(false);//步骤二//底层会发起了一个TCP三次握手尝试建立连接channel.connect(new InetSocketAddress("localhost", 9000));//步骤三selector = Selector.open();//监听connect行为channel.register(selector, SelectionKey.OP_CONNECT);//步骤四:注册到selector + 关注OP_CONNECT事件while(true) {//三次握手成功后,服务端会给客户端返回一个响应请求,通过如下代码把响应请求拿到selector.select();//步骤五Iterator<SelectionKey> keysIterator = selector.selectedKeys().iterator();while(keysIterator.hasNext()) {SelectionKey key = (SelectionKey) keysIterator.next();keysIterator.remove();//如果server返回的是一个connectable的消息if (key.isConnectable()) {//步骤六channel = (SocketChannel) key.channel();if (channel.isConnectionPending()) {//一旦建立连接成功了以后,此时就可以给server发送一个数据了channel.finishConnect();ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.put("你好".getBytes());buffer.flip();channel.write(buffer);}//接下来监听READ事件就是准备读服务端返回的数据channel.register(selector, SelectionKey.OP_READ);//步骤六:注册到selector + 关注OP_READ事件} else if (key.isReadable()) {//步骤七:说明服务器端返回了一条数据可以读了channel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int len = channel.read(buffer);//把数据写入buffer,position推进到读取的字节数数字if (len > 0) {System.out.println("[" + Thread.currentThread().getName() + "]收到响应:" + new String(buffer.array(), 0, len));Thread.sleep(5000);//睡眠5秒后,接下来继续监听WRITE事件,并准备写数据给服务端channel.register(selector, SelectionKey.OP_WRITE);//步骤七:注册到selector + 关注OP_WRITE事件}} else if (key.isWritable()) {//步骤八ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.put("重复你好了".getBytes());buffer.flip();channel = (SocketChannel) key.channel();channel.write(buffer);//再次重复发数据给服务端后,接下来要监听READ事件就是准备读服务端返回的数据channel.register(selector, SelectionKey.OP_READ);//步骤八:注册到selector + 关注OP_READ事件}}}} catch (Exception e) {e.printStackTrace();} finally {if (channel != null){try {channel.close();} catch (IOException e) {e.printStackTrace();}}if (selector != null) {try {selector.close();} catch (IOException e) {e.printStackTrace();}}}}}
}
9.NIO优点总结
优点一:SocketChannel的连接操作是异步的
也就是客户端发起的连接操作SocketChannel.connect()是异步的。可以将SocketChannel注册到多路复用器上并关注OP_CONNECT事件等待后续结果,不需要像BIO的客户端那样被同步阻塞。
优点二:SocketChannel的读写操作都是异步的
也就是客户端发起的读写操作SocketChannel.read()和write()是异步的。如果没有可读写的数据它不会同步等待,而会直接返回。这样IO通信链路就可以处理其他链路了,不需要同步等待这个链路可用。
优点三:优化了线程模型
由于JDK的Selector在Linux等主流操作系统上通过epoll实现,从而没有了连接句柄数的限制。这意味着一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随客户端增加而线性下降。注意:Selector.select()是同步阻塞的。
优点四:优化了IO读写
BIO的读写是面向流的,一次性只能从流中读取一子节或多字节,而且读完后流无法再读取,需要自己缓存数据。NIO的读写是面向Buffer的,可随意读取里面任何字节的数据。不需要自己缓存数据,只需要移动读写指针即可。
10.NIO问题总结
问题一:NIO的类库和API繁杂,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
问题二:处理常见问题的工作量和难度比较大,比如客户断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流等。
问题三:NIO的epoll bug会导致Selector空轮询,最终导致CPU达到100%。
相关文章:
Netty基础—4.NIO的使用简介二
大纲 1.Buffer缓冲区 2.Channel通道 3.BIO编程 4.伪异步IO编程 5.改造程序以支持长连接 6.NIO三大核心组件 7.NIO服务端的创建流程 8.NIO客户端的创建流程 9.NIO优点总结 10.NIO问题总结 4.伪异步IO编程 (1)BIO的主要问题 (2)BIO编程模型的改进 (3)伪异步IO编程 …...
WPS 接入 DeepSeek-R1 深度实践:打造全能AI办公助手
一、智能化办公革命的技术底座 1.1 全球办公自动化发展现状 根据Gartner 2024年数字化转型报告显示,全球83%的企业已启动文档处理智能化改造,其中中国企业的AI办公渗透率以年均37%的增速领跑全球。WPS Office凭借其月活设备数突破5.6亿的生态优势&…...
MySQL与Redis的缓存一致性问题
MySQL与Redis的缓存一致性问题 前言 在学习中,为了提高数据的读取效率,我们往往会使用Redis来作为MySQL数据的缓存,那么,自然就产生了二者间数据的一致性问题。 想要对MySQL和Redis进行数据处理,自然会产生以下问题…...
【前端文件下载实现:多种表格导出方案的技术解析】
前端文件下载实现:多种表格导出方案的技术解析 背景介绍 在企业级应用中,数据导出是一个常见需求,特别是表格数据的导出。在我们的管理系统中,不仅需要支持用户数据的Excel导出,还需要处理多种格式的表格文件下载&am…...
933. 最近的请求次数
最近的请求次数 题目描述尝试做法推荐做法 题目描述 写一个 RecentCounter 类来计算特定时间范围内最近的请求。 请你实现 RecentCounter 类: RecentCounter() 初始化计数器,请求数为 0 。 int ping(int t) 在时间 t 添加一个新请求,其中 …...
[C++Qt] 槽函数收不到信号问题(信号的注册)
📢博客主页:https://loewen.blog.csdn.net📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉📢现…...
吴恩达机器学习笔记复盘(三)Jupyter NoteBook
Jupyter NoteBook Jupyter是一个开源的交互式计算环境: 特点 交互式编程:支持以单元格为单位编写和运行代码,用户可以实时看到代码的执行结果,便于逐步调试和理解代码逻辑。多语言支持:不仅支持Python,还…...
面向对象Demo02
方法的调用的回顾 package oop; public class Demo02 {//静态public static void main(String[] args) {//student student new student();student.speak();//非静态 //student student new student();} } package oop; public class student {public static void sp…...
Quitzilla 学习版(安卓):戒掉坏习惯的高效助手
Quitzilla 是一款源自国外的日程管理工具,专为帮助用户戒除不良习惯而设计。它通过强大的习惯追踪功能和丰富的激励机制,让用户能够清晰地看到自己的进步,并逐步克服成瘾行为。这款软件的前身是一款戒烟应用,但经过改进后…...
AWB-illuminant_weight_vector
1.概念 光源权重向量 AWB 处理通过求取R/G-B/G 空间中灰色统计数据位置的平均值来估计光源, 此估计基于灰度世界假设. 因此, 输入统计数据中真实的灰色统计数据很重要. 通用环境下,灰色统计数据的平均值会遭到灰色区中的非灰色统计数据污染. 因此,估计结果的精确性依赖与选择灰…...
微信小程序面试内容整理-数据绑定
在微信小程序中,数据绑定是将 JavaScript 中的数据与 WXML 中的视图进行关联和同步的过程。它使得视图可以根据数据的变化自动更新,极大地简化了前端开发的复杂度。 数据绑定有两种主要类型: 1. 单向数据绑定:数据从 Java器更新到视图。...
蓝桥每日打卡--背包问题
#蓝桥#JAVA#背包问题 题目描述 有一个背包,它的容量为 W 4。有 3 个物品,它们的重量分别为 [2, 1, 3],价值分别为 [4, 2, 3]。问在不超过背包容量的情况下,能装入背包的物品的最大价值是多少? 解题思路 动态规划的…...
基于图像比对的跨平台UI一致性校验工具开发全流程指南——Android/iOS/Web三端自动化测试实战
一、需求背景与方案概述 1.1 为什么需要跨平台UI校验? 在移动互联网时代,同一产品需覆盖Android、iOS和Web三端。由于不同平台的开发框架(如Android的Material Design与iOS的Cupertino风格)及渲染引擎差异,UI界面易出…...
3D点云目标检测——KITTI数据集读取与处理
一、 数据基本情况 KITTI数据集是由德国卡尔斯鲁厄理工学院和丰田美国技术研究院联合创建的一个大规模自动驾驶场景下的计算机视觉算法评测数据集。以下是关于它的详细介绍: 数据集背景:为评估自动驾驶中计算机视觉算法的性能而设计。自动驾驶汽车需在…...
【鸿蒙开发】Hi3861学习笔记- 外部中断
00. 目录 文章目录 00. 目录01. 概述02. EXTI相关API03. 硬件设计04. 软件设计05. 实验现象06. 附录 01. 概述 我们在做按键控制实验时,虽然能实现 IO 口输入功能,但代码是一直在检测 IO 输入口的变化,因此效率不高,特别是在一些…...
技术与情感交织的一生 (一)
目录 一条朋友圈 静默 至暗时刻 选择 成人高考 歇一下 一条朋友圈 大年初一是我合作伙伴的生日,我称呼他为老高,他发的朋友圈写到:“50岁了,留下的皆是珍贵回忆。” ,看到留言的瞬间,只有一个感觉&a…...
30天学习Java第六天——Object类
Object类 java.lang.Object时所有类的超类。Java中所有类都实现了这个类中的方法。 toString方法 将Java对象转换成字符串的表示形式。 public String toString() {return getClass().getName() "" Integer.toHexString(hashCode()); }默认实现是:完…...
基于WebRTC与P2P技术,嵌入式视频通话EasyRTC实现智能硬件音视频交互,适配Linux、ARM、RTOS、LiteOS
EasyRTC不仅仅是一个连接工具,更是一个经过深度优化的通信桥梁。它在嵌入式设备上进行了特殊优化,通过轻量级SDK设计、内存和存储优化以及硬件加速支持,解决了传统WebRTC在嵌入式设备上的适配难题,显著节省了嵌入式设备的资源。 1…...
向量库集成指南
文章目录 向量库集成指南Chroma集成Pinecone集成MiLvus集成向量库集成指南 向量库是一种索引和存储向量嵌入以实现高效管理和快速检索的数据库。与单独的向量索引不同,像Pinecone这样的向量数据库提供了额外的功能,例如,索引管理、数据管理、元数据存储和过滤,以及水平扩展…...
深度研究deep-research优秀开源项目
原文链接:https://i68.ltd/notes/posts/20250305-deep-research2/ 港大开源AI科研神器AI-Researcher 项目仓库:GitHub - HKUDS/AI-Researcher: "AI-Researcher: Fully-Automated Scientific Discovery with LLM Agents" & "Open-Sourced Alternative to G…...
芯谷D8563TS:低功耗CMOS实时时钟/日历电路的优选方案
在电子设备中,实时时钟(RTC)电路对于提供准确的时间和日历信息至关重要。芯谷D8563TS作为一款低功耗的CMOS实时时钟/日历电路,以其丰富的功能、高精度和灵活的可编程性,成为众多嵌入式系统和电池供电设备中的理想选择。…...
FPGA中级项目1——IP核(ROM 与 RAM)
FPGA中级项目1——IP核(ROM 与 RAM) IP核简介 在 FPGA(现场可编程门阵列)设计中,IP 核(Intellectual Property Core,知识产权核)是预先设计好的、可重用的电路模块,用于实…...
Redis的持久化-AOF
1.AOF AOF(Append Only File)持久化:以独立日志的方式记录每次写命令,重启时在重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。理解掌握好A…...
jmeter-sample
jmeter-sample http request:接口测试常用请求参数ParametersBody DataFiles Upload jdbc request配置JDBC Connection Configuration创建JDBC Requst请求 http request:接口测试常用 请求参数 Parameters 常见于get请求,与拼在接口后面是一样的效果:如…...
2025-03-15 学习记录--C/C++-PTA 练习3-4 统计字符
合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。💪🏻 一、题目描述 ⭐️ 练习3-4 统计字符 本题要求编写程序,输入10个字符,统计其中英文字母、空格或回车、…...
编程自学指南:java程序设计开发,网络编程基础,TCP编程,UDP编程,HTTP客户端开发
编程自学指南:java程序设计开发,网络编程基础 学习目标: 理解网络协议(TCP/IP、UDP)的核心概念 掌握Socket编程实现客户端与服务端通信 能够通过多线程处理并发网络请求 开发简单的网络应用(如聊天程序…...
C++ primer plus 类和对象
目录 前言 一 接口的设计 二 方法的设计和使用 三 构造函数 四 析构函数 五 析构函数和构造函数小结 总结 前言 前面已经描述了很多有关于类和对象的知识了,所以我们直接开始上手操作 一 接口的设计 首先我们要知道什么是接口 接口是一个…...
k8s 修改节点驱逐阈值
编辑 /var/lib/kubelet/config.yaml 文件 kind: KubeletConfiguration evictionHard:nodefs.available: "5%" # 降低磁盘压力触发阈值imagefs.available: "10%" # 调整容器镜像存储触发阈值nodefs.inodesFree: "3%...
HiPixel开源AI驱动的图像超分辨率的原生macOS 应用程序,使用 SwiftUI 构建并利用 Upscayl 强大的 AI 模型
一、软件介绍 文末提供程序和源码下载 HiPixel是一个开源程序基于SwiftUI构建的macOS原生应用程序,用于AI驱动的图像超分辨率,并利用Upscayl的强大AI模型。 二、软件特征 具有 SwiftUI 界面的原生 macOS 应用程序使用 AI 模型进行高质量图像放大通过 G…...
使用 .NET Core 实现 RabbitMQ 消息队列的详细教程
RabbitMQ 是一个流行的消息队列中间件,它允许应用程序通过异步消息的方式进行通信。RabbitMQ 支持 AMQP 协议,可以通过多种方式与应用程序交互。在本教程中,我们将深入探讨如何在 .NET Core 环境中使用 RabbitMQ 来实现消息队列。我们将学习如…...
深度学习——同一台电脑使用ssh配置多个github账号
如果一台电脑只有一个github账号,那么进行默认的ssh配置,通过git拉取和提交代码即可,但在实际的工作中,有时候需要在一台电脑登录多个github账号,将不同的项目代码提交到不同的github账号,这个时候如果仅仅…...
windows常用cmd命令
Windows 命令提示符(CMD)提供了许多实用的命令,用于管理文件、目录、网络、系统配置等。以下是一些常用的 CMD 命令及其用途: 文件和目录操作 dir: 列出当前目录下的文件和子目录。 dircd: 切换当前目录。 cd C:\Users cd .. # 返…...
C语言中的流程控制语句
一.流程控制语句的分类: 1.顺序结构 概念:从上往下依次执行,也是程序默认的执行顺序 2.分支结构 概念:程序在执行的过程中出现了岔路(我们只能选择一条支线进行执行) (1).if语句…...
C语言【数据结构】:理解什么是数据结构和算法(启航)
引言 启航篇,理解什么是数据结构和算法 在 C 语言编程领域,数据结构和算法是两个核心且紧密相关的概念 一、数据结构 定义 数据结构是指相互之间存在一种或多种特定关系的数据元素的集合(比如数组),它是组织和存储数…...
WebLogic XMLDecoder反序列化漏洞(CVE-2017-10271)深度解析与实战复现
0x00 漏洞概述 CVE-2017-10271 是Oracle WebLogic Server WLS Security组件中的远程代码执行漏洞。攻击者通过构造恶意XML请求,利用XMLDecoder反序列化机制绕过安全验证,最终实现服务器权限接管。 影响版本 WebLogic 10.3.6.0WebLogic 12.1.3.0WebLog…...
【动态规划篇】746.使用最小花费爬楼梯
746.使用最小花费爬楼梯 题目链接: 746.使用最小花费爬楼梯 题目叙述: 给你一个整数数组 cost ,其中 cost[i] 是从楼梯第i个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。 你可以选择从下标为 …...
类和对象:
1. const运算符重载: 1. const成员函数: 我们来看我们的下面的代码: 我们来看这个,我们的对象使用const进行修饰,然后我们对象d1调用我们的成员函数,然后我们取d1的地址然后传过去,这时候我们的…...
研究整除的性质——最大公约数(GCD)和最小公倍数(LCM)
最大公约数(GCD)和最小公倍数(Least Common Multiple,LCM)研究整除的性质,非常古老,在2000多年前就得到了很好的研究。由于简单易懂,有较广泛的应用,它们是竞赛中频繁出现…...
jenkins 配置邮件问题整理
版本:Jenkins 2.492.1 插件: A.jenkins自带的, B.安装功能强大的插件 配置流程: 1. jenkins->系统配置->Jenkins Location 此处的”系统管理员邮件地址“,是配置之后发件人的email。 2.配置系统自带的邮件A…...
FastAPI复杂查询终极指南:告别if-else的现代化过滤架构
title: FastAPI复杂查询终极指南:告别if-else的现代化过滤架构 date: 2025/3/14 updated: 2025/3/14 author: cmdragon excerpt: 本文系统讲解FastAPI中复杂查询条件的构建方法,涵盖参数验证、动态过滤、安全防护等18个核心技术点。通过引入策略模式、声明式编程等技术,彻…...
MySQL行列转化
初始化表结构: CREATE TABLE student_scores (student_id int NOT NULL,student_name varchar(50) DEFAULT NULL,math_score int DEFAULT NULL,english_score int DEFAULT NULL,science_score int DEFAULT NULL,PRIMARY KEY (student_id) ) ENGINEInnoDB DEFAULT C…...
施磊老师c++(六)
继承与多态-多重继承 文章目录 继承与多态-多重继承1.虚基类和虚继承本节内容 2.菱形继承---怎么解决?本节内容**面试问题: 怎么理解多重继承的?**---重点 3.c提供的四种类型转换本节内容 1.虚基类和虚继承 本节内容 多重继承? 代码复用, 一个派生类 有多个基类 抽象类—有…...
c++:AVL树
1.概念 由于二叉搜索树不能确保为近似完全二叉树的结构,节点相同的情况下,高度可能会很高,高度有可能会很低,所以搜索次数不能稳定维持在logn级别。我们在二叉搜索树的基础上进行平衡调整就可以控制搜索次数稳定在logn级别。 而AV…...
HTML编辑MP4保存名称
上图是HTML的界面,需要点击EDIT_MP4的选项,然后就出现文本框输入MP4名称。输入对应的MP4文件名称后,则点击Add_MP4按钮就可以把MP4的名称修改到json文件里面,json文件是network_detail.json文件。 HTML和CGI程序的交互 上图是htm…...
以太坊AI代理与PoS升级点燃3月市场热情,2025年能否再创新高?
币热网深度报道:以太坊AI代理与PoS升级引爆3月热潮,2025年能否再攀历史新高? 原文来源:币热网 - 区块链信息资讯平台 以太坊升级,市场热情高涨 近期,以太坊市场犹如被一股神秘力量点燃,掀起了…...
IDEA2024又一坑:连接Docker服务连不上,提示:Cannot run program “docker“: CreateProcess error=2
为新电脑安装了IDEA2024版,因为局域网中安装有Docker,所以这台电脑上没有安装,当运行时发现死活连不上Docker报:Cannot run program “docker“: CreateProcess error2 分析: Docker服务有问题 其它电脑都能连,排除 网…...
css基本功
为什么 ::first-letter 是伪元素? ::first-letter 的作用是选择并样式化元素的第一个字母,它创建了一个虚拟的元素来包裹这个字母,因此属于伪元素。 grid布局 案例一 <!DOCTYPE html> <html lang"zh-CN"><head&…...
ALSA vs OSS:Linux 音频架构的演变与核心区别
在 Linux 音频系统的发展过程中,OSS(Open Sound System) 和 ALSA(Advanced Linux Sound Architecture) 曾分别在不同阶段承担着音频管理的角色。OSS 是 Linux 早期的音频架构,而 ALSA 作为其继任者…...
双指针算法介绍+算法练习(2025)
一、介绍双指针算法 双指针(或称为双索引)算法是一种高效的算法技巧,常用于处理数组或链表等线性数据结构。它通过使用两个指针来遍历数据,从而减少时间复杂度,避免使用嵌套循环。双指针算法在解决诸如查找、排序、去重…...
第八节:红黑树(初阶)
【本节要点】 红黑树概念红黑树性质红黑树结点定义红黑树结构红黑树插入操作的分析 一、红黑树的概念与性质 1.1 红黑树的概念 红黑树 ,是一种 二叉搜索树 ,但 在每个结点上增加一个存储位表示结点的颜色,可以是 Red和 Black 。 通过对 任何…...