一、简介

  通道是 Java NIO 的核心内容之一,在使用上,通道需和缓存类(ByteBuffer)配合完成读写等操作。与传统的流式 IO 中数据单向流动不同通道中的数据可以双向流动通道既可以读,也可以写。

  Java NIO 出现在 JDK 1.4 中,由于 NIO 效率高于传统的 IO,所以 Sun 公司从底层对传统 IO 的实现进行了修改。修改的方式就是在保证兼容性的情况下,使用 NIO 重构 IO 的方法实现,无形中提高了传统 IO 的效率。

  所有的 NIO 操作始于通道,通道是数据来源或数据写入的目的地。

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。

  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据

    

二、基本操作

  通道类型分为两种。一种是文件通道(面向文件的),另一种是Socket通道(面向网络的)。具体的类声明如下:

  • FileChannel文件通道,用于文件的读和写

  • DatagramChannel:用于 UDP 连接的接收和发送

  • SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端

  • ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求

    

  Channel 经常翻译为通道,类似 IO 中的流,用于读取和写入。它与前面介绍的 Buffer 打交道,读操作的时候将 Channel 中的数据填充到 Buffer 中,而写操作时将 Buffer 中的数据写入到 Channel 中。

三、通道

(一).FileChannel文件通道

  FileChannel 是一个用于连接文件的通道,通过该通道,既可以从文件中读取,也可以向文件中写入数据。与SocketChannel 不同,FileChannel 无法设置为非阻塞模式,这意味着它只能运行在阻塞模式下

  在使用FileChannel 之前,需要先打开它。由于 FileChannel 是一个抽象类,所以不能通过直接创建而来。必须通过像 InputStream、OutputStream 或 RandomAccessFile 等实例获取一个 FileChannel 实例。

FileInputStream fis = new FileInputStream(FILE_PATH);
FileChannel channel = fis.getChannel(); FileOutputStream fos = new FileOutputStream(FILE_PATH);
FileChannel channel = fis.getChannel(); RandomAccessFile raf = new RandomAccessFile(FILE_PATH , "rw");
FileChannel channel = raf.getChannel();

 读写操作

// 获取管道
RandomAccessFile raf = new RandomAccessFile(FILE_PATH, "rw");
FileChannel rafChannel = raf.getChannel(); // 准备数据
String data = "新数据,时间: " + System.currentTimeMillis();
System.out.println("原数据:\n" + " " + data);
//准备缓冲区
ByteBuffer buffer = ByteBuffer.allocate(128);
buffer.clear();
buffer.put(data.getBytes());
buffer.flip(); // 向管道中写入数据
rafChannel.write(buffer); rafChannel.close();
raf.close();//管道关闭 // 重新打开管道
raf = new RandomAccessFile(FILE_PATH, "rw");
rafChannel = raf.getChannel(); // 读取管道中的数据
buffer.clear();
rafChannel.read(buffer); // 打印读取出的数据
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println("读取到的数据:\n" + " " + new String(bytes)); rafChannel.close();
raf.close();
//结果如下

 数据转移操作

  我们有时需要将一个文件中的内容复制到另一个文件中去,最容易想到的做法是利用传统的 IO 将源文件中的内容读取到内存中,然后再往目标文件中写入。

  现在,有了 NIO,我们可以利用更方便快捷的方式去完成复制操作。FileChannel 提供了一对数据转移方法 - transferFrom/transferTo,通过使用这两个方法,即可简化文件复制操作。

public static void main(String[] args) throws IOException {
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel(); long position = 0;
long count = fromChannel.size(); // 将 fromFile 文件找那个的数据转移到 toFile 中去
System.out.println("before transfer: " + readChannel(toChannel));
fromChannel.transferTo(position, count, toChannel);
System.out.println("after transfer : " + readChannel(toChannel)); fromChannel.close();
fromFile.close();
toChannel.close();
toFile.close();
} private static String readChannel(FileChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(32);
buffer.clear(); // 将 channel 读取位置设为 0,也就是文件开始位置
channel.position(0);
channel.read(buffer); // 再次将文件位置归零
channel.position(0); buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
return new String(bytes);
}
//结果

  通过上面的代码,我们可以明显感受到,利用 transferTo 减少了编码量。那么为什么利用 transferTo 可以减少编码量呢?在解答这个问题前,先来说说程序读取数据和写入文件的过程。

  我们现在所使用的 PC 操作系统,将内存分为了内核空间用户空间。操作系统的内核和一些硬件的驱动程序就是运行在内核空间内,而用户空间就是我们自己写的程序所能运行的内存区域。

  这里,当我们调用 read 从磁盘中读取数据时,内核首先将数据读取内核空间中,然后再将数据内核空间复制到用户空间内。也就是说,我们需要通过内核进行数据中转。同样,写入数据也是如此。系统先从用户空间将数据拷贝到内核空间中,然后再由内核空间向磁盘写入。相关示意图如下:

