java-copy文件效率的探究
1.字节流和缓冲流
我们需要了解一个知识,就是字节流FileInputStream 和 缓冲流.BufferedInputStream 这两个区别.
下面这段代码是字节流的copy文件示例.
import java.io.*;
public class CopyFile {
public static void main(String[] args) {
try {
// Create a byte stream to read from the source file
FileInputStream fis = new FileInputStream("source.txt");
// Create a byte stream to write to the destination file
FileOutputStream fos = new FileOutputStream("destination.txt");
// Read bytes from the source file and write them to the destination file
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
// Close the streams
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
下面这段代码是缓冲流的copy文件示例.
import java.io.*;
public class CopyFile {
public static void main(String[] args) {
try {
// Create a buffered stream to read from the source file
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.txt"));
// Create a buffered stream to write to the destination file
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("destination.txt"));
// Read bytes from the source file and write them to the destination file
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
// Close the streams
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
可以看到字节流和缓冲流的表面上区别就是缓冲流在字节流之上包装了一个Buffered.但是他们本质上的运行原理是不一样的.
字节流每读一个字节,都是cpu去访问文件,而缓冲流却是先将文件的部分(默认8M)缓冲到内存里面,缓冲流每读一个字节,是cpu去访问内存.
在文件相对大的情况下,一个是访问内存,一个是访问磁盘,它们的效率不可同日而语.
或许上述的描述不太准确,因为我们都见过byte b = new byte[8192]
这样的代码.不管是字节流或者说缓冲流都会这样使用,那么如果要使用数组接受,描述应该是这样的:
字节流每读一个数组,都是cpu去访问文件,而缓冲流却是先将文件的部分(默认8M)缓冲到内存里面,缓冲流每读一个数组,是cpu去访问内存.
即使如此,它们的效率也是不可同日而语.
我们的结论是:
缓冲流的效率高于字节流
如果文件很小,反而字节流的效率要高.因为构造缓冲区本身还需要一定资源的.
2.FileUtil工具类
如果是copy文件,那么每次都要写上述的代码,那就很繁琐,所以我们就有了工具类,但是不同的工具类底层到底是字节流还是缓冲流,我们需要探究下,来选择我们所需要的.
- org.springframework.util.FileCopyUtils
可以追溯源码,到StreamUtils里面
public static int copy(InputStream in, OutputStream out) throws IOException {
Assert.notNull(in, "No InputStream specified");
Assert.notNull(out, "No OutputStream specified");
int byteCount = 0;
int bytesRead;
for(byte[] buffer = new byte[4096]; (bytesRead = in.read(buffer)) != -1; byteCount += bytesRead) {
out.write(buffer, 0, bytesRead);
}
out.flush();
return byteCount;
}
这里面InputStream是ChannelInputStream,就是字节流,尽管有4096的数组.
- cn.hutool.core.io.FileUtil
往下追溯,一般会走到系统的native的copy里面,系统的函数一般是有缓冲区的
private static native void CopyFileEx0(long existingAddress, long newAddress,
int flags, long addressToPollForCancel) throws WindowsException;
- org.apache.commons.io.FileUtil
与上一个一样,也是走的系统的函数.
private static native void CopyFileEx0(long existingAddress, long newAddress,
int flags, long addressToPollForCancel) throws WindowsException;
那么很明显得出结论:FileCopyUtils的效率不高.
3.系统的函数
系统的cp都是调用系统的copy函数
剪切 会调用move函数 或者rename函数.
值得注意的是:File.renameTo
底层就是系统的rename函数.rename函数可能会直接修改名字/位置.那么在相同磁盘的剪切非常快速,底层使用的就是rename函数.只是修改目录和名字而已,但是不同磁盘的却是需要copy移动,所以速度就慢了下来.