深入理解Java IO、NIO

本文基于 jdk8

在了解 NIO 前,先来看下 IO。

Java IO

原理探究

下面这个例子将数据从一个文件读取到后,写入到另一个文件。

FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(des);
byte[] bytes = new byte[1024 * 8];
int len;
while ((len = fis.read(bytes)) != -1) {
    fos.write(bytes, 0, len);
}

这里的 fis.read函数调用的是:
在这里插入图片描述
跟进 OpenJdk 的源码,可以看到 readBytes 的实现,如下图:
在这里插入图片描述
中间经过了三次数据 copy

  • DMA 从硬件 copy 到内核空间
  • 内核空间 copy 到堆外内存 buf (用户空间)
  • 从 buf copy 到 JVM 堆内存 byte数组

这里有个问题,为什么中间多一步从内核空间 copy 到堆外内存 buf,不直接从内核空间 copy 到 JVM 堆内存呢?这是因为 JVM 有垃圾回收机制,内存地址可能会发送变化,需要 native 代码将 JVM 数组引用转换为实际的内存地址。

交互过程如下图:
在这里插入图片描述

优缺点、适用场景

优点就是简单直观,易懂易上手。缺点主要是性能问题,因为是阻塞 IO,而 IO 又是非常慢的操作,会浪费大量进程、线程资源。

数据量不大,并发不高,对阻塞不在意。

JAVA NIO

1.4引入的,提供了比传统 IO 更高效、更灵活的 IO 操作。

NIIO 主要接口

  • Buffer:顾名思义,就是一块可读、可写的内存区。
  • Channel:读写数据的双向通道,面向 Buffer,非阻塞式。主要类型有面向网络的 SocketChannel、ServerSocketChannel、DatagramChannel,还有面向文件的 FileChannel。
  • Selector:多路复用选择器,封装了下 IO 多路复用中的 select,参见 如何理解Linux IO 模型 (中的多路复用 )
Buffer

通过三个例子来看下不同的 buffer。

HeapByteBuffer
FileChannel readChannel = fis.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 8);
readChannel.read(byteBuffer);

可以看到这里分配的是 HeapByteBuffer
在这里插入图片描述
跟进源码
在这里插入图片描述
如下图所示,这里分配了一个临时 DirectBuffer
在这里插入图片描述
继续跟代码,发现后边就是系统调用了。交互过程类似上述 IO,如下图所示:
在这里插入图片描述

DirectByteBuffer
FileChannel readChannel = fis.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 8);
readChannel.read(byteBuffer);

交互过程如下图,从图上可以看出,比 HeapByteBuffer 减少了一次用户态的数据copy。
在这里插入图片描述

MappedByteBuffer

DirectByteBuffer 依然有两次数据 copy,一次是从硬件设备 copy 到内核空间,一次是从内核空间 copy 到用户空间。那有没有更高效的方式呢?来一起看下 MappedByteBuffer。

FileChannel channel = FileChannel.open(Paths.get("xxx.txt"), StandardOpenOption.READ);
MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_ONLY
, 0, channel.size());

交互过程如下图,仅需要一次数据 copy,从硬件设备 copy 数据到内核空间。
在这里插入图片描述

transferTo、transferFrom

跟进源码,transferFrom 实际使用了 MappedByteBuffer,这里就不赘述了。重点来看下 transferTo,这就是很多框架宣称“零拷贝”的原理。

 FileChannel inChannel = FileChannel.open(Paths.get("xxx.txt"), StandardOpenOption.READ);
 FileChannel outChannel = FileChannel.open(Paths.get("xxx2.txt"),     StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
 inChannel.transferTo(0,inChannel.size(),outChannel);

这里调用了 Linux 操作系统提供的 sendFile 函数,它的交互过程如下图所示:
在这里插入图片描述
实际上还是存在在数据copy,数据依然是从硬件复制到内核空间,再由内核空间复制到网卡,对操作系统而言,不存在冗余的复制,这就是“零拷贝”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值