与上面的数据流向不同,FileChannel 的 transferTo 方法底层基于 sendfile64(Linux 平台下)系统调用实现。sendfile64 会直接在内核空间内进行数据拷贝,免去了内核往用户空间拷贝用户空间再往内核空间拷贝这两步操作,因此提高了效率。其示意图如下:

  通过上面的讲解,大家应该知道了 transferTo 和 transferFrom 的效率会高于传统的 read 和 write 在效率上的区别。区别的原因在于免去了内核空间和用户空间的相互拷贝,虽然内存间拷贝的速度比较快,但涉及到大量的数据拷贝时,相互拷贝的带来的消耗是不应该被忽略的。


其他操作

  FileChannel 还有一些其他的方法,这里通过一个表格来列举这些方法,就不一一展开说明了。如下:

方法名 用途
position 返回或修改通道读写位置
size 获取通道所关联文件的大小
truncate 截断通道所关联的文件
force 强制将通道中的新数据刷新到文件中
close 关闭通道
lock 对通道文件进行加锁

四、总结

   以上章节对 NIO 文件通道的用法和部分方法的实现进行了简单分析。

  从上面的分析可以看出,NIO FileChannel 在实现上,实际上是对底层操作系统的一些 API 进行了再次封装,也就是一层皮。有了这层封装后,对上就屏蔽了底层 API 的细节,以降低使用难度。

  Java 为了提高开发效率,屏蔽了操作系统层面的细节。虽然 Java 可以屏蔽这些细节,但作为开发人员,我觉得我们不能也去屏蔽这些细节(虽然不了解这些细节也能写代码),有时间还是应该多了解了解这些底层的东西。毕竟要想往更高的层次发展,这些底层的知识必不可少。

最新文章

  1. GO 1.5 代码编译安装 [centos7 64位]
  2. localForage——轻松实现 Web 离线存储(转)
  3. python __name__
  4. 关于jQuery的inArray 方法介绍
  5. 基于Delphi的三层数据库系统的实现方法
  6. ubuntu安装多个qt版本--不同qt版本编译同一个程序时出现错误--解决方案
  7. Student
  8. 5个Unix命令
  9. (中等) CF 585D Lizard Era: Beginning,中途相遇。
  10. CocoaLumberjack——带颜色的Log
  11. TechEmpower最新一轮的性能测试出炉,ASP.NET Core依旧表现不俗
  12. OracleAES加密
  13. Win10上安装TensorFlow(官方文档翻译)
  14. 论文笔记——Factorized Convolutional Neural Networks
  15. mfc 类对象数组
  16. Weblogic wls-wsat组件反序列化漏洞(CVE-2017-10271)
  17. Windows配置MinGW环境变量
  18. IBM DEVOPS IN CLOUD--chaos monkey
  19. MEF——.NET中值得体验的精妙设计
  20. IDEA 修改JSP和后端数据后,页面刷新可以实时更新

热门文章

  1. 数据结构实验之二叉树一:树的同构 (SDUT 3340)
  2. php curl 传递数据
  3. php rsa 非对称加解密类
  4. 微信H5的video标签解决方案
  5. CF2B The least round way(贪心+动规)
  6. [提权]Windows COM组件提权漏洞 (CVE-2017-0213)
  7. hello world&Restart the Journey
  8. 详解css3 pointer-events(阻止hover、active、onclick等触发事件来
  9. 实现Callable接口实现多线程
  10. 【零基础】使用Tensorflow实现神经网络