自学网站开发需要看什么书,平阴网站建设费用,wordpress建个人博客,华强北附近网站建设BIO、NIO、AIO 的区别是什么#xff1f;同/异步、阻/非阻塞的区别是什么#xff1f;文件读写最优雅的实现方式是什么#xff1f;NIO 如何实现多路复用功能#xff1f;带着以上这几个问题#xff0c;跟着芒果一起进入IO的世界吧。在开始之前#xff0c;我们先来思考一个问… BIO、NIO、AIO 的区别是什么同/异步、阻/非阻塞的区别是什么文件读写最优雅的实现方式是什么NIO 如何实现多路复用功能带着以上这几个问题跟着芒果一起进入IO的世界吧。在开始之前我们先来思考一个问题我们经常所说的“IO”的全称到底是什么可能很多人看到这个问题和我一样一脸懵逼IO的全称其实是Input/Output的缩写。一、IO 介绍我们通常所说的 BIO 是相对于 NIO 来说的BIO 也就是 Java 开始之初推出的 IO 操作模块BIO 是 BlockingIO 的缩写顾名思义就是阻塞 IO 的意思。1.1 BIO、NIO、AIO的区别BIO 就是传统的 java.io 包它是基于流模型实现的交互的方式是同步、阻塞方式也就是说在读入输入流或者输出流时在读写动作完成之前线程会一直阻塞在那里它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观缺点就是 IO 的效率和扩展性很低容易成为应用性能瓶颈。NIO 是 Java 1.4 引入的 java.nio 包提供了 Channel、Selector、Buffer 等新的抽象可以构建多路复用的、同步非阻塞 IO 程序同时提供了更接近操作系统底层高性能的数据操作方式。AIO 是 Java 1.7 之后引入的包是 NIO 的升级版本提供了异步非堵塞的 IO 操作方式所以人们叫它 AIO(Asynchronous IO)异步 IO 是基于事件和回调机制实现的也就是应用操作之后会直接返回不会堵塞在那里当后台处理完成操作系统会通知相应的线程进行后续的操作。1.2 全面认识 IO传统的 IO 大致可以分为4种类型InputStream、OutputStream 基于字节操作的 IOWriter、Reader 基于字符操作的 IOFile 基于磁盘操作的 IOSocket 基于网络操作的 IOjava.net 下提供的 Scoket 很多时候人们也把它归为 同步阻塞 IO ,因为网络通讯同样是 IO 行为。java.io 下的类和接口很多但大体都是 InputStream、OutputStream、Writer、Reader 的子集所有掌握这4个类和File的使用是用好 IO 的关键。1.3 IO 使用接下来看 InputStream、OutputStream、Writer、Reader 的继承关系图和使用示例。1.3.1 InputStream 使用继承关系图和类方法如下图InputStream 使用示例InputStream inputStream new FileInputStream(D:\\log.txt);byte[] bytes new byte[inputStream.available()];inputStream.read(bytes);String str new String(bytes, utf-8);System.out.println(str);inputStream.close();1.3.2 OutputStream 使用继承关系图和类方法如下图OutputStream 使用示例OutputStream outputStream new FileOutputStream(D:\\log.txt,true);// 参数二表示是否追加true追加outputStream.write(你好老王.getBytes(utf-8));outputStream.close();1.3.3 Writer 使用Writer 继承关系图和类方法如下图Writer 使用示例Writer writer new FileWriter(D:\\log.txt,true); // 参数二是否追加文件true追加writer.append(老王你好);writer.close();1.3.4 Reader 使用Reader 继承关系图和类方法如下图Reader 使用示例Reader reader new FileReader(filePath);BufferedReader bufferedReader new BufferedReader(reader);StringBuffer bf new StringBuffer();String str;while ((str bufferedReader.readLine()) ! null) { bf.append(str \n);}bufferedReader.close();reader.close();System.out.println(bf.toString());二、同步、异步、阻塞、非阻塞上面说了很多关于同步、异步、阻塞和非阻塞的概念接下来就具体聊一下它们4个的含义以及组合之后形成的性能分析。2.1 同步与异步同步就是一个任务的完成需要依赖另外一个任务时只有等待被依赖的任务完成后依赖的任务才能算完成这是一种可靠的任务序列。要么成功都成功失败都失败两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成只是通知被依赖的任务要完成什么工作依赖的任务也立即执行只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成依赖它的任务无法确定所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。2.2 阻塞与非阻塞阻塞与非阻塞主要是从 CPU 的消耗上来说的阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事等这个慢的操作完成时CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。2.3 同/异、阻/非堵塞 组合同/异、阻/非堵塞的组合有四种类型如下表# 三、优雅的文件读写Java 7 之前文件的读取是这样的// 添加文件FileWriter fileWriter new FileWriter(filePath, true);fileWriter.write(Content);fileWriter.close();// 读取文件FileReader fileReader new FileReader(filePath);BufferedReader bufferedReader new BufferedReader(fileReader);StringBuffer bf new StringBuffer();String str;while ((str bufferedReader.readLine()) ! null) { bf.append(str \n);}bufferedReader.close();fileReader.close();System.out.println(bf.toString());Java 7 引入了Files(java.nio包下)的大大简化了文件的读写如下// 写入文件(追加方式StandardOpenOption.APPEND)Files.write(Paths.get(filePath), Content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);// 读取文件byte[] data Files.readAllBytes(Paths.get(filePath));System.out.println(new String(data, StandardCharsets.UTF_8));读写文件都是一行代码搞定没错这就是最优雅的文件操作。Files 下还有很多有用的方法比如创建多层文件夹写法上也简单了// 创建多(单)层目录(如果不存在创建存在不会报错)new File(D://a//b).mkdirs();四、Socket 和 NIO 的多路复用本节带你实现最基础的 Socket 的同时同时会实现 NIO 多路复用还有 AIO 中 Socket 的实现。4.1 传统的 Socket 实现接下来我们将会实现一个简单的 Socket服务器端只发给客户端信息再由客户端打印出来的例子代码如下int port 4343; //端口号// Socket 服务器端(简单的发送信息)Thread sThread new Thread(new Runnable() { Override public void run() { try { ServerSocket serverSocket new ServerSocket(port); while (true) { // 等待连接 Socket socket serverSocket.accept(); Thread sHandlerThread new Thread(new Runnable() { Override public void run() { try (PrintWriter printWriter new PrintWriter(socket.getOutputStream())) { printWriter.println(hello world); printWriter.flush(); } catch (IOException e) { e.printStackTrace(); } } }); sHandlerThread.start(); } } catch (IOException e) { e.printStackTrace(); } }});sThread.start();// Socket 客户端(接收信息并打印)try (Socket cSocket new Socket(InetAddress.getLocalHost(), port)) { BufferedReader bufferedReader new BufferedReader(new InputStreamReader(cSocket.getInputStream())); bufferedReader.lines().forEach(s - System.out.println(客户端 s));} catch (UnknownHostException e) { e.printStackTrace();} catch (IOException e) { e.printStackTrace();}调用 accept 方法阻塞等待客户端连接利用 Socket 模拟了一个简单的客户端只进行连接、读取和打印在 Java 中线程的实现是比较重量级的所以线程的启动或者销毁是很消耗服务器的资源的即使使用线程池来实现使用上述传统的 Socket 方式当连接数极具上升也会带来性能瓶颈原因是线程的上线文切换开销会在高并发的时候体现的很明显并且以上操作方式还是同步阻塞式的编程性能问题在高并发的时候就会体现的尤为明显。以上的流程如下图4.2 NIO 多路复用介于以上高并发的问题NIO 的多路复用功能就显得意义非凡了。NIO 是利用了单线程轮询事件的机制通过高效地定位就绪的 Channel来决定做什么仅仅 select 阶段是阻塞的可以有效避免大量客户端连接时频繁线程切换带来的问题应用的扩展能力有了非常大的提高。// NIO 多路复用ThreadPoolExecutor threadPool new ThreadPoolExecutor(4, 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue());threadPool.execute(new Runnable() { Override public void run() { try (Selector selector Selector.open(); ServerSocketChannel serverSocketChannel ServerSocketChannel.open();) { serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), port)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); // 阻塞等待就绪的Channel Set selectionKeys selector.selectedKeys(); Iterator iterator selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key iterator.next(); try (SocketChannel channel ((ServerSocketChannel) key.channel()).accept()) { channel.write(Charset.defaultCharset().encode(你好世界)); } iterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } }});// Socket 客户端(接收信息并打印)try (Socket cSocket new Socket(InetAddress.getLocalHost(), port)) { BufferedReader bufferedReader new BufferedReader(new InputStreamReader(cSocket.getInputStream())); bufferedReader.lines().forEach(s - System.out.println(NIO 客户端 s));} catch (IOException e) { e.printStackTrace();}首先通过 Selector.open() 创建一个 Selector作为类似调度员的角色然后创建一个 ServerSocketChannel并且向 Selector 注册通过指定 SelectionKey.OP_ACCEPT告诉调度员它关注的是新的连接请求为什么我们要明确配置非阻塞模式呢这是因为阻塞模式下注册操作是不允许的会抛出 IllegalBlockingModeException 异常Selector 阻塞在 select 操作当有 Channel 发生接入请求就会被唤醒下面的图可以有效的说明 NIO 复用的流程就这样 NIO 的多路复用就大大提升了服务器端响应高并发的能力。4.3 AIO 版 Socket 实现Java 1.7 提供了 AIO 实现的 Socket 是这样的如下代码// AIO线程复用版Thread sThread new Thread(new Runnable() { Override public void run() { AsynchronousChannelGroup group null; try { group AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4)); AsynchronousServerSocketChannel server AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(InetAddress.getLocalHost(), port)); server.accept(null, new CompletionHandler() { Override public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) { server.accept(null, this); // 接收下一个请求 try { Future f result.write(Charset.defaultCharset().encode(你好世界)); f.get(); System.out.println(服务端发送时间 new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date())); result.close(); } catch (InterruptedException | ExecutionException | IOException e) { e.printStackTrace(); } } Override public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) { } }); group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } catch (IOException | InterruptedException e) { e.printStackTrace(); } }});sThread.start();// Socket 客户端AsynchronousSocketChannel client AsynchronousSocketChannel.open();Future future client.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));future.get();ByteBuffer buffer ByteBuffer.allocate(100);client.read(buffer, null, new CompletionHandler() { Override public void completed(Integer result, Void attachment) { System.out.println(客户端打印 new String(buffer.array())); } Override public void failed(Throwable exc, Void attachment) { exc.printStackTrace(); try { client.close(); } catch (IOException e) { e.printStackTrace(); } }});Thread.sleep(10 * 1000);五、总结以上基本就是 IO 从 1.0 到目前版本(本文的版本)JDK 8 的核心使用操作了可以看出来 IO 作为比较常用的基础功能发展变化的改动也很大而且使用起来也越来越简单了IO 的操作也是比较好理解的一个输入一个输出掌握好了输入输出也就掌握好了 IOSocket 作为网络交互的集成功能显然 NIO 的多路复用给 Socket 带来了更多的活力和选择用户可以根据自己的实际场景选择相应的代码策略。