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编程 …...
双指针算法专题之——复写零
文章目录 题目介绍思路分析异地复写优化为就地复写 AC代码 题目介绍 链接: 1089. 复写零 思路分析 那么这道题我们依然可以使用双指针算法来解决 异地复写 先不考虑题目的要求,直接就地在原数组上修改,可能不太好想,我们这里可以先在一个…...
【Pandas】pandas Series last_valid_index
Pandas2.2 Series Time Series-related 方法描述Series.asfreq(freq[, method, how, …])用于将时间序列数据转换为指定的频率Series.asof(where[, subset])用于返回时间序列中指定索引位置的最近一个非缺失值Series.shift([periods, freq, axis, …])用于将时间序列数据沿指…...
python-leetcode-子数组最大平均数 I
643. 子数组最大平均数 I - 力扣(LeetCode) 可以使用滑动窗口(Sliding Window)的方法来解决这个问题。具体步骤如下: 先计算数组 nums 中前 k 个元素的和 sum_k,作为初始窗口的和。然后滑动窗口࿰…...
【度的数量——数位DP】
题目 分析 数位DP可以解决“区间内满足某种性质的数的个数”的问题 通常按照数位分支,形成一颗数位树 最左分支的值由上界值决定,右分支可以直接计算权重 有可能最左分支会有一个权重 代码 #include <bits/stdc.h> using namespace std;cons…...
STM32使用EXTI触发进行软件消抖(更新中)
在STM32的HAL库中,为了实现按键的软件消抖,通常需要在按键中断处理或轮询程序中加入一定的延时和状态检测逻辑。以下是一个简单的示例,展示了如何使用HAL库来实现按键的软件消抖。 假设你有一个按键连接到GPIO引脚,并且已经配置好…...
计算机操作系统进程(3)
系列文章目录 第二章:进程的描述与控制 文章目录 系列文章目录前言一、进程同步的基本概念:二、临界资源:总结 前言 前面我们学习了进程的定义和特征,进程状态的转换,接下来我们开始学习我们最重要的一点也是相对最难…...
搭建阿里云专有网络VPC
目录 一、概述 二、专有网络vpc 2.1 vpc基本信息 2.2 vpc资源管理 2.3 vpc网段管理 三、交换机 四、NAT网关 4.1 绑定弹性公网IP 4.2 NAT网关信息 4.3 绑定的弹性公网IP 4.4 DNAT 4.5 SNAT 五、弹性公网IP 六、访问控制ACL(绑定交换机) 6…...
centos steam8 部署k8s
kubernetes搭建 文章目录 kubernetes搭建[toc] 准备工作(三节点)安装docker(三节点)安装cri-dockerd(三节点)添加阿里云软件源(三节点)安装kubeadm、kubelet、kubectl(三节点)初始化…...
DB2 字符串比较 (= 或 IN) 时,忽略末尾的空格踩坑与解决方法
一、问题描述 在 DB2 中,VARCHAR 类型的字段在 字符串比较 ( 或 IN) 时会忽略末尾的空格,这可能导致查询结果与预期不符。例如: SELECT * FROM t_user WHERE id IN (016110110000011763); 如果 id 字段中存储的值为016110110000011763 &…...
windows系统,pycharm运行.sh文件
博主亲身试验过,流程简单,可用。 需要pycharm ,git。 注意需要Git Bash.exe ,也就是Git Bash的应用程序,而不是快捷方式。 需要把这个应用程序的路径复制一下。可以通过右键,复制文件地址的方式。 接着在…...
论文调研 | 一些开源的AI代码生成模型调研及总结【更新于250313】
本文主要介绍主流代码生成模型,总结了基于代码生成的大语言模型,按照时间顺序排列。 在了解代码大语言模型之前,需要了解代码相关子任务 代码生成 文本生成代码(Text to code):根据自然语言描述生成代码 重构代码(Refactoring …...
筛选法找质数(信息学奥赛一本通-2040)
【题目描述】 用筛法求出n(2≤n≤1000)以内的全部质数。 【输入】 输入n。 【输出】 多行,由小到大的质数。 【输入样例】 10 【输出样例】 2 3 5 7 【题解代码】 #include<bits/stdc.h> using namespace std;const int N 1e3 10; int nums[N];void isprim…...
第5关:猴子爬山
任务描述 本关任务:一个顽猴在一座有n级台阶的小山上爬山跳跃,猴子上山一步可跳1级,或跳3级,试求上山的n级台阶有多少种不同的爬法。 编程要求 根据提示,在右侧编辑器补充代码,求上山的n级台阶有多少种不同…...
保险项目的基本流程
保险项目的基本流程通常包括以下几个阶段,涵盖从产品设计到理赔的完整生命周期: 1. 保险产品设计与开发 市场调研:分析目标客户需求、市场趋势、监管要求。产品设计:确定保障范围、保险责任、保费计算方式、免责条款等。风险评估…...
Unity Timeline 扩展
这里认为大家已经会timeline的基本使用了,只介绍怎么自定义扩展。 第一步.自定义Track 首先要自定义一条轨道。剪辑是要在轨道里跑的,系统自带的轨道我们加不了自定义剪辑,得新建自己用的。这个很简单。 [TrackClipType(typeof(TransformTw…...
qt介绍信号槽一
信号和槽时qt框架中事件处理的一种机制,qt是基于窗口框架的程序,基于窗口框架额程序都是基于事件的,本质信号对应的就是一个事件,槽对应事件处理的动作。信号槽机制类似于设计模式力的观察者模式。观察者模式就是我一直观察是否有…...
【linux】解决 Linux 系统中 root 用户无法打开图形界面问题
【linux】解决 Linux 系统中 root 用户无法打开图形界面问题 问题描述: 在 Linux 系统中,当我们远程SSH尝试以 root 用户身份运行需要图形界面的应用程序时,可能会遇到以下错误信息: MoTTY X11 proxy: Unsupported authorisati…...
【开源项目-爬虫】Firecrawl
看到其他项目引用了这个项目 Firecrawl 用免费额度试了一下,这个项目和之前的 https://r.jina.ai/ 很像(类似的还有 https://www.scrapingbee.com/?),将爬取到的网页转换为 markdown 格式,这样大语言模型用…...
【已解决】电脑空间告急?我的 Ollama、Docker Desktop软件卸载清理全记录
一、卸载 Ollama、Windows SDK 和 Docker Desktop的原因 最近电脑总提示空间不足,前段时间想本地部署大模型而安装的 Ollama、多个 Windows SDK 以及暂时用不到的 Docker Desktop 占用了不少空间。果断动手卸载,现在把过程整理成博客,分享给同…...
便利店商品推荐数字大屏:基于python和streamlit
基于python和streamlit实现的便利店商品推荐大屏,针对选择困难症消费者。 import streamlit as st import pandas as pd import numpy as np import altair as alt from datetime import datetime, timedelta import time# 模拟数据生成 def generate_data():np.ra…...
OpenAI智能体初探:使用 OpenAI Responses API 在 PDF 中实现检索增强生成(RAG)
大家好,我是大 F,深耕AI算法十余年,互联网大厂技术岗。 知行合一,不写水文,喜欢可关注,分享AI算法干货、技术心得。 欢迎关注《大模型理论和实战》、《DeepSeek技术解析和实战》,一起探索技术的无限可能! 引子 在信息爆炸的时代,从大量 PDF 文档中快速准确地检索信息…...
什么是物理信息神经网络PINN
定义原理 物理信息神经网络(PINN)是一种创新的机器学习方法,将深度学习与物理知识相结合,旨在解决偏微分方程(PDE)相关问题。PINN的核心思想是在神经网络的训练过程中引入物理定律,从而提高模型的泛化能力和预测精度。 PINN的工作原理基于以下关键步骤: 构建神经网络…...
【实战-解决方案】Webpack 打包后很多js方法报错:not defined
问题分析 在不打包的情况下,方法(如 checkLoginStatus、filterSites、initProgressBar 等)可以正常运行,而经过 Webpack 打包后报 is not defined 错误,通常有以下几个可能的原因: 全局变量丢失 在 Webpac…...
【大模型基础_毛玉仁】2.3 基于 Encoder-only 架构的大语言模型
更多内容:XiaoJ的知识星球 目录 2.3 基于Encoder-only 架构的大语言模型2.3.1 Encoder-only 架构2.3.2 BERT 语言模型1)BERT 模型结构2)BERT 预训练方式3)BERT 下游任务 2.3.3 BERT 衍生语言模型1)RoBERTa 语言模型2&a…...
链表所有节点值的和
class Node:# 节点类,每个节点包含数据(data)和指向下一个节点的引用(next)def __init__(self, data):self.data data # 存储节点的数据self.next None # 指向下一个节点,默认值为None,表示没有下一个节点class LinkedList:# 链表类&…...
[TPCTF 2025] crypto 复现两题
周末很忙。比赛都没怎么看。晚上把密码复现两个。 randomized random 这题在小鸡块博客里见过,稍有区别。 # FROM python:3 import random with open("flag.txt","rb") as f:flagf.read() for i in range(2**64):print(random.getrandbits(3…...
STM32 F407ZGT6开发板
#ifndef _tftlcd_H #define _tftlcd_H #include "system.h" //定义LCD彩屏的驱动类型 可根据自己手上的彩屏背面型号来选择打开哪种驱动 //#def…...
Java中的try-catch在jvm层面是怎么做的?
简单描述 java中的try-catch通过异常表和栈展开来实现 异常表(exception-table) 每个方法的字节码中都有一个异常表,用于记录try-catch块的作用范围和对应的异常处理逻辑 异常表的每个条目包含以下信息: 起点,终点…...
c# txt文档的实时显示,用来查看发送接收指令
通讯历史按钮 private void uiButton1_Click(object sender, EventArgs e){try{logf new logF();logf.Show();}catch (Exception){throw;} }主页面关闭函数(点击保存就为true true就不删除) private void page1_FormClosed(object sender, FormClos…...
