Java几种文件复制的测评
前言
Java复制文件的实现方式有很多种。
IO模式下:
1.FileInputSTream+FileOutputStream
2.BufferedReader+BufferedWriter 采用readLine方法
3.BufferedReader+BufferedWriter 采用 read(char buffer[])方法
4.InputStream+OutputStream
NIO模式下:
1.FileChannel+FileChannel 复制缓冲区
2.FileChannel+FileChannel 管道直连
Java-Files工具类:
1.Files.copy()
说明
此次测试使用了一个400M的文件测试,并未采用小文件或者超大文件测试。
代码
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@Slf4j
@Data
public class FileCopyDemo {
public static void fileInputStreamIoCopy(String source,String target) throws IOException {
FileInputStream fis=null;
FileOutputStream fos=null;
try {
fis = new FileInputStream(new File(source));
fos = new FileOutputStream(new File(target));
int ch;
byte[] buffer = new byte[1024];
while((ch=fis.read(buffer)) != -1){
fos.write(buffer, 0, ch);
}
fos.flush();
}finally {
if(null != fos){
fos.close();
}
if(null != fis){
fis.close();
}
}
}
public static void bufferReaderLineIoCopy(String source,String target) throws IOException {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(source)));
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(target)));
String s;
while((s=br.readLine())!=null){
bw.write(s);
bw.newLine();
}
bw.flush();
}finally {
if(null != bw){
bw.close();
}
if(null != br){
br.close();
}
}
}
public static void bufferReaderByteIoCopy(String source,String target) throws IOException {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(source)));
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(target)));
char[] buffer = new char[1024];
int bytesRead;
while ((bytesRead = br.read(buffer)) != -1) {
bw.write(buffer, 0, bytesRead);
}
bw.flush();
}finally {
if(null != bw){
bw.close();
}
if(null != br){
br.close();
}
}
}
public static void inputStreamByteIoCopy(String source,String target) throws IOException {
InputStream br = null;
OutputStream bw = null;
try {
br =new FileInputStream(source);
bw =new FileOutputStream(target);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = br.read(buffer)) != -1) {
bw.write(buffer, 0, bytesRead);
}
bw.flush();
}finally {
if(null != bw){
bw.close();
}
if(null != br){
br.close();
}
}
}
public static void fileChannelBufferNioCopy(String source,String target) throws IOException {
FileChannel inChannel = null;
FileChannel outChannel = null;
File file = new File(target);
if(!file.exists()){
Files.createFile(Paths.get(target));
}
try {
RandomAccessFile inFile = new RandomAccessFile(source, "rw");
inChannel = inFile.getChannel();
RandomAccessFile outFile = new RandomAccessFile(target, "rw");
outChannel = outFile.getChannel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
while (inChannel.read(buffer)!=-1){
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
}finally {
if(null != inChannel){
inChannel.close();
}
if(null != outChannel){
outChannel.close();
}
}
}
public static void fileChannelTransferNioCopy(String source,String target) throws IOException {
FileChannel inChannel = null;
FileChannel outChannel = null;
File file = new File(target);
if(!file.exists()){
Files.createFile(Paths.get(target));
}
try {
RandomAccessFile inFile = new RandomAccessFile(source, "rw");
inChannel = inFile.getChannel();
RandomAccessFile outFile = new RandomAccessFile(target, "rw");
outChannel = outFile.getChannel();
inChannel.transferTo(0,inChannel.size(),outChannel);
}finally {
if(null != inChannel){
inChannel.close();
}
if(null != outChannel){
outChannel.close();
}
}
}
public static void main(String[] args) throws IOException {
String source = "D:/test/mail_foxymoon.sql";
String target1 = "D:/test/mail_foxymoon-1.sql";
String target2 = "D:/test/mail_foxymoon-2.sql";
String target3 = "D:/test/mail_foxymoon-3.sql";
String target4 = "D:/test/mail_foxymoon-4.sql";
String target5 = "D:/test/mail_foxymoon-5.sql";
String target6 = "D:/test/mail_foxymoon-6.sql";
String target7 = "D:/test/mail_foxymoon-7.sql";
String target8 = "D:/test/mail_foxymoon-7.sql";
long l1 = System.currentTimeMillis();
fileInputStreamIoCopy(source,target1);
log.info("fileInputStreamIoCopy:总共耗时:{}",System.currentTimeMillis()-l1);
long l2 = System.currentTimeMillis();
bufferReaderLineIoCopy(source,target2);
log.info("bufferReaderLineIoCopy:总共耗时:{}",System.currentTimeMillis()-l2);
long l3 = System.currentTimeMillis();
bufferReaderByteIoCopy(source,target3);
log.info("bufferReaderByteIoCopy(1024):总共耗时:{}",System.currentTimeMillis()-l3);
long l4 = System.currentTimeMillis();
inputStreamByteIoCopy(source,target4);
log.info("inputStreamByteIoCopy(1024):总共耗时:{}",System.currentTimeMillis()-l4);
long l5 = System.currentTimeMillis();
fileChannelBufferNioCopy(source,target5);
log.info("fileChannelBufferNioCopy:总共耗时:{}",System.currentTimeMillis()-l5);
long l6 = System.currentTimeMillis();
fileChannelTransferNioCopy(source,target6);
log.info("fileChannelTransferNioCopy :总共耗时:{}",System.currentTimeMillis()-l6);
long l7 = System.currentTimeMillis();
Files.copy(Paths.get(source),Paths.get(target7));
log.info("Files.copy 底层是inputByteIoCopy(8096) 适用于确定目标文件不存在 否则报错:总共耗时:{}",System.currentTimeMillis()-l7);
long l8 = System.currentTimeMillis();
Files.copy(Paths.get(source),Paths.get(target8), StandardCopyOption.REPLACE_EXISTING);
log.info("Files.copy 底层是inputByteIoCopy(8096) 存在则替换:总共耗时:{}",System.currentTimeMillis()-l8);
}
}
测试结果
: fileInputStreamIoCopy:总共耗时:3428
: bufferReaderLineIoCopy:总共耗时:4618
: bufferReaderByteIoCopy(1024):总共耗时:4212
: inputStreamByteIoCopy(1024):总共耗时:2183
: fileChannelBufferNioCopy:总共耗时:2241
: fileChannelTransferNioCopy :总共耗时:816
: Files.copy 底层是inputByteIoCopy(8096) 适用于确定目标文件不存在 否则报错:总共耗时:734
: Files.copy 底层是inputByteIoCopy(8096) 存在则替换:总共耗时:1202
从结果上来看,NIO管道直连的方式最佳。也就是传说中的零拷贝技术。
分析
管道技术FileChannel和输入输出流,看起来都类似于一种通道,为什么性能差距未如此之大呢?
这就不得不说说原理了。
内存分为 内核态和用户态,内核态由操作内核管理,上下文为操作系统。用户态由应用程序管理,上下文为应用程序,例如JVM申请的内存。
其中,数据从内核态→用户态,内核态→内核态都需要cpu进行调度。数据从磁盘到内核态是由DMA技术实现。
那么传统输入输出流文件下载的服务器的过程是: 磁盘→(磁盘缓存区)内核态→(java变量)用户态→(网络缓存区)内核态,中间cpu参与了2次调度
零拷贝技术的原理是:磁盘→(磁盘缓存区)内核态→(网络缓存区)内核态,cpu参与了1次调度
所以:
传统的Java输入输出流通道代表的是 磁盘--内核--用户态
管道技术FileChannel通道代表的是 磁盘--内核
那么:
是不是用了管道FileChannel就代表用了NIO,零拷贝了呢?
答案:
肯定不是,管道技术只是提供了NIO多路复用的可行性,且对比fileChannelBufferNioCopy和fileChannelTransferNioCopy 可知,在使用transferTo,transferFrom才是零拷贝技术,自己声明Buffer再拷贝,其实还是传统的输入输出流模式。