摘要: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:

  • 标准IO, 基于字节流和字符流进行操作,阻塞IO。
  • NIO基于通道channel和缓冲区Buffer进行操作,支持非阻塞IO,提供选择器

§ JavaNIO核心3组件:

§ Channels 通道

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);
  • 注意上图上,fileChannel.read(buf)是将a.txt里的数据读到buf, 即a.txt->buf
  • fileChannel.write(buf)是将buf里的数据写入到a.txt中, 即buf->a.txt,不要搞反啦!
  • 通道和缓冲区的关系

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

  • FileChannel

Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下

创建方式

RandomAccessFile    file = new RandomAccessFile("D:/aa.txt");
FileChannel    fileChannel = file.getChannel();
  • SocketChannel

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));
  • ServerSocketChannel

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...
}

Buffer缓冲区

  • 我们真正要把数据拿到或者要写数据, 实际上都是通过buffer进行操作的。
    文件 <-> buffer <-> 数据
  • buffer是1个即可读也可写的缓冲区,拥有读写2种模式。
  • buffer的capacity属性限定了每个buffer的最大容量,下面的1024就是capacity。

ByteBuffer buf = ByteBuffer.allocate(1024);

  • buffer拥有1个position属性,表示当前的读写位置。
  • 往buffer中写数据时,position就会增加。
  • position最大值为capacity-1
  • 把fileChannel对应文件里的数据 写入到buffer,叫做写模式
  • 写之后,调用flip,让buffer的postion置0,此时相当于准备读取buffer里的数据(即调用buffer.get()拿数据)
  • (这个模式的叫法个人也觉得不太好,很容易绕,你可以就记忆成: flip就是从写模式转成读模式!)

Q: buffer调用flip()方法从写模式切换到读模式时,position会变成多少?
A: 变为0。

 ByteBuffer buf = ByteBuffer.allocate(1024);// 数据读到buf中,并返回数量,每次最多读1024个int byteRead = fileChannel.read(buf);// 输出byteRead的数量,最多为1024System.out.println("position=" + buf.position()+", byteRead=" + byteRead);buf.flip();// 切换到读模式了,输出0System.out.println("position=" + buf.position());
  • buffer拥有1个limit属性。
  • 写模式下,buffer的limit就是buffer的capacity。
    Q: 当buffer从写模式切换到读模式时,limit为多少?
    A: 每次切换前都要调用flip(),切换后,limit为写模式中的position。
        int byteRead = fileChannel.read(buf);// 输出1024System.out.println("limit=" + buf.limit() + ",postion=" + buf.position());System.out.println("切换到读模式");buf.flip();// 输出byteRead数量System.out.println("limit=" + buf.limit());

结果如下

Q: 向buf缓冲区写数据的方式有哪些?
A:

  • int byteRead = fileChannel.read(buf);
    从通道中读数据到buf中, 即相当于向buf缓冲区中写数据。
  • buf.putChar(‘a’);
    手动向buf中写入字符a, postion加1。

Q: 从buf缓冲区读数据的方式有哪些?

  • int bytesWrite = fileChannel.write(buf)
    buf中的数据写入到管道,即相当于fileChannel读取buf中的数据。
  • byte getByte = buf.get()
    手动读取1个buf中的字符,postion加1.

Q: 手动修改当前缓冲区的postion的方法有哪些?
A:

  • rewind() 将postion设置为0
  • mark() 可以标记1个特定的位置, 相当于打标记, 在一顿操作后,可通过reset()回到之前mark()的位置(就像你需要mark我的这几篇博文一样!)

Q:1个channel管道支持多个buffer吗?
A: 支持。 通道的write和read方法都支持传入1个buffer数组,会按照顺序做读写操作。

Buffer的种类:

