在上篇《Java IO(2)阻塞式输入输出(BIO)》的末尾谈到了什么是阻塞式输入输出,通过Socket编程对其有了大致了解。现在再重新回顾梳理一下,对于只有一个“客户端”和一个“服务器端”来讲,服务器端需要阻塞式接收客户端的请求,这里的阻塞式表示服务器端的应用代码会被挂起直到客户端有请求过来,在高并发的应用场景有多个客户端发起连接下非阻塞式IO(NIO)是不二之选(且只需要在服务器端使用1个线程来管理,并不需要多个线程来处理多个连接)。在现实情况下,Tomcat、Jetty等很多Web服务器均使用了NIO技术。

  接下来对于非阻塞式输入输出(NIO)的学习以及理解首先从它的三个基础概念讲起。

  在NIO中,你需要忘掉“流”这个概念,取而代之的是“通道”。举例在网络应用程序中有多个客户端连接,此时数据传输的概念并不是“流”而“通道”,通道与流最大的不同就是,通道是双向的,而流是单向的(例如InputStream、OutputStream)。

Java IO(3)非阻塞式输入输出(NIO)

Buffer(缓冲区)

  在NIO中并不是简单的将流的概念替换为了通道,与通道搭配的是缓冲区。在BIO的字节流中并不会使用到缓冲区,而是直接操作文件通过字节方式直接读取,而NIO则不同,它会将通道中的数据读入缓存区,或者将缓存区的数据写入通道。

Java IO(3)非阻塞式输入输出(NIO)

Selector(选择器)

  如果使用NIO的应用程序中只有一个Channel,选择器则是可以不需要的,而如果有多个Channel,换言之有多个连接时,此时通过选择器,在服务器端的应用程序中就只需要1个线程对多个连接进行管理。

Java IO(3)非阻塞式输入输出(NIO)

  当然从最开始就说到Channel是双向的,所以在最终图的示例为下图所示:

Java IO(3)非阻塞式输入输出(NIO)

  下面再重新回到这三个概念,详细解释它们是如何协同工作的。

Channel & Buffer

  通常情况下Channel会和Buffer配合使用,但可以不使用Channel。首先需要明确的是,应用程序不管是从文件(包括网络或者其他什么地方)中读取数据,还是写入数据到文件(包括网络或者其他什么地方)都需要Buffer。

1. 直接将数据写入Buffer,应用程序从Buffer中获取数据

1 ByteBuffer buffer = ByteBuffer.allocate(1024);
2 byte b = 121;
3 buffer.put(b);
4 buffer.flip();    //读写转换,由“写模式”转换为“读模式”
5 System.out.println((char)buffer.get());    

  第1行,分配一个1KB大小的Buffer缓冲区,ByteBuffer.allcoate返回HeapByteBuffer实例。

  第3行,向Buffer中写入一个字节。

  第4行,Buffer由“写模式”转换为“读模式”。

  第5行,ByteBuffer.get方法读取Buffer中的数据,并且position索引+1。 在上面的代码中有一个重点——flip方法,这个方法的存在是由于Buffer兼顾了读和写的操作,在ByteBuffer的实现中有三个重要的成员变量需要注意: capacity——Buffer容量 position——索引位置 limit——读时表示最大容量,即limit = capacity;写时表示最后一个数据所在的索引位置。 用图例来说明上面代码的执行过程。

Java IO(3)非阻塞式输入输出(NIO)

Java IO(3)非阻塞式输入输出(NIO)

Java IO(3)非阻塞式输入输出(NIO)

  从上图可以清晰的看到Buffer内部是如何进行读写操作的,其中调用flip方法是很关键且重要的一个步骤,试想如果不调用flip进行读写转换,此时position、limit、capacity的索引位置将会如下图所示。

Java IO(3)非阻塞式输入输出(NIO)

  此时进行读的操作将会得到一个错误数据(0)。 尽管在讲这个小标题“直接将数据写入Buffer,应用程序从Buffer中获取数据”,但实际上已经简要介绍了Buffer的内部实现原理。

  通过上面的例子可以看到,Channel和Buffer并不一定要在一起,单独使用Buffer也是可以的,但要使用Chnnel那就必须得配合Buffer。

2. 从文件中读取数据写入Buffer,应用程序从Buffer中获取数据

  此时的数据来源是文件,开头提过在NIO中忘掉“流”,记住“通道”。在NIO中可以通过传统的流获取通道。例如从输入流FileInputSteram中调用getChannel,或者从输出流FileOutputStream中调用getChannel,当然还有兼顾输入和输出的RandomAccessFile类从中调用getChannel。

  BIO中首先获取流,NIO中首先获取通道。

1 RandomAccessFile file = new RandomAccessFile("/Users/yulinfeng/Documents/Coding/Idea/simplenio/src/main/java/com/demo/test.json", "rw");
2 FileChannel channel = file.getChannel();
3 ByteBuffer buffer = ByteBuffer.allocate(1024);
4 channel.read(buffer);
5 buffer.flip();
6 System.out.println(new String(buffer.array()));

  看到这段NIO读取文件数据的代码,心中默写传统的BIO是如何读取文件数据的。

1 InputStream in = new FileInputStream("/Users/yulinfeng/Documents/Coding/Idea/simplenio/src/main/java/com/demo/test.json");
2 byte[] bytes = new byte[1024];
3 in.read(bytes);
4 System.out.println(new String(bytes));
View Code

相关文章:

  • 2021-05-15
  • 2021-05-08
  • 2021-12-30
  • 2022-12-23
  • 2021-08-17
  • 2021-07-21
  • 2021-07-05
猜你喜欢
  • 2021-07-01
  • 2021-12-16
  • 2021-11-14
  • 2021-05-28
  • 2021-06-21
  • 2021-08-27
相关资源
相似解决方案