1、流与块快有半个月没继续看这东西了,今天也没啥事情干,就继续记录下学习的NIO这个N代表的是"new"的意思,据我百度所致,NIO是在jdk 1.4的版本中引入的,目的是为了弥补原来的I/O的不足之处,提供了一个更高速的、面向块的I/O。
standard IO是对流的读写,以流的形式处理数据,每次进行IO操作的时候都要创建一个流对象(如InputStream、OutputStream),流对象进行IO操作都是按字节进行操作,一个一个字节的进行读写操作,因此操作效率比较慢。
而NIO则是将IO抽象成块,以块的形式处理数据,类似硬盘的读写,每次IO操作的单位都是一个块,块存入内存后便是一个byte[]数组,一次可以读写多个字节,因此按块处理数据比按流处理数据要快得多。
jdk1.4的版本中在java.io.*包中已经以NIO的基础重新实现了一遍IO类,所以它可以利用NIO的一些特性。
通道与缓冲区 1、通道通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
通道包括以下类型:
FileChannel: 从文件中读写数据;DatagramChannel: 通过 UDP 读写网络中数据;SocketChannel: 通过 TCP 读写网络中数据;ServerSocketChannel: 可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 2、缓冲区
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
缓冲区包括以下类型:
ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer 缓冲区状态变量
capacity: 最大容量;position: 当前已经读写的字节数;limit: 还可以读写的字节数。
状态变量的改变过程举例:
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 移动设置为 5,limit 保持不变。
③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
NIO常常被叫做非阻塞IO,主要是因为NIO在网络通信中的非阻塞特性被广泛使用。
NIO实现了IO多路复用中的Reactor模型(这个还不太了解),一个线程Thread使用一个选择器Selector通过轮询的方式去监听多个Channel上的事件,从而让一个线程就可以处理多个事件
通过配置监听的通道Channel为非阻塞,那么当Channel上的IP事件还未到达时,就不会进入阻塞状态一直等待,二十继续轮询其他Channel,找到IP事件已经到达的Channel执行。
因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件具有更好的性能
通道事件
将通道注册到选择器上,选择如下具体事件,可以通过"|"将事件组成事件集如:int interestSet = Selection.OP_READ | Selection.OP_WRITEpublic static final int OP_READ = 1 << 0;public static final int OP_WRITE = 1 << 2;public static final int OP_ConNECT = 1 << 3;public static final int OP_ACCEPT = 1 << 4;
demoNioServer.java
package NioDemo.SocketNioDemo;import java.io.IOException;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;public class NioServer { public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 将通道设置为非阻塞模式 serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); ServerSocket serverSocket = serverSocketChannel.socket(); InetSocketAddress address = new InetSocketAddress("localhost", 80); serverSocket.bind(address); while (true) { // 监听事件,会一直阻塞到监听事件的到达 selector.select(); Set
NioClient.java
package NioDemo.SocketNioDemo;import java.io.IOException;import java.io.OutputStream;import java.net.Socket;public class NioClient { public static void main(String[] args) throws IOException { for (int i = 0; i < 10; i++) { // 建立socket连接就是一个accept事件的到达 Socket socket = new Socket("localhost", 80); OutputStream outputStream = socket.getOutputStream(); String data = "hello, world" + i; // OutputStream进行一个写的操作,对于服务器来说就是一个Read事件的到达 outputStream.write(data.getBytes()); outputStream.close(); } }}
建议大家用代码去debug看看背后的运行流程
模拟一个并发,服务端代码一样唯一变化的就是这个客户端的请求代码不一样,大家可以自己写着玩玩
守护线程
package NioDemo.SocketNioDemo;import java.util.concurrent.CountDownLatch;public class NioClientDaemon { public static void main(String[] args) throws InterruptedException { Integer clientNumber = 3; CountDownLatch countDownLatch = new CountDownLatch(clientNumber); for (int i = 0; i < clientNumber; i++, countDownLatch.countDown()) { NioRequestThread nioRequestThread = new NioRequestThread(countDownLatch, i); new Thread(nioRequestThread).start(); } synchronized (NioClientDaemon.class) { NioClientDaemon.class.wait(); } }}
请求线程
package NioDemo.SocketNioDemo;import java.io.IOException;import java.io.OutputStream;import java.net.Socket;import java.nio.charset.StandardCharsets;import java.util.concurrent.CountDownLatch;public class NioRequestThread implements Runnable{ private CountDownLatch countDownLatch; private Integer clientIndex; public NioRequestThread() { } public NioRequestThread(CountDownLatch countDownLatch, Integer clientIndex) { this.countDownLatch = countDownLatch; this.clientIndex = clientIndex; } @Override public void run() { Socket socket = null; OutputStream outputStream = null; try { socket = new Socket("localhost", 80); outputStream = socket.getOutputStream(); this.countDownLatch.await(); outputStream.write(("这是 " + clientIndex + "号线程发送的消息").getBytes(StandardCharsets.UTF_8)); outputStream.flush(); } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { try { if (outputStream != null) { outputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } }}
参考
http://tutorials.jenkov.com/java-nio/overview.html
https://www.pdai.tech/md/java/io/java-io-nio.html