Buffer的另外3个方法:

  • warp:
    根据一个byte[]来生成一个固定的ByteBuffer时,使用ByteBuffer.wrap()非法的合适。他会直接基于byte[]数组生成一个新的buffer,值也保持一致。
  • slice:
    得到切片后的数组。
  • duplicate:
    调用duplicate方法返回的Buffer对象就是复制了一份原始缓冲区,复制了position、limit、capacity这些属性
  • 注意!!!!!!
    以上warp\slice\duplicte生成的缓冲区get和put所操作的数组还是与原始缓冲区一样的。所以对复制后的缓冲区进行修改也会修改原始的缓冲区,反之亦然。
    因此duplicte、slice一般是用于操作一下poistion\limit等处理,但是原内容不会去变他,否则就会引起原缓冲器的修改。

§ Selector

selector可用来在线程中关联多个通道,并进行事件监听。

Q: 在NIO中Selector的好处是什么?
A:

  • 可以用更少的线程来管理各个通道。
  • 减少线程上下文切换的资源开销。

Q: Selector支持注册哪种类型的通道?
A:
支持非阻塞的通道。
通道要在注册前调用 channel.configureBlocking(false) 设置为非阻塞。
例如FileChannel就没办法注册,他注定是阻塞的。而socketChannel就可以支持非阻塞。

Q: Selector注册时,支持监听哪几种事件,对应的常量是什么?(啊最不喜欢记忆这种东西了…)
A:共有4种可监听事件

  • Connect 成功连接到1个服务器,对应常量SelectionKey.OP_CONNECT
  • Accept 准备好接收新进入的连接, 对应常量SelectionKey.OP_ACCEPT
  • Read, 有数据可读,对应常量SelectionKey.OP_READ
  • Write 接收到往里写的数据, 对应常量SelectionKey.OP_WRITE
    如果希望对该通道监听多种事件,可以用"|"位或操作符把常量连接起来。
 int interestingSet = Selectionkey.OP_READ | Selectionkey.OP_WRITE;
Selectionkey key = channel.register(selector,interestingSet)
  • SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系

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对准备就绪的通道做处理:

  1. 调用select()方法获取已就绪的通道,返回的int值表示有多少通道已经就绪
  2. 从selector中获取selectedkeys
  3. 遍历selectedkeys
  4. 查看各SelectionKey中 是否有事件就绪了。
  5. 如果有事件就绪,从key中获取对应对应管道。做对应处理
    类似如下,一般都会启1个线程来run这个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()方法是阻塞的?

  • 其实NIO的非阻塞,指的是IO不阻塞,即我们不会卡在read()处,我们会用selector去查询就绪状态,如果状态ok就。
  • 而查询操作是需要时间,因此select()必须要把所有通道都检查一遍才能告诉结果,因此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-catchfileLock.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));}
}

