netty——IO、NIO、AIO进化之路
IO、NIO、AIO进化之路
- BIO——同步阻塞IO
- 伪异步阻塞IO
- NIO——同步非阻塞IO
- AIO——异步IO
- 总结
本文会说明各种IO的特点、分别解决了什么样的问题做一个分析阐述,并结合Java代码例子来辅助理解,像这些的历史演进和详细的底层原理网上很多,所以我们只站在应用层,使用者的角度去分析
(所有例子均可直接运行)
BIO——同步阻塞IO
看这个名称大家可能会有点陌生,我们直接上例子:
服务端:
public static void main(String[] args) throws IOException {//1.创建服务端Socket 并绑定端口ServerSocket serverSocket = new ServerSocket(8080);//2.等待客户端连接 阻塞的Socket accept = serverSocket.accept();System.out.println(accept.getRemoteSocketAddress() + " 客户端已连接");//3.获取输入、输出流InputStream inputStream = accept.getInputStream();OutputStream outputStream = accept.getOutputStream();//4.接收客户端信息byte[] bytes = new byte[1024];inputStream.read(bytes);String data = new String(bytes);System.out.println("来自" + accept.getRemoteSocketAddress() + "的信息:" + data);//5.返回信息outputStream.write(data.getBytes());accept.shutdownOutput();//6.关闭资源inputStream.close();outputStream.close();accept.close();serverSocket.close();}
客户端:
public static void main(String[] args) throws IOException {//1.创建客户端SocketSocket socket = new Socket("127.0.0.1",8080);//2.获取输入、输出流InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();//3.给服务端发送信息outputStream.write("你好".getBytes());socket.shutdownOutput();//4.获取服务端返回信息byte[] data = new byte[1024];inputStream.read(data);System.out.println("来自服务端的信息:" + new String(data));//6.关闭资源inputStream.close();outputStream.close();socket.close();}
这就是我们熟知的Socket连接,也是Java最早的网络通信IO,为什么这种叫同步阻塞IO:
因为在做read操作、accept操作的时候会阻塞没法往下执行,说白了就是串行的,就因为这个服务端和客户端只能1对1通信,这合理嘛?肯定不合理啊,所以进阶的有了伪异步IO
伪异步阻塞IO
看完上面的,很多人就有想法了,你说同步的只能1对1通信,那我直接把服务端改成多线程版本不就好了嘛,不就可以1对多通信了嘛,没错这版本确实是这样,如下:
服务端:
public static void main(String[] args) throws IOException {//1.创建服务端Socket 并绑定端口ServerSocket serverSocket = new ServerSocket(8080);//2.等待客户端连接 多线程模式 (开线程异步等待)new Thread(()->{while (true){try {Socket accept = serverSocket.accept();System.out.println(accept.getRemoteSocketAddress() + " 客户端已连接");// 开线程异步处理客户端连接任务new Thread(new AcceptHandler(accept)).start();} catch (IOException e) {e.printStackTrace();}}}).start();// 阻塞防止程序退出while (true){}}private static class AcceptHandler implements Runnable{private Socket accept;private InputStream inputStream = null;private OutputStream outputStream =null;public AcceptHandler(Socket accept){this.accept=accept;}@Overridepublic void run() {try {//3.获取输入、输出流inputStream = accept.getInputStream();outputStream = accept.getOutputStream();//4.接收客户端信息byte[] bytes = new byte[1024];inputStream.read(bytes);String data = new String(bytes);if(data!=null){System.out.println("来自" + accept.getRemoteSocketAddress() + "的信息:" + data);//5.返回信息outputStream.write(data.getBytes());accept.shutdownOutput();}} catch (IOException e) {System.out.println(accept.getRemoteSocketAddress() + "发送异常断开连接");closeSource();}finally {System.out.println(accept.getRemoteSocketAddress() + "断开连接");closeSource();}}private void closeSource(){//6.关闭资源try {if(inputStream!=null){inputStream.close();}if(outputStream!=null){outputStream.close();}accept.close();} catch (IOException ioException) {ioException.printStackTrace();}}}
客户端不变,服务端我们做了三个改动:
- 一:在等待客户端连接的时候我们开启一个线程,并死循环等待连接,这样可以保证不阻塞主线程的运行,同时可以不断的和客户端建立连接
- 二:和客户端建立连接后又开启一个线程来单独处理与客户端的通信
- 三:最后加了个死循环防止程序退出,因为现在是异步的了
这样处理不就是异步的了吗?为什么叫伪异步阻塞IO呢?
虽然现在不会阻塞主线程了,但是阻塞并没有解决,该阻塞的地方依旧还是会阻塞,所以本质上来说只是解决了1对1连接通信的问题
但是新的问题又来了,现在虽然是1对多通信,但是有一个客户端连接就新建一个线程,1万个客户端就1万个线程,这合理吗?这明显不合理啊,用线程池管理?那也不行啊,这连接一多还要排队吗?极端情况下,队列不一样会爆?
那怎么办?有没有可能一个线程监听多个连接呢?于是有了NIO
NIO——同步非阻塞IO
NIO的引入同时引入了三个概念ByteBuffer缓冲区、Channel通道和Selector多路复用器
- Channel的作用:就是一个通道,数据读取和写入的通道,根据功能可以分为不同的通道如:网络通道ServerSocketChannel和SocketChannel、文件操作通道FileChannel等等
- Selector的作用:是轮询Channel上面的事件,如读事件、写事件、连接事件、接受连接事件
- ByteBuffer缓冲区:就是向Channel读取或写入数据的对象,本质就是个字节数组
怎么理解这三个呢?说白了以传统IO为例:服务端accept就是接受连接事件、客户端connect就是连接事件、发送消息就是写事件、读取消息就是读事件
Selector就是监听这些事件的工具
ServerSocketChannel是服务端接受连接的通道,所以只能注册监听连接事件
SocketChannel是服务端与客户端连接建立后的通道,所以可以注册读写事件、连接事件
ByteBuffer就是Channel读取或写入数据的单位对象
下面搞个例子看看,注释全有:
服务端:
public static void main(String[] args) throws IOException {// 开启服务端Socket通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 设置为非阻塞serverSocketChannel.configureBlocking(false);// 绑定端口serverSocketChannel.socket().bind(new InetSocketAddress(8080));// 打开多路复用器 并将其注册到通道上 监听连接请求事件Selector selector = Selector.open();// 为服务端Socket通道 注册一个接受连接的事件 // 假设有客户端要连接 下面轮询的时候就会触发这个事件 我们就可以去与客户端建立连接了serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) {// 这段时间没获取到任何事件,则跳过下面操作// 不同于IO和BIO的阻塞 多路复用器会一直轮询 如果长时间无事件 这里会一直空循环// 所以这里在查询事件的时候加了个时间 这样无事件的情况下 1s才会循环一次if (selector.select(1000) == 0) {continue;}// 获取到本次轮询所获取到的全部事件Iterator<SelectionKey> selectorKeys = selector.selectedKeys().iterator();// 轮询获取到的事件,并处理while (selectorKeys.hasNext()) {SelectionKey selectorKey = selectorKeys.next();//这个已经处理的事件Key一定要移除。如果不移除,就会一直存在在selector.selectedKeys集合中//待到下一次selector.select() > 0时,这个Key又会被处理一次selectorKeys.remove();try {// 事件key处理 也就是事件处理selectorKeyHandler(selectorKey, selector);} catch (Exception e) {SocketChannel channel = (SocketChannel) selectorKey.channel();System.out.println(channel.getRemoteAddress() + "客户端已断开连接");if (selectorKey != null) {selectorKey.cancel();if (selectorKey.channel() != null) {selectorKey.channel().close();}}}}}}// 事件处理方法 按照事件类型处理不同的事件public static void selectorKeyHandler(SelectionKey selectorKey, Selector selector) throws IOException {// 连接事件 代表有客户端连接 所以需要去处理这个连接请求if (selectorKey.isAcceptable()) {acceptHandler(selectorKey, selector);}// 读事件 可以去读取信息if (selectorKey.isReadable()) {readHandler(selectorKey, selector);}// 写事件 可以向客户端发送信息if (selectorKey.isWritable()) {SocketChannel socketChannel = (SocketChannel) selectorKey.channel();writeHandler(socketChannel);// 写事件完成后要取消写事件不然会一直写 我这里就干脆注册了个读事件socketChannel.register(selector,SelectionKey.OP_READ);}}// 连接事件处理 这个有客户端要建立连接了 所以accept与客户端建立连接public static void acceptHandler(SelectionKey selectorKey, Selector selector) throws IOException {ServerSocketChannel channel = (ServerSocketChannel) selectorKey.channel();SocketChannel accept = channel.accept();// 建立连接后 客户端和服务端就等于形成了一个数据交互的通道 SocketChannel// 这个通道也要设置为非阻塞accept.configureBlocking(false);// 为这个通道注册一个读事件 表示我先读取客户端信息accept.register(selector, SelectionKey.OP_READ);System.out.println(accept.getRemoteAddress() + "客户端已连接");}// 读事件处理 读取客户端的信息public static void readHandler(SelectionKey selectorKey, Selector selector) throws IOException {SocketChannel channel = (SocketChannel) selectorKey.channel();ByteBuffer allocate = ByteBuffer.allocate(1024);int read = channel.read(allocate);if (read > 0) {allocate.flip();byte[] bytes = new byte[allocate.remaining()];allocate.get(bytes);System.out.println(channel.getRemoteAddress() + "发来消息:" + new String(bytes));}if(read<0){System.out.println(channel.getRemoteAddress() + "断开连接");}// 读完信息后要给客户端发送信息 所以这个再注册一个写的事件channel.register(selector, SelectionKey.OP_WRITE);}// 写事件处理public static void writeHandler(SocketChannel socketChannel) throws IOException {byte[] bytes = "你好".getBytes();ByteBuffer allocate = ByteBuffer.allocate(bytes.length);allocate.put(bytes);allocate.flip();socketChannel.write(allocate);}
客户端:
public static void main(String[] args) throws IOException {// 开启一个Socket通道SocketChannel clientChannel = SocketChannel.open();// 设置非阻塞clientChannel.configureBlocking(false);// 允许端口复用clientChannel.socket().setReuseAddress(true);// 连接地址clientChannel.connect(new InetSocketAddress("127.0.0.1", 8080));// 开启多路复用器Selector selector = Selector.open();// 为这个通道注册一个连接事件clientChannel.register(selector, SelectionKey.OP_CONNECT);while (true) {// 这段时间没获取到任何事件,则跳过下面操作// 不同于IO和BIO的阻塞 多路复用器会一直轮询 如果长时间无事件 这里会一直空循环// 所以这里在查询事件的时候加了个时间 这样无事件的情况下 1s才会循环一次if (selector.select(1000) == 0) {continue;}// 获取到本次轮询所获取到的全部事件Iterator<SelectionKey> selectorKeys = selector.selectedKeys().iterator();// 轮询获取到的事件,并处理while (selectorKeys.hasNext()) {SelectionKey selectorKey = selectorKeys.next();//这个已经处理的事件Key一定要移除。如果不移除,就会一直存在在selector.selectedKeys集合中//待到下一次selector.select() > 0时,这个Key又会被处理一次selectorKeys.remove();try {// 事件key处理selectorKeyHandler(selectorKey, selector);} catch (Exception e) {if (selectorKey != null) {selectorKey.cancel();if (selectorKey.channel() != null) {selectorKey.channel().close();}}}}}}// 事件处理方法public static void selectorKeyHandler(SelectionKey selectorKey, Selector selector) throws IOException {// 连接事件 判断是否连接成功if (selectorKey.isValid()) {SocketChannel channel = (SocketChannel) selectorKey.channel();if (selectorKey.isConnectable() && channel.finishConnect()) {System.out.println("连接成功........");// 连接成功注册写事件 向服务端发送信息channel.register(selector,SelectionKey.OP_WRITE);}}// 读事件 可以去读取信息if (selectorKey.isReadable()) {readHandler(selectorKey, selector);}// 写事件 可以向客户端发送信息if (selectorKey.isWritable()) {SocketChannel channel = (SocketChannel) selectorKey.channel();writeHandler(channel);// 写事件完成后要取消写事件不然会一直写 我这里就干脆注册了个读事件channel.register(selector,SelectionKey.OP_READ);}}// 读事件处理 就是处理服务端发来的消息public static void readHandler(SelectionKey selectorKey, Selector selector) throws IOException {SocketChannel channel = (SocketChannel) selectorKey.channel();ByteBuffer allocate = ByteBuffer.allocate(1024);int read = channel.read(allocate);if (read > 0) {allocate.flip();byte[] bytes = new byte[allocate.remaining()];allocate.get(bytes);System.out.println("服务端发来消息:" + new String(bytes));}if(read<0){System.out.println("与服务端断开连接");}}// 写事件处理 就是像服务端发送消息public static void writeHandler(SocketChannel socketChannel) throws IOException {byte[] bytes = "你好".getBytes();ByteBuffer allocate = ByteBuffer.allocate(bytes.length);allocate.put(bytes);allocate.flip();socketChannel.write(allocate);}
可以看到写法和传统的IO完全不一样了,操作的对象都是Channel,读写对象都是ByteBuffer,那到底是什么引起了这种改变呢?因为系统内核的优化,说白了这种操作都是API,底层都是需要系统支持的,系统在这块也有一个模型优化,简单介绍三种模型区别:
- select: 每有一个连接的产生会打开一个Socket描述符(下面简称FD),select会把这些FD保存在一个数组中,因为是数组所以就代表有了容量的上限意味了连接数量的上限,每次调用,都会遍历这个数组,1w个连接就算只有一个事件,也会遍历这1w个连接,效率极低
- poll: 和select不同,这个底层结构是链表,所有没了连接数量的上限,但是每次调用依旧会遍历所有的
- epoll: 底层结构是红黑树,同样没有连接数量的上限,而且有一个就绪的事件列表,这意味着不再需要遍历所有的连接了
JDK中采用的就是epoll模型,但尽管这样也依旧是同步的,因为还是需要主动去获取结果,只是从方式阻塞等待变成了轮询,有没有什么方式在结果产生的时候异步的回调呢?于是有了AIO
AIO——异步IO
这种方式同样需要系统的支持,目前主流还是NIO,这块就不多介绍了,提供个例子:
服务端:
public static void main(String[] args) throws IOException {AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(8080));// 接收连接的时候 提供连接处理类serverSocketChannel.accept(serverSocketChannel, new ServerSocketHandler());// 异步的 防止程序退出while (true) {}}// 连接处理public static class ServerSocketHandler implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel> {@Overridepublic void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) {// 继续接受连接attachment.accept(attachment, this);try {System.out.println(result.getRemoteAddress() + " 已连接");} catch (IOException e) {e.printStackTrace();}new Thread(() -> {// 异步读readHandler(result);}).start();// 写数据处理writeHandler(result, "你好");}@Overridepublic void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {System.out.println("发生异常");}public void readHandler(AsynchronousSocketChannel socketChannel) {ByteBuffer allocate = ByteBuffer.allocate(1024);socketChannel.read(allocate, allocate, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {try {if (result > 0) {attachment.flip();byte[] bytes = new byte[attachment.remaining()];attachment.get(bytes);System.out.println(socketChannel.getRemoteAddress() + " 客户端消息: " + new String(bytes));readHandler(socketChannel);}} catch (IOException e) {e.printStackTrace();}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.out.println();try {System.out.println(socketChannel.getRemoteAddress() + " 已下线");socketChannel.close();} catch (IOException e) {e.printStackTrace();}}});}public void writeHandler(AsynchronousSocketChannel socketChannel, String data) {byte[] bytes = data.getBytes();ByteBuffer allocate = ByteBuffer.allocate(bytes.length);allocate.put(bytes);allocate.flip();socketChannel.write(allocate, allocate, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {if (attachment.hasRemaining()) {socketChannel.write(attachment, attachment, this);}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {try {socketChannel.close();} catch (IOException e) {e.printStackTrace();}}});}}
客户端:
public static void main(String[] args) throws IOException {AsynchronousSocketChannel socketChannel=AsynchronousSocketChannel.open();socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080), null, new AsyncClientHandler(socketChannel));while (true){}}public static class AsyncClientHandler implements CompletionHandler<Void, AsyncClientHandler>{private AsynchronousSocketChannel socketChannel;public AsyncClientHandler(AsynchronousSocketChannel socketChannel){this.socketChannel=socketChannel;}@Overridepublic void completed(Void result, AsyncClientHandler attachment) {new Thread(()->{// 异步 一秒发送一次消息while (true){writeHandler("你好");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}).start();// 读处理readHandler();}@Overridepublic void failed(Throwable exc, AsyncClientHandler attachment) {}public void readHandler() {ByteBuffer allocate = ByteBuffer.allocate(1024);socketChannel.read(allocate, allocate, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {attachment.flip();byte[] bytes = new byte[attachment.remaining()];attachment.get(bytes);System.out.println(" 服务端消息: " + new String(bytes));}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {try {socketChannel.close();} catch (IOException e) {e.printStackTrace();}}});}public void writeHandler( String data) {byte[] bytes = data.getBytes();ByteBuffer allocate = ByteBuffer.allocate(bytes.length);allocate.put(bytes);allocate.flip();socketChannel.write(allocate, allocate, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {if (attachment.hasRemaining()) {socketChannel.write(attachment, attachment, this);}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {try {socketChannel.close();} catch (IOException e) {e.printStackTrace();}}});}}
总结
| BIO | 伪异步IO | NIO | AIO | |
| 线程:客户端 | 1:1 | N:M (M可以大于N) | 1:N (一个线程处理多个) | 0:M (无需额外线程,异步回调) |
| I/O类型 | 同步阻塞 | 伪异步阻塞 | 同步非阻塞 | 异步非阻塞 |
| 可靠性 | 非常差 | 差 | 高 | 高 |
| 难度 | 简单 | 简单 | 复杂 | 复杂 |
| 性能 | 低 | 中 | 高 | 高 |
相关文章:
netty——IO、NIO、AIO进化之路
IO、NIO、AIO进化之路BIO——同步阻塞IO伪异步阻塞IONIO——同步非阻塞IOAIO——异步IO总结本文会说明各种IO的特点、分别解决了什么样的问题做一个分析阐述,并结合Java代码例子来辅助理解,像这些的历史演进和详细的底层原理网上很多,所以我们…...
AI稳定生成图工业链路打造
前沿这篇文章会以比较轻松的方式,跟大家交流下如何控制文本生成图片的质量。要知道如何控制文本生成质量,那么我们首先需要知道我们有哪些可以控制的参数和模块。要知道我们有哪些控制的参数和模块,我们就得知道我们文本生成图片的这架机器或…...
20230220华南金牌主板u盘启动
20230220华南金牌主板u盘启动 2023/2/20 10:29 百度搜索:华南金牌主板u盘启动 https://www.zhihu.com/question/498121895?utm_id0 华南金牌主板b85u盘启动怎么设置? 华南金牌主板b85u盘启动怎么设置 海的那边 上小学后才发现还是幼儿园好混…… 华南一般是F7和F1…...
测试团队都在用哪些不错的测试用例管理平台?盘点6大主流测试管理系统
测试团队使用的主流测试用例管理平台:1.PingCode ;2.TestRail;3.Testlink;4.ZephyrJira;5.TestCenter;6.飞蛾。目前市面上的测试用例管理工具有很多,但由于针对的项目、领域、目标用户ÿ…...
linux 系统编程之线程
线程 文章目录线程1 线程概念2 NPT安装线程 man page:查看指定线程的 LWP 号:3 线程的特点4 线程共享资源5 线程非共享资源6 线程的优缺点7线程常用操作1 线程号pthread_self函数:pthread_equal函数:参考代码2 错误返回值分析参考代码3 线程的…...
从0开始学python -35
Python3 File(文件) 方法 open() 方法 Python open() 方法用于打开一个文件,并返回文件对象。 在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError。 注意:使用 open() 方法一定要保证关闭文件对…...
1.14 golang中的结构体
1. 结构体 Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。 1.1. 类型别名和自定义类型 1.1.1. 自定义类型 在Go语言中有一些基本的数据类型,如string、整…...
原创不易,坚持更难
早上CSDN发消息,今天是创作满三年的纪念日,邀请写一篇博文,谈谈感受 开博原因 2020年是一个特殊的年份,疫情刚爆发第一年,也是第一次居家办公,从过完年就一直居家办公,一直居家了38天。2020年…...
计算机网络 | 谈谈TCP的流量控制与拥塞控制
文章目录一、TCP的流量控制1、利用滑动窗口实现流量控制【⭐⭐⭐】2、如何破解【死锁】局面❓二、TCP的拥塞控制1、拥塞控制的一般原理① 解决网络拥塞的误区② 拥塞控制与流量控制的关系【重点理解✔】2、TCP的拥塞控制方法① 接收窗口【rwnd】与拥塞窗口【cwnd】② 慢开始和拥…...
Flask入门(7):内置装饰器(钩子函数)
目录7.内置装饰器(钩子函数)7.1 before_request7.2 after_request7.3 before_first_request7.4 error_handlers7.5 template_filter7.6 template_global复习装饰器基础及其应用,可参考文章:闭包和装饰器 7.内置装饰器(…...
Java8新特性
✨作者:猫十二懿 ❤️🔥账号:CSDN 、掘金 、个人博客 、Github 🎉公众号:猫十二懿 写在最前面 在企业中更多的都是使用 Java8 ,随着 Java8 的普及度越来越高,很多人都提到面试中关于Java 8 也…...
哈希表题目:设计哈希集合
文章目录题目标题和出处难度题目描述要求示例数据范围解法一思路和算法代码复杂度分析解法二思路和算法代码复杂度分析题目 标题和出处 标题:设计哈希集合 出处:705. 设计哈希集合 难度 3 级 题目描述 要求 不使用任何内建的哈希表库设计一个哈希…...
java static关键字 万字详解
目录 一、为什么需要static关键字: 二、static关键字概述 : 1.作用 : 2.使用 : 三、static修饰成员变量详解 : 1.特点 : 2.细节 : ①什么时候考虑使用static关键字? ②静态变量和非静态变量的区别? ③关于静态变量的初始化问题 : ④关于静态变…...
光谱实验反射、透射光谱测量
标题反射、透射光谱测量的基本原理 暗背景/基线:Dark………………………………………………………………0% (空)白参考:Reference…………………………………………………………100% 样品反射/透射光谱:Sampl…...
【基础算法】之 冒泡排序优化
冒泡排序思想基本思想: 冒泡排序,类似于水中冒泡,较大的数沉下去,较小的数慢慢冒起来(假设从小到大),即为较大的数慢慢往后排,较小的数慢慢往前排。直观表达,每一趟遍历,…...
Python | 线程锁 | 3分钟掌握【同步锁】(Threading.Lock)
文章目录概念无锁加锁死锁解决死锁概念 threading.Lock 同步锁,可以用于保证多个线程对共享数据的独占访问。 当一个线程获取了锁之后,其他线程在此期间将不能再次获取该锁,直到该线程释放锁。这样就可以保证共享数据的独占访问,…...
Linux下安装MySQL8.0的详细步骤(解压tar.xz安装包方式安装)
Linux下安装MySQL8.0的详细步骤 第一步:下载安装配置 第二步:修改密码,并设置远程连接(为了可以在别的机器下面连接该mysql) 第三步:使用Navicat客户端连接 搞了一台云服务器,首先要干的活就是…...
leaflet 绘制多个点的envelope矩形(082)
第082个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中如何根据多边形的几个坐标点来绘制envelope矩形。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共78行)安装插件相关API参考:专栏目标示例…...
CAJ论文怎么批量免费转换成Word
大家都知道CAJ文件吗?这是中国学术期刊数据库中的文件,这种文件类型比较特殊。如果想要提取其中的内容使用,该如何操作呢?大家可以试试下面这种免费的caj转word的方法,多个文档也可以一起批量转换。准备材料:CAJ文档、…...
面试必问: 结构体大小的计算方法
结构体大小的计算需同时满足以下几点 一、结构体成员的偏移量必须是当前成员大小的整数倍。(0是任何数的整数倍) 举一个例子 struct Test1{char a; // 当前偏移量为0,是char所占字节数1的整数倍 所以所占大小为1char b; …...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
