netty——IO、NIO、AIO进化之路

news/2024/4/27 5:33:46/文章来源:https://blog.csdn.net/weixin_44102992/article/details/129131613

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伪异步IONIOAIO
线程:客户端1:1N:M
(M可以大于N)
1:N
(一个线程处理多个)
0:M
(无需额外线程,异步回调)
I/O类型同步阻塞伪异步阻塞同步非阻塞异步非阻塞
可靠性非常差
难度简单简单复杂复杂
性能

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【基础算法】之 冒泡排序优化

冒泡排序思想基本思想: 冒泡排序&#xff0c;类似于水中冒泡&#xff0c;较大的数沉下去&#xff0c;较小的数慢慢冒起来&#xff08;假设从小到大&#xff09;&#xff0c;即为较大的数慢慢往后排&#xff0c;较小的数慢慢往前排。直观表达&#xff0c;每一趟遍历&#xff0c;…

Docker----------day3

常规安装大体步骤 1.安装tomcat 1.查找tomcat docker search tomcat2.拉取tomcat docker pull tomcat3.docker images查看是否有拉取到的tomcat 4.使用tomcat镜像创建容器实例(也叫运行镜像) docker run -it -p 8080:8080 tomcat5.新版tomcat把webapps.dist目录换成webapp…

【大数据离线开发】7.4 HBase数据保存和过滤器

7.4 数据保存的过程 注意&#xff1a;数据的存储&#xff0c;都需要注意Region的分裂 HDFS&#xff1a;数据的平衡 ——> 数据的移动&#xff08;拷贝&#xff09;HBase&#xff1a;数据越来越多 ——> Region的分裂 ——> 数据的移动&#xff08;拷贝&#xff09; …

清理bib文件(删除重复项,仅保留tex中引用的条目)

在写latex文件的过程中&#xff0c;经常会遇到添加了一堆文献的bibtex到bib文件中&#xff0c;有时候文章一长同一篇文献用不同的cite-key引用了多次&#xff0c;同时也会有一些文献最后并没被正文引用&#xff0c;这就需要对bib文件进行清理。 删除重复项 可以用JabRef 在J…

经理与员工工资关系-课后程序(JAVA基础案例教程-黑马程序员编著-第四章-课后作业)

【案例4-6】经理与员工工资案例&#xff08;利用多态实现&#xff09; 欢迎点赞关注收藏 【案例介绍】 案例描述 某公司的人员分为员工和经理两种&#xff0c;但经理也属于员工中的一种&#xff0c;公司的人员都有自己的姓名和地址&#xff0c;员工和经理都有自己的工号、工…

不同投票需要的不同上传方式outlook 投票功能怎么设置投票 html5

“艺空间手造坊”网络评选投_投票方式的选择_免费图文教学投票教学关于微信投票&#xff0c;我们现在用的最多的就是小程序投票&#xff0c;今天的网络投票&#xff0c;在这里会教大家如何用“活动星投票”小程序来进行投票。我们现在要以“艺空间手造坊”为主题进行一次投票活…

AcWing1015.摘花生

AcWing 1015. 摘花生Hello Kitty想摘点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地(如下图)&#xff0c;从西北角进去&#xff0c;东南角出来。地里每个道路的交叉点上都有种着一株花生苗&#xff0c;上面有若干颗花生&#xff0c;经过一株花生苗就能摘走该它…

Java并发知识点

文章目录1. start()和run()方法的区别&#xff1f;2. volatile关键字的作用&#xff1f;使用volatile能够保证&#xff1a;防止指令重排3. sleep方法和wait方法有什么区别&#xff1f;sleep()方法4. 如何停止一个正在运行的线程&#xff1f;方法一&#xff1a;方法二&#xff1…

多重继承的虚函数表

同一个类,不同对象使用同一张虚函数表 不同类使用不同的虚函数表 子类自己添加的虚函数(非重写),在VS中是将此放在第一个继承类的虚函数表里. #include <iostream> using namespace std;class Father { public:virtual void func1() { cout << "Father::f…

<Linux>vscode搭建Linux远程开发工具

一、下载vscode&#x1f603;可以去vscode的官网下载&#xff0c;不过是外网下载速度较慢提速可以参考&#xff1a;(81条消息) 解决VsCode下载慢问题_vscode下载太慢_wang13679201813的博客-CSDN博客官网&#xff1a;Visual Studio Code - Code Editing. Redefined这里推荐的是…