三种模式:

  • MapMode.READ_ONLY(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException。
  • MapMode.READ_WRITE(读/写): 对得到的缓冲区的更改会写入文件,需要调用fore()方法
  • MapMode.PRIVATE(专用): 可读可写,但是修改的内容不会写入文件,只是buffer自身的改变。

Q:NIO中ByteBuffer, 该如何根据正确的编码,转为对应的CharBuffer
A:利用Charset的decode功能。

ByteBuffer byteBuffer = ...;
Charset charset = Charset.forName("UTF-8");
CharBuffer charBuffer = charset.decode(byteBuffer);

如果是CharBuffer转ByteBuffer, 就用charset.encode。

点击关注,第一时间了解华为云新鲜技术~

java中的NIO和IO到底是什么区别?20个问题告诉你答案相关推荐

  1. NIO 和 IO 到底有什么区别?别说你不会!

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 源码精品专栏 原创 | Java 2020 超神之路,很肝~ 中文详细注释的开源项目 ...

  2. java中的三种IO(BIO、NIO、AIO)

    IO 阻塞和非阻塞主要指的是访问 IO 的线程是否会阻塞(或者说是等待) 线程访问资源,该资源是否准备就绪的一种处理方式 BIO(传统的IO) BIO是同步阻塞式的IO,以流的方式处理数据(效率低) ...

  3. java中属性是什么意思啊_Java中的字段和属性到底有什么区别?

    Java中的字段和属性到底是什么?他们的含义真的是相同的吗?如果不同,那他们到底又分别是何含义呢? 相信上面的问题,对于很多Java初学者都是相当困惑的,但是好像把他们理解为一个含义也没啥问题,很多J ...

  4. java中的native方法性能到底怎么样?

    前言 java中的native方法性能到底怎么样? 第一次写博客,如果写的不好,望见谅,烦请指出问题,虚心学习 先说结论,native 方法性能不如java方法 一.native方法? 主要是java ...

  5. java堆和栈 常量池_GitHub - han-guang-xue/difference-of-stack-heap-pool: Java中堆、栈和常量池的区别...

    Java中堆.栈和常量池的区别 栈 堆 常量池的概念 首先我们先了解一下概念,Java把内存分成两种,一种叫做栈内存,一种叫做堆内存. 栈内存 存放基本类型的变量数据和对象类型的引用(请注意存放的是引 ...

  6. Java中,一切皆是对象——java中的对象类型与基本数据类型的区别

    起因:取一个list给另一个list赋值,然后在另一个list中取出进行修改.list其中类型为对象时,String时,基本数据类型时. [java中的对象类型与基本数据类型的区别] #声明和实例化 ...

  7. Java中Array和ArrayList之间的9个区别

    array和ArrayList都是Java中两个重要的数据结构,在Java程序中经常使用. 即使ArrayList在内部由数组支持,了解Java中的数组和ArrayList之间的差异对于成为一名优秀的 ...

  8. java中list,set,map集合的区别,及面试要点

    Map集合:链接: Map集合的五种遍历方式及Treemap方法 Set集合:链接: Java中遍历Set集合的三种方法 TreeSet集合:链接: Java深入了解TreeSet,和迭代器遍历方法 ...

  9. java中*和**的作用 以及 /和/*和/**的区别

    一.java中*和**的作用 "*"就表示了所有的文件,但是"*"并不包括子目录下的文件: "**"匹配包含任意级子目录中所有的文件: 二. ...

最新文章

  1. 北京科技大学智能视觉参赛队伍 - 对于比赛总结
  2. 数据库视频总结四(存储过程和触发器)
  3. k8s核心技术-集群安全机制(RBAC实现鉴权)---K8S_Google工作笔记0040
  4. linux驱动编写(入门)
  5. 小米相机曝光_小米11pro曝光,超级屏+双6400万+骁龙875,不愧是小米旗舰
  6. zencart产品页面调用WordPress最新文章
  7. 写给非网工的CCNA教程(4)聊聊ping命令后的原理(续)
  8. Remote Desktop Connection Manager (RDCMan)
  9. 本地配置微信H5测试
  10. 第一道web类CTF题——一起来撸猫
  11. 虚拟机中计算机内存不够,win7系统VMware虚拟机电脑安装系统提示虚拟内存不足的解决方法...
  12. Gradle 2.0 用户指南翻译——第五十四章. 构建本机二进制文件
  13. linux机顶盒界面,基于嵌入式Linux的IPTV机顶盒的设计与实现
  14. 【PHP】\r \r\n \t是什么
  15. Hardhat创建、编译、测试智能合约
  16. 如何在Flatter中以正确的方式存储登录凭证
  17. C#获取月份的中/英文名称
  18. 讲一下 SVG... 吧
  19. 字符映射表没有所有字体(专用字符),以及显示空白的解决办法
  20. 2020年最后15天总结

热门文章

  1. Bootstrap缩略图.thumbnail
  2. 树莓派Raspberry Pi OS开机自启动脚本
  3. php 星座运势_星座运势查询示例代码
  4. 有关java的参考软件_Java的相关的排序实现(参考软件设计师教程)
  5. 东风畅行java_东风畅行载货车为何可以口碑很好吗?是配置高?或者另有原因?...
  6. 苹果x屏幕出现一条绿线_部分用户反映苹果 iPhone 12 屏幕出现划痕 - iPhone 12
  7. 在Linux中切换用户的命令是set,Linux基础命令---切换用户su
  8. python主要用于做什么-python主要用于哪些方向
  9. log日志java web_Javaweb项目中使用Log4j记录日志
  10. 中移物联网答案java_【分享】中移物联网校园招聘笔试-java