概述
一、NIO简介
- NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
- NIO基于 Channel 和 Buffer 进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中
- Selector用于监听多个通道的事件(比如:连接打开,数据到达),单个线程可以监听多个数据通道
二、NIO VS 传统IO
- IO是面向流的,NIO是面向缓冲区的:NIO中的缓冲区的存在使我们可以在其中对数据进行操作,增加了灵活性
- IO的各种流是阻塞的,NIO是非阻塞模式:Selector用于监听多个通道的事件,从而实现一个线程管理多个输入和输出通道
三、Channel & Buffer & Selector
1、channel
- 传统IO中的stream是单向的,而 NIO 中的channel是双向的,既可读又可写;
- NIO中的Channel的主要实现有:这几个实现分别对应 IO、UDP、TCP
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
2、buffer
- NIO中的关键Buffer实现有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应几种基本的数据类型
- NIO中还有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等
3、selector
- 要使用Selector, 得向Selector注册Channel,然后调用select()方法,这个方法会一直阻塞到某个注册的通道有事件就绪
- 事件就绪后这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收等
FileChannel
一、NIO的实例
public static void method1(){
RandomAccessFile aFile = null;
try{
aFile = new RandomAccessFile("d:\\123.txt","rw");
FileChannel fileChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
int bytesRead = fileChannel.read(buf);
System.out.println(bytesRead);
while(bytesRead != -1)
{
buf.flip();
while(buf.hasRemaining())
{
System.out.print((char)buf.get());
}
buf.compact();
bytesRead = fileChannel.read(buf);
}
}catch (IOException e){
e.printStackTrace();
}finally{
try{
if(aFile != null){
aFile.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
二、buffer 使用
1、基本原理
- buffer 的本质是一个容器,是一个连续的数组;
- NIO 数据传递的基本过程如图:
- 向Buffer中写数据:
从Channel写到Buffer (fileChannel.read(buf))
通过Buffer的put()方法 (buf.put(…))
- 从Buffer中读数据:
从Buffer读取到Channel (channel.write(buf))
使用get()方法从Buffer中读取数据 (buf.get())
2、详细使用步骤描述
buffer的几个参数
- capacity:指定了可以存储在缓冲区中的最大数据容量
- position:指的是下一个要被读写的元素的数组下标索引,该值会随get()和put()的调用自动更新
- limit:指的是缓冲区中第一个不能读写的元素的数组下标索引,也可以认为是缓冲区中实际元素的数量
- mark:一个备忘位置,调用mark()方法的话,mark值将存储当前position的值,等下次调用reset()方法时,会设定position的值为之前的标记值
- 四个参数之间的大小关系为:0 <= mark <= position <= limit <= capacity
buffer的实际使用过程
- 创建一个容量大小为10的字符缓冲区:
ByteBuffer bf = ByteBuffer.allocate(10);
- 往缓冲区中put()五个字节:
bf.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'0');
- 调用flip()方法,切换为读就绪状态:
bf.flip()
- 读取两个元素:
System.out.println("" + (char) bf.get() + (char) bf.get());
- 标记此时的position位置:
bf.mark()
- 读取两个元素后,恢复到之前mark的位置处:
System.out.println("" + (char) bf.get() + (char) bf.get());
bf.reset();
- 调用compact()方法,释放已读数据的空间,准备重新填充缓存区:bf.compact() ; 这里要是调用 clear 方法的话,position将被设回0,limit设置成capacity
SocketChannel
一、概述
- NIO的channel抽象的一个重要特征就是可以通过配置它的阻塞行为,以实现非阻塞式的信道
channel.configureBlocking(false)
二、TCP示例
1、client 使用 NIO
public static void client(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = null;
try
{
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
if(socketChannel.finishConnect())
{
int i=0;
while(true)
{
TimeUnit.SECONDS.sleep(1);
String info = "I'm "+i+++"-th information from client";
buffer.clear();
buffer.put(info.getBytes());
buffer.flip();
while(buffer.hasRemaining()){
System.out.println(buffer);
socketChannel.write(buffer);
}
}
}
}
catch (IOException | InterruptedException e)
{
e.printStackTrace();
}
finally{
try{
if(socketChannel!=null){
socketChannel.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
2、server 使用 BIO
public static void server(){
ServerSocket serverSocket = null;
InputStream in = null;
try
{
serverSocket = new ServerSocket(8080);
int recvMsgSize = 0;
byte[] recvBuf = new byte[1024];
while(true){
Socket clntSocket = serverSocket.accept();
SocketAddress clientAddress = clntSocket.getRemoteSocketAddress()