摘要:NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。
本文分享自华为云社区《java中的NIO和IO到底是什么区别?20个问题告诉你答案【奔跑吧!JAVA】》,原文作者:breakDraw 。
NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。
Q: NIO和标准IO有什么区别?
A:
Q: 通道Channel对象能同时做读写操作吗?
还是说需要像标准IO那样,需要同时创建input和output对象才能做读写操作?
A:通道Channel是双向的, 既可以从channel中读数据,也可以写数据。
可以看到既能调用read也能调用write,且需要依赖缓冲区buffer。
FileChannel fileChannel = FileChannel.open(new File("a.txt").toPath()); ByteBuffer buf = ByteBuffer.allocate(1024); fileChannel.read(buf); fileChannel.write(buf);

Q: 通道支持异步读写吗
A: 支持。
Q: 通道的读写是否必须要依赖缓冲区buffer?
A: 一般都是依赖buffer的。 但也支持2个管道之间的传输,即管道之间直接读写。
String[] arr=new String[]{"a.txt","b.txt"}; FileChannel in=new FileInputStream(arr[0]).getChannel(); FileChannel out =new FileOutputStream(arr[1]).getChannel(); // 将a.txt中的数据直接写进b.txt中,相当于文件拷贝 in.transferTo(0, in.size(), out);
常用的几种Channel
Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下
创建方式
RandomAccessFile file = new RandomAccessFile("D:/aa.txt"); FileChannel fileChannel = file.getChannel();
Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。支持非阻塞模式socketChannel.configureBlocking(false)。可以通过以下2种方式创建SocketChannel:
打开一个SocketChannel并连接到互联网上的某台服务器。一个新连接到达ServerSocketChannel时,会创建一个SocketChannel
创建方式
SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("192.168.1.100",80));
Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。SocketChannel和ServerSocketChannel的区别: 前者用于客户端,后者用于服务端
创建方式:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket.bind(new InetSocketAddress(80)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannle != null) doSomething... }
ByteBuffer buf = ByteBuffer.allocate(1024);
Q: buffer调用flip()方法从写模式切换到读模式时,position会变成多少?
A: 变为0。
ByteBuffer buf = ByteBuffer.allocate(1024); // 数据读到buf中,并返回数量,每次最多读1024个 int byteRead = fileChannel.read(buf); // 输出byteRead的数量,最多为1024 System.out.println("position=" + buf.position()+", byteRead=" + byteRead); buf.flip(); // 切换到读模式了,输出0 System.out.println("position=" + buf.position());
int byteRead = fileChannel.read(buf); // 输出1024 System.out.println("limit=" + buf.limit() + ",postion=" + buf.position()); System.out.println("切换到读模式"); buf.flip(); // 输出byteRead数量 System.out.println("limit=" + buf.limit());
结果如下

Q: 向buf缓冲区写数据的方式有哪些?
A:
Q: 从buf缓冲区读数据的方式有哪些?
Q: 手动修改当前缓冲区的postion的方法有哪些?
A:
Q:1个channel管道支持多个buffer吗?
A: 支持。 通道的write和read方法都支持传入1个buffer数组,会按照顺序做读写操作。
Buffer的种类:
Buffer的另外3个方法:
selector可用来在线程中关联多个通道,并进行事件监听。
Q: 在NIO中Selector的好处是什么?
A:
Q: Selector支持注册哪种类型的通道?
A:
支持非阻塞的通道。
通道要在注册前调用 channel.configureBlocking(false) 设置为非阻塞。
例如FileChannel就没办法注册,他注定是阻塞的。而socketChannel就可以支持非阻塞。
Q: Selector注册时,支持监听哪几种事件,对应的常量是什么?(啊最不喜欢记忆这种东西了…)
A:共有4种可监听事件
int interestingSet = Selectionkey.OP_READ | Selectionkey.OP_WRITE; Selectionkey key = channel.register(selector,interestingSet)
Q: Selector维护的SelectionKey集合共有哪几种?
A:共有三种。
(1)已注册的所有键的集合(Registered key set)
所有与选择器关联的通道所生成的键的集合称为已经注册的键的集合。并不是所有注册过的键都仍然有效。这个集合通过keys()方法返回,并且可能是空的。这个已注册的键的集合不是可以直接修改的;试图这么做的话将引发java.lang.UnsupportedOperationException。
(2)已选择的键的集合(Selected key set)
已注册的键的集合的子集。这个集合的每个成员都是相关的通道被选择器(在前一个选择操作中)判断为已经准备好的,并且包含于键的interest集合中的操作。这个集合通过selectedKeys()方法返回(并有可能是空的)。
不要将已选择的键的集合与ready集合弄混了。这是一个键的集合,每个键都关联一个已经准备好至少一种操作的通道。每个键都有一个内嵌的ready集合,指示了所关联的通道已经准备好的操作。键可以直接从这个集合中移除,但不能添加。试图向已选择的键的集合中添加元素将抛出java.lang.UnsupportedOperationException。
(3)已取消的键的集合(Cancelled key set)
已注册的键的集合的子集,这个集合包含了cancel()方法被调用过的键(这个键已经被无效化),但它们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。
注册之后, 如何使用selector对准备就绪的通道做处理:
while(true) { int readyNum = selector.select(); if (readyNum == 0) { continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectedKeys.iterator(); while(it.hasNext()) { SelectionKey key = it.next(); if(key.isAcceptable()) { // 接受连接 } else if (key.isReadable()) { // 通道可读 } else if (key.isWritable()) { // 通道可写 } it.remove(); } }
Q:select()方法其实是阻塞方法,即调用时会进入等待,直到把所有通道都轮询完毕。如果希望提前结束select(),有哪些方法?
A:有2个办法:
wakeup(), 调用后,select()方法立刻返回。
close(), 直接关闭selector。
PS: 之前说NIO是非阻塞IO,但为什么上面却说select()方法是阻塞的?
Q: 多线程读写同一文件时,如何加锁保证线程安全?
A:使用FileChannel的加锁功能。
RandomAccessFile randFile = new RandomAccessFile(target, "rw"); FileChannel channel = randFile.getChannel(); // pos和siz决定加锁区域, shared指定是否是共享锁 FileLock fileLock = channel.lock(pos , size , shared); if (fileLock!=null) { do(); // 这里简化了,实际上应该用try-catch fileLock.release(); }
Q: 如果需要读1个特大文件,可以使用什么缓冲区?
A:使用MappedByteBuffer。
这个缓冲区可以把大文件理解成1个byte数组来访问(但实际上并没有加载这么大的byte数组,实际内容放在内存+虚存中)。
主要通过FileChannel.map(模式,起始位置,区域)来生成1个MappedByteBuffer。然后可以用put和get去处理对应位置的byte。
int length = 0x8FFFFFF;//一个byte占1B,所以共向文件中存128M的数据 try (FileChannel channel = FileChannel.open(Paths.get("src/c.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE);) { MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length); for(int i=0;i<length;i++) { mapBuffer.put((byte)0); } for(int i = length/2;i<length/2+4;i++) { //像数组一样访问 System.out.println(mapBuffer.get(i)); } }
三种模式:
Q:NIO中ByteBuffer, 该如何根据正确的编码,转为对应的CharBuffer
A:利用Charset的decode功能。
ByteBuffer byteBuffer = ...; Charset charset = Charset.forName("UTF-8"); CharBuffer charBuffer = charset.decode(byteBuffer);
如果是CharBuffer转ByteBuffer, 就用charset.encode。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/4003/viewspace-2796022/ ,如需转载,请注明出处,否则将追究法律责任。