# Java网络编程中的NIO
# Java NIO概述
Java NIO(New IO或Non-Blocking IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
# 阻塞IO
通常在进行同步IO操作时,如果读取数据,代码或阻塞直至有可读的数据。同样,当写入数据时也会阻塞至有数据可写。传统的Server/Client模式会基于TPR(Thread per Request),服务器为每一个请求建立一个线程,于是当请求很多时,会造成线程数量的剧增,为了避免这个问题,都采用了线程池模型,但是当线程池中所有的线程都在进行大文件操作时,再来一个新的请求,会导致无法处理。
# 非阻塞IO
NIO中非阻塞IO采用了基于Reactor模式的工作方法,IO调用不会被阻塞,相反是注册感兴趣的特定IO事件,如可读数据到达,新的套接字连接等等,在发生特定事件时,系统再通知我们。NIO中实现非阻塞IO的核心对象是Selector,Selector就是注册各种IO事件的地方,而且当我们感兴趣的事件发生时,就是对这个对象告诉我们所发生的事。
从图中可以看出,当有读写请求或其他注册事件发生时,可以从Selector中获得相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的的具体的SelectableChannel,以获得客户端发送过来的数据。
非阻塞指的是IO事件本身不阻塞,但是获取IO事件的select()方法是需要阻塞等待的。区别是阻塞的IO会阻塞在IO操作上,而NIO阻塞在事件获取上,没有事件就没有IO,从高层次看IO就不阻塞了。也就是说只有IO已经发生那么我们才评估IO是否阻塞,但是select()阻塞的时候IO还没有发生,何谈IO的阻塞呢?NIO的本质是延迟IO操作到真正发生IO的时候,而不是以前的只要IO流打开了就一直等待IO操作。
# NIO 概述
Java NIO由几个核心部分组成:
- Channels
- Buffers
- Selectors
除此之外还有很多类和组件,但Channel、Buffer和Selector构成了核心的API
# Channel
Channel是双向的,而IO中的Stream是单向的。譬如:InputStream、OutputStream只能单向读或写,而Channel主要实现有FileChannel、DatagramChannel、SocketChannel,可以实现对文件、UDP、TCP的处理。
# Buffer
NIO中关键的Buffer实现有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,分别对应几种基本数据类型。
# Selector
Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。例如在一个聊天服务器中,要是用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到有事件发生。一旦这个方法返回,线程就可以处理这些事件。
# Java NIO(Channel)
# Channel概述
Channel是一个通道,可以通过它进行读取或写入数据。NIO中通过channel封装了对数据源的操作,而不必关系数据源具体的物理结构。在大多数应用中,channel与文件描述符或者socket是一一对应的。channel通常是搭配buffer来传输数据的。
Channel是一个对象,可以通过它读取和写入数据,但是不会将字节直接写入通道中,所有的数据通过buffer对象来处理。读取数据的时候,先将数据读入到缓冲区,再从缓冲区获取数据;写入数据时也是将数据先写入到缓冲区。
# FileChannel介绍与示例
FileChannel类可以实现常用的read、write以及scatter/gather操作,同时它也提供了很多专用于文件的新方法。
方法 | 描述 |
---|---|
int read(ByteBuffer dst) | 从FileChannel读取数据到Buffer |
从FileChannel读数据到Buffer
public class ChannelTest {
//通过FileChannel读取数据到buffer中
public static void main(String[] args) throws Exception {
//创建FileChannel
RandomAccessFile aFile = new RandomAccessFile("E:\\ideaproject\\01.txt","rw");
FileChannel channel = aFile.getChannel();
//创建Buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//读取数据到buffer中
int bytesRead = channel.read(buf);
while(bytesRead!=-1){
System.out.println("读取了"+bytesRead);
buf.flip();
while(buf.hasRemaining()){
System.out.println((char)buf.get());
}
buf.clear();
bytesRead = channel.read(buf);
}
aFile.close();
System.out.println("结束了");
}
}
从Buffer写数据到FileChannel
public class ChannelTest1 {
//FileChannel写操作
public static void main(String[] args) throws Exception {
//打开一个FileChannel
RandomAccessFile aFile = new RandomAccessFile("E:\\ideaproject\\01.txt","rw");
FileChannel channel = aFile.getChannel();
//创建一个buffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
String newDate = "data somnus";
buffer.clear();
//写入内容
buffer.put(newDate.getBytes());
buffer.flip();
//FileChannel完成最终实现
while(buffer.hasRemaining()){
channel.write(buffer);
}
//关闭
channel.close();
}
}
通道间进行数据传输
//通道之间进行数据传输
public class ChannelTest2 {
public static void main(String[] args) throws Exception {
//创建两个FileChannel
RandomAccessFile aFile = new RandomAccessFile("E:\\ideaproject\\01.txt","rw");
FileChannel fromChannel = aFile.getChannel();
RandomAccessFile bFile = new RandomAccessFile("E:\\ideaproject\\02.txt","rw");
FileChannel toChannel = bFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel,position,count);
//关闭通道
aFile.close();
bFile.close();
}
}
← 多线程