Java文件拷贝最佳实践的思考
原始时代
对于Java中的文件IO我们最初学到也是最熟悉的方式就是通过BIO的方式去拷贝
1 | import java.io.FileInputStream; |
为什么不好?
但是通过这种方式拷贝文件有什么不好的地方呢?
我们最先想到的可能就是存在多次拷贝。
其实我们仔细分析,这里面存在了四次文件的拷贝。
硬盘 –DMA–> 内核缓冲区(PageCache)
这里还有一次用户态和内核态的转换
内核缓冲区 –CPU–> 用户空间
这里也还有一次用户态和内核态的转换
用户空间 –CPU–> 硬盘缓冲区
硬盘缓冲区 –DMA–> 硬盘
这种拷贝的实现方式太慢了,所以我们自然而然的就想到使用零拷贝
而Java中的NIO存在零拷贝的实现。
Java中的Files.copy()底层其实也是通过NIO去实现的
NIO优化?
1 |
|
这里面也涉及到了IO多路复用对于这个场景的优化,这里就不展开讲了,有兴趣的读者可以自己去查询资料。
零拷贝复制真的是最佳解?
问题
上面的零拷贝看起来好像已经完美的解决了这个问题,但是事实确实是如此吗?
我们不妨来分析一下零拷贝的这个过程。
- 硬盘 –DMA–> 内核缓冲区(PageCache)
- 内核缓冲区(PageCache) –CPU–> 硬盘缓冲区
- 硬盘缓冲区 –DMA–> 硬盘
我们发现零拷贝的过程其实依赖了一个内核缓冲区(PageCache)的东西,假如当我们拷贝一个超大文件时,我们发现大文件是无法利用内核缓冲区(PageCache)的,而因为内核缓冲区(PageCache)被占据,导致小文件也无法使用。反而减慢了拷贝的过程。
解决方法
所以我们自然而然的就想到了当大文件读写时绕过内核缓冲区,一种称为直接IO的技术。对于直接IO的方案,AIO通常是个好方法。
最终方案
通过上面的讨论,我们发现大文件(异步IO + 直接IO) + 小文件零拷贝是一种很好的解决方案。以下是这种方案代码实现。
1 |
|