一、NIO简介
NIO(Non-blocking IO)即非阻塞IO,在JDK1.4中引入,提供面向块的非阻塞IO操作
1.NIO家族成员
a).Buffer
NIO是面向块的,不是一个一个的传递数据,自然要有一个东西来打包数据。Buffer缓冲区就是来打包数据用的,它是NIO数据处理的基础单元。
NIO具体有八种Buffer即ByteBuffer,CharBuffer,ShortBuffer,FloatBuffer…对应了java的基本数据类型(除了Boolean类型)
Buffer的几个重要属性
b).Channel
类似于流,但数据在Channel中的流动是双向的
Channel具体实现有FileChannel,SocketChannel,DatagramChannel等
c).Selectors
处理事情的分发器。Selector能够检测到多个注册的通道上是否由事件放生,如果有事件发生,便获取事情然后针对每个事情进行相应的处理。这样就可以只用一个单线程去管理多个通道
我们把Channel注册到Selector上,指定监听的事件。
Selctor使用SelectionKey这个类去一对一绑定Channel,每一个Selector内部有一个Set集合来维护所有的SelectionKey。
Selector提供select()以及其他重载方法,来轮询所有的SelectionKey,观察他们对应的Channel是否有事件发生,返回有事件发生的SelectionKey个数。
Selector提供selectedKeys()方法来返回当前有事件发生的SelectorKey的集合,通过每一个selectionKey获取它发生的事件,然后获取它对应绑定的channel进行相应的业务逻辑处理
Selector的4个基本事件
二、Demo
1.Buffer
(1).示例1
这个例子创建了一个容量为5的IntBuffer
public class IntBufferTest {
public static void main(String[] args) {
IntBuffer intBuffer =IntBuffer.allocate(5);
for (int i = 0; i <intBuffer.capacity() ; i++) {
intBuffer.put(i+5);
}
//flip方法的作用是使limit=Position,且使Position指针重回数组的首位
intBuffer.flip();
while (intBuffer.hasRemaining())
{
//get方法得到一个数据后,并不会在从数组中删除它,而是使Position指针后移
System.out.println(intBuffer.get());
}
}
}
(2)实例2
Buffer的两个特性
Scattering: 将数据写入到buffer时,可以采用Buffer数组,依次写入
Gathering:从buffer读数据,可以采用Buffer数组,依次读
public static void main(String[] args) throws IOException {
//开启一个serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(7000));
//开辟一个ByteBuffer数组并赋值
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] =ByteBuffer.allocate(5);
byteBuffers[1] =ByteBuffer.allocate(3);
SocketChannel accept = serverSocketChannel.accept();
while (true)
{
accept.read(byteBuffers);
}
}
补充
Buffer的filp方法
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
2.Channel
输出流程为
1.创建一个输出流
2.从输出流获得管道Channel
3.创建一个Buffer
4.将数据写入Buffer
5.将Buffer写入Channel
public static void write(String filename) throws IOException {
String str = "hello,fileChannel";
//创建一个输出流
FileOutputStream fileOutputStream = new FileOutputStream(filename);
//通过输出流获取对应的channel
FileChannel fileChannel = fileOutputStream.getChannel();
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将数据放入到缓冲区
byteBuffer.put(str.getBytes());
//对buffer进行反转,position指针放置数组开头
byteBuffer.flip();
//将buffer写入channel
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
输入
public static void read (String filename) throws IOException {
FileInputStream fileInputStream = new FileInputStream(filename);
FileChannel fileChannel = fileInputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//从通道读取数据放入缓冲区
fileChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
将一个文件拷贝到另一个文件,这里考虑到文件的大小大于Buffer需要多次读取的情况
FileInputStream fileInputStream = new FileInputStream("file01.txt");
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("file02.txt");
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(4);
while (true)
{
//重要!
//循环读时,一次性读不完,再读要清空buffer
byteBuffer.clear();
int read = fileInputStreamChannel.read(byteBuffer);
if(read ==-1) break;
byteBuffer.flip();
fileOutputStreamChannel.write(byteBuffer);
}
fileInputStream.close();
fileOutputStream.close();
使用Channel的transferFrom直接将另一个管道的数据转移过来
public static void transform() throws IOException {
FileInputStream fileInputStream = new FileInputStream("file01.txt");
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("file02.txt");
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
fileOutputStreamChannel.transferFrom(fileInputStreamChannel,0,fileInputStreamChannel.size());
}
3.综合示例
public class NIOServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//得到一个Selector
Selector selector = Selector.open();
//绑定一个端口
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//把channel注册到selector,监听的事情为OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true)
{
if(selector.select(1000)==0)
{
System.out.println("服务器等待了一秒,无连接");
continue;
}
//如果selector.select返回大于0,就获取到相关的selectionkey集合
//1.如果返回的>0 表示已经h获取到关注的事件
//2.selector.selectedKeys()返回关注事件的集合
//通过selectionkeys反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext())
{
//获得SelectionKey
SelectionKey selectionKey = keyIterator.next();
//获取key对应通道发生的事件
if(selectionKey.isAcceptable())
{
//事件是连接事件,那我们调用accept方法的时候就会立刻获取一个连接
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
//把这个channel注册到selector,关注事情为OP_READ,同时给这个channel关联一个BUFF
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(selectionKey.isReadable())
{
//通过key反向获得Channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//获得和这个Channel关联的buffer
ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
socketChannel.read(byteBuffer);
System.out.println("客户端发来的"+new String(byteBuffer.array()));
}
//手动从集合中移动,避免重复处理
keyIterator.remove();
}
}
}
}
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
//提供服务器端IP和端口
if(!socketChannel.connect(new InetSocketAddress("127.0.0.1",6666)))
{
while (!socketChannel.finishConnect())
{
System.out.println("没连接上,也不会阻塞");
}
}
String str = "hello server";
//获得和数据大小相同的buffer
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
socketChannel.write(byteBuffer);
System.in.read();
}
}
三、补充
1.NIO和零拷贝
主要应用就是FileChannel的transferTo方法
很多操作系统可以直接将文件缓存传输到channel,不经过CPU的拷贝
//Linux环境下可以把文件一次性的传输到Channel
//Windows环境下一次只可以传输8M
//var1表示文件开始的字节,var3表示要传输的字节数,var5表示目标channel
public long transferTo(long var1, long var3, WritableByteChannel var5) throws IOException