NIO学习笔记
NIO区别于IO,是同步非阻塞的。nio直接使用native函数库,直接分配对外内存,通过DirectByteBuffer对象作为这块内存的引用操作,避免了数据在java堆和对外内存间来回复制。
概念区分:
1.同步:使用同步IO时,Java自己处理IO读写。
2.异步:使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS,完成后OS通知Java处理(回调)。
3.阻塞:使用阻塞IO时,Java调用会一直阻塞到读写完成才返回。
4.非阻塞:使用非阻塞IO时,如果不能立马读写,Java调用会马上返回,当IO事件分发器通知可读写时在进行读写,不断循环直到读写完成。
BIO:同步且阻塞 NIO:同步非阻塞 AIO:异步非阻塞
应用场景:并发连接数不多时采用BIO,因为它编程和调试都非常简单,但如果涉及到高并发的情况,应选择NIO或AIO,更好的建议是采用成熟的网络通信框架Netty。
NIO主要组件:
Channel 通道,文件数据首先存在于channel中 可通过FileInputStream.getChannel()获取到channel
Buffer 缓冲区,数据可以从通道读入缓冲区,也可以将数据从缓冲区写到通道中
Selector 用于管理一个或多个通道
nio读取文件示例
1 public class TestChannel { 2 public static void main(String[] args) throws IOException { 3 //读取到文件 4 RandomAccessFile f = new RandomAccessFile("C:\\test-nio.txt", "rw"); 5 //获取channel 6 FileChannel channel = f.getChannel(); 7 //分配缓存大小 8 ByteBuffer buffer = ByteBuffer.allocate(1024); 9 //从channel中读取数据循环读取到缓存中 10 int bytesRead = channel.read(buffer); 11 while (bytesRead != -1){ 12 System.out.println("before flip:"+buffer); 13 //切换为读模式,反转pos为0,使下一步get从0开始 14 buffer.flip(); 15 System.out.println("after flip:"+buffer); 16 while (buffer.hasRemaining()){ 17 System.out.print((char)buffer.get()); 18 } 19 //上一步打印完成后清除缓存内数据,使缓冲区可以再次被写入 20 buffer.clear(); 21 System.out.println("\nafter clear:"+buffer); 22 23 bytesRead = channel.read(buffer); 24 } 25 //关闭通道 26 channel.close();29 } 30 }
将字符串持久化示例
public class TestWriteChannel { public static void main(String[] args) throws IOException { RandomAccessFile file = new RandomAccessFile("c:/wriate-test.txt","rw"); FileChannel fileChannel = file.getChannel(); String data = "我wjeabcdefghijklmnopqrstuvwxyz"+System.currentTimeMillis(); System.out.println(data.getBytes().length); //注意buffer分配的内存空间要大于data.getBytes().length,否者报BufferOverflowException异常 ByteBuffer buffer = ByteBuffer.allocate(88); buffer.clear(); buffer.put(data.getBytes()); //切换为读模式 pos=0 buffer.flip(); while (buffer.hasRemaining()){ fileChannel.write(buffer); } fileChannel.close(); } }
几个方法说明:
buffer
flip() 将buffer从写模式切换为读模式,是pos=0 limit=“写模式时最后的pos“。limit表示此缓冲区中最多可以读取的字节数
clear() 清空缓冲区,使pos=0 limit=capaticy
compact() 将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。注意和clear区分,clear不处理未读数据,直接清空
rewind() 将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少
mark()/reset() 通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position
put() 将字节存放到buffer中
get() 从buffer中读取字节
channel
size() channel关联的文件的大小
position() 获取当前的位置,加上参数则是设置位置
truncate() 截取文件,如truncate(1024)截取文件的的前1024字节
force() 将channel中的数据强制写到磁盘上
scatter和gather:分散和聚合
scatter:将channel中的数据读取到多个buffer中,按byffer数组的顺序依次填充buffer
gather:将多个buffer中的数据写到同一个channel中
1 public class TestScatterGather { 2 public static void main(String[] args) throws IOException { 3 RandomAccessFile raf = new RandomAccessFile("c:/test-nio.txt", "rw"); 4 FileChannel channel = raf.getChannel(); 5 6 ByteBuffer header = ByteBuffer.allocate(10); 7 ByteBuffer body = ByteBuffer.allocate(200); 8 9 ByteBuffer[] bufferArr = {header,body}; 10 long x = channel.read(bufferArr); 11 12 header.flip(); 13 while (header.hasRemaining()){ 14 System.out.print((char)header.get()); 15 } 16 header.clear(); 17 18 System.out.println("============"); 19 20 body.flip(); 21 while (body.hasRemaining()){ 22 System.out.print((char)body.get()); 23 } 24 body.clear(); 25 } 26 }
FileChannel的transferFrom/transferTo方法可用于复制文件,如下示例:
1 public class TestTransfer { 2 3 public static void main(String[] args) throws IOException { 4 Long s = System.currentTimeMillis(); 5 RandomAccessFile fromFile = new RandomAccessFile("C:\\迅雷下载\\linux-lite-3.6-32bit.iso", "rw"); 6 FileChannel fromChannel = fromFile.getChannel(); 7 8 RandomAccessFile toFile = new RandomAccessFile("C:\\迅雷下载\\linux-lite-3.6-32bit-bak.iso", "rw"); 9 FileChannel toChannel = toFile.getChannel(); 10 11 fromChannel.transferTo(0, fromChannel.size(), toChannel); 12 //或者 13 //toChannel.transferFrom(fromChannel, 0, fromChannel.size()); 14 15 fromChannel.close(); 16 toChannel.close(); 17 18 System.out.println("文件复制消耗时间:"+(System.currentTimeMillis()-s)); 19 } 20 }
Selector在socket的用法示例
public class TestSelector { public static void main(String[] args) throws IOException { int port = 9999; ServerSocketChannel channel = ServerSocketChannel.open(); channel.bind(new InetSocketAddress(port)); channel.configureBlocking(false); Selector selector = Selector.open(); channel.register(selector, SelectionKey.OP_ACCEPT); while (true){ System.out.println("开始监听..."); int selNum = selector.select(); System.out.println("selnum: "+selNum); if(selNum == 0){ continue; } Set<SelectionKey> keySet = selector.selectedKeys(); Iterator<SelectionKey> iterator = keySet.iterator(); while (iterator.hasNext()){ SelectionKey selectionKey = iterator.next(); if(selectionKey.isConnectable()){ System.out.println("connectable"); } if(selectionKey.isAcceptable()){ System.out.println("acceptable"); } if(selectionKey.isReadable()){ System.out.println("readable"); } if(selectionKey.isWritable()){ System.out.println("writable"); } } } } }