【数据结构】二叉树的四种遍历

写在前面首先二叉树是一个大家族&#xff0c;这篇文章就讲一讲二叉树的遍历&#xff1a;递归遍历迭代遍历先识概念二叉树的存储结构&#xff0c;可以为顺序存储&#xff0c;即使用数组&#xff1b;也可以为链式存储&#xff0c;即使用链表。我们使用较多的就是链式存储结构&…

Ceres的自动求导实现原理剖析

目录数学原理实现原理总结首先注意数值求导和自动求导在使用的时候的不同之处。 实际上&#xff0c;正是自动求导这个地方使用了类模板&#xff0c;导致它不仅可以传入参数&#xff0c;还可以传入Jet类型的数据&#xff0c;从而实现了参数的雅可比矩阵的计算&#xff0c;完成自…

TPM密钥管理、使用

前面讲过证书相关内容&#xff0c;除了在软件方面有所应用外&#xff0c;在硬件方面也有很多应用。本次讲一下TPM相关的内容。 一、TPM介绍 1.1背景 TCG基于硬件安全的架构是为应对1990s后期日益增多的复杂恶意软件攻击应用而生的。当时以及现在&#xff0c;抵御PC客户端网络…

树状数组(高级数据结构)-蓝桥杯

一、简介树状数组 (Binary Indexed Tree,BIT)&#xff0c;利用数的二进制特征进行检索的一种树状结构。一种真正的高级数据结构&#xff1a; 二分思想、二叉树、位运算、前缀和。高效!代码极其简洁!二、基本应用数列a1,a2,....,an&#xff0c;操作&#xff1a;单点修改&#xf…

详解HashMap

目录 1.hash code 2.数据结构 3.初始化 4.存取 4.1.put 4.2.get 5.迭代 6.扩容 7.JDK1.7版本存在的问题 7.1.性能跌落 7.2.循环链表 8.散列运算 9.扰动函数 1.hash code hash code是使用hash函数运算得到的一个值&#xff0c;是对象的身份证号码&#xff0c;用于…

OpenSumi 是信创开发云的首选

原文作者&#xff1a;行云创新技术总监 邓冰寒 引言 随着云原生应用的日益普及&#xff0c;开发上云也逐步被越来越多的厂商和开发者接受&#xff0c;在这个赛道国内外有不少玩家&#xff0c;国外的 GitHub Codespaces、CodeSandbox&#xff0c;GitPod、亚马逊 Cloud9&#xf…

借力英特尔® Smart Edge,灵雀云 ACP 5G 专网解决方案获得多维度优化加速

近日&#xff0c;灵雀云联合英特尔推出了集成Smart Edge 模块的灵雀云 ACP 5G 专网解决方案&#xff0c;同时共同发布了《借力英特尔 Smart Edge&#xff0c;基于云原生解决方案的灵雀云 ACP 5G 专网版本获得多维度优化加速》白皮书。 得益于云计算技术和 5G 网络的高速发展&am…

Win10 环境 安卓ollvm编译与配置 ndk代码混淆加密

确定你正在使用的ndk版本 查看build.gradle ndkVersion 21.4.7075529 确定你使用的ndk的ollvm版本 C:\Users\Administrator\AppData\Local\Android\Sdk\ndk\21.4.7075529\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-config.exe --version 9.0.9svn 确定了ollvm版本后…

动手学深度学习(第二版)学习笔记 第二章

官网&#xff1a;http://zh.d2l.ai/ 视频可以去b站找 记录的是个人觉得不太熟的知识 第二章 预备知识 代码地址&#xff1a;d2l-zh/pytorch/chapter_preliminaries 2.1 数据操作 2.1. 数据操作 — 动手学深度学习 2.0.0 documentation 如果只想知道张量中元素的总数&#…

GIT分支管理策略

git基本操作git操作的前提条件:本地windows安装git学习idea中的插件使用idea的git基本操作:远程仓库remote更新fetch:git fetch拉取pull: git pull上传push: git push合并merge: git merge 合并分支本地提交commit:git commit分支branch: git branch 查看分支或者 切换分支上述…