Java I/O(2):NIO中的Channel

打印 上一主题 下一主题

主题 868|帖子 868|积分 2604

您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~
  
 
为了解决标准Java I/O令人难以忍受的效率问题,从JDK1.4开始,NIO出现了(Non-blocking I/O,官方称之为New I/O)。NIO不但新增加了许多全新的类,而且还对原来的很多类进行了改写。之所以是NIO,是因为使用它的场景众多,譬如开发中必不可少的Tomcat,以及大名鼎鼎的Netty,而Netty更是把NIO发挥到了极致,成为了RPC技术事实上的标准,所以它在JDK1.7中又升级为了AIO(NIO2)。
NIO主要有三大核心部分:

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器/多路复用器)
传统I/O基于字节流或字符流进行操作,而NIO基于新的Channel和Buffer进行操作。这是它们的比较:

 
 
 
至于原理,不用记,可以这么来理解(我始终秉持的态度是:如果你在大厂是自研类RPC系统或类MQ中间件的,那这个一定要精通;否则理解就好,不必死磕):
可以看到,I/O就像个直肠子,直来直去,对数据流完全是来者不拒,来多少接多少,也不管能不能处理得了,这样极容易造成线程阻塞,也就是电脑卡顿。
而NIO就有点弯弯绕了,它告诉线程:“如果我忙不过来就别等我,你先忙你的”。所以,按照这个约定,如果线程发现它不搭理自己的时候就会去忙别的。不会造成信息堵车。
Channel接口最重要的实现可以分为两大类:用于本地文件和用于网络的Channel。

  • FileChannel:用于本地文件数据的读写
  • DatagramChannel:用于网络UDP数据的读写
  • SocketChannel:客户端用于实现网络TCP数据的读写
  • ServerSocketChannel:服务端用于监听网络TCP的连接请求,每个请求会创建会一个SocketChannel(即客户端连接)
这是和Channel相关的继承结构图:

 
 
 
I/O本就枯燥,如果只是空洞说技术原理就更毫无价值,还是上代码,把NIO和IO比较一下。
创建一个普通的Java项目:

 
 
 

 
 
 
然后随便在网上或者自己电脑上找一个大文件,比如小电影之类的,写这样的代码:
  1. // 把file1中的内容写到file2中去,看看耗时
  2. // I/O读写
  3. long start = System.currentTimeMillis();
  4. try {
  5.     FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
  6.     FileOutputStream fos = new FileOutputStream("你电脑上还不存在的文件路径,例如C:\\file2");
  7.     BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fis), 1024);
  8.     String line = null;
  9.     while ((line = bufferedReader.readLine()) != null) {
  10.         fos.write(line.getBytes());
  11.     }
  12.     fis.close();
  13.     fos.close();
  14. } catch (IOException e) {
  15.     e.printStackTrace();
  16. }
  17. long end = System.currentTimeMillis();
  18. System.out.println(end - start);
复制代码
 
 
然后再稍稍改进一下,看看byte[]相对于BufferedReader的不同:
  1. // 把file1中的内容写到file3中去,看看耗时
  2. // I/O读写(改进)
  3. start = System.currentTimeMillis();
  4. try {
  5.     FileInputStream fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1");
  6.     FileOutputStream fos = new FileOutputStream("你电脑上还不存在的文件路径,例如C:\\file3");
  7.     byte[] b = new byte[1024];
  8.     int len = 0;
  9.     while ((len = fis.read(b)) != -1) {
  10.         fos.write(b, 0, len);
  11.     }
  12.     fis.close();
  13.     fos.close();
  14. } catch (IOException e) {
  15.     e.printStackTrace();
  16. }
  17. end = System.currentTimeMillis();
  18. System.out.println(end - start);
复制代码
 
 
最后再用NIO试试看:
  1. // 把file1中的内容写到file4中去,看看耗时
  2. // NIO读写
  3. start = System.currentTimeMillis();
  4. try {
  5.     FileChannel fis = new FileInputStream("你电脑上已经存在的文件路径,例如C:\\file1").getChannel();
  6.     FileChannel fos = new FileOutputStream("你电脑上还不存在的文件路径,例如C:\\file4").getChannel();
  7.     ByteBuffer bytedata = ByteBuffer.allocate(1024);
  8.     while (fis.read(bytedata) != -1) {
  9.         // 读写交叉进行
  10.         bytedata.flip();
  11.         fos.write(bytedata);
  12.         bytedata.clear();
  13.     }
  14.     fis.close();
  15.     fos.close();
  16. } catch (IOException e) {
  17.     e.printStackTrace();
  18. }
  19. end = System.currentTimeMillis();
  20. System.out.println(end - start);
复制代码
 
 
在main()方法中分别执行这三个方法,看看耗时上有啥不同。尽量找很大的文件,比如几个G的那种。因为现在计算机的配置都比较高,文件太小,一会就读完了,根本看不出来差别。
另外,另外,在NIO中如果一个channel是FileChannel类型的,那么可以直接把FileChannel的数据传输到另一个Channel,就像这样:

 
 
 
SocketChannel、ServerSocketChannel和DatagramChannel的使用也比较简单,就不堆代码了。
Channel还提供了一种被称为Scatter/Gather(分散/聚集)的新功能(也称为Vectored I/O,矢量I/O),它在多个Buffer上实现一个简单的I/O操作。说人话就是:Scatter是把单个Channel的数据发给多个Buffer(分散),而Gather则是把多个Buffer的数据发给单个Channel(聚集),就像这样:

 
 
同样可以用代码来演示一下:
  1. // Scattering reads分散过程
  2. ByteBuffer buffer1 = ByteBuffer.allocate(1024);
  3. ByteBuffer buffer2 = ByteBuffer.allocate(1024);
  4. ByteBuffer[] bufferArray1 = { buffer1, buffer2 };
  5. FileChannel channel1 = new FileInputStream("/testfile1").getChannel();
  6. channel1.read(bufferArray1);
  7. // Gathering writes聚集过程
  8. ByteBuffer buffer3 = ByteBuffer.allocate(1024);
  9. ByteBuffer buffer4 = ByteBuffer.allocate(1024);
  10. ByteBuffer[] bufferArray2 = { buffer1, buffer2 };
  11. FileChannel channel2 = new FileInputStream("/testfile1").getChannel();
  12. channel2.write(bufferArray2);
复制代码
 
 
好了,NIO也属于Java中比较重要的内容,说多了容易搞晕。慢慢来~
 
 
感谢您抽空品鉴!技术、产品、运营和管理问题,可随时留言私信,欢迎骚扰~
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

飞不高

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表