作者:Grey
原文地址:Java IO学习笔记一:为什么带Buffer的比不带Buffer的快
Java中为什么BufferedReader,BufferedWriter要比FileReader 和 FileWriter高效?
问题来自于:https://www.zhihu.com/question/29351698
其中R大的一个回答:
现在我们可以通过实验来说明这个问题:
环境:CentOS 7, jdk1.8
首先,写一个不带buffer的代码
static byte[] data = "123456789\n".getBytes(); static String path = "/data/io/out.txt"; public static void testBasicFileIO() throws Exception { File file = new File(path); FileOutputStream out = new FileOutputStream(file); while (true) { out.write(data); } }
同时,我们写一个带buffer的代码
public static void testBufferedFileIO() throws Exception { File file = new File(path); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); while (true) { out.write(data); } }
通过main函数的args参数来控制执行哪个方法,完整代码为:
import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; public class OSFileIO { static byte[] data = "123456789\n".getBytes(); static String path = "/data/io/out.txt"; public static void main(String[] args) throws Exception { switch (args[0]) { case "0": testBasicFileIO(); break; case "1": testBufferedFileIO(); break; default: break; } } public static void testBasicFileIO() throws Exception { File file = new File(path); FileOutputStream out = new FileOutputStream(file); while (true) { out.write(data); } } public static void testBufferedFileIO() throws Exception { File file = new File(path); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); while (true) { out.write(data); } } }
在Linux(先安装好jdk1.8)中,准备好目录:
mkdir -p /data/io
安装必要工具
yum install -y strace lsof pmap tcpdump
将OSFileIO.java这个类上传到/data/io目录下,在/data/io目录下,新建一个mysh.sh的脚本,脚本内容如下:
rm -rf *out* /usr/local/jdk/bin/javac OSFileIO.java strace -ff -o out /usr/local/jdk/bin/java OSFileIO $1
赋予mysh.sh执行权限
chmod +x /data/io/mysh.sh
先监控带buffer的writer和不带buffer的writer的写效率,
不带buffer的writer效率,在控制台执行:
./mysh.sh 0
打开另外一个控制台,进入/data/io目录,监控生成out文件大小的速度,不断执行
ll -h
可以看到out.txt的增长速度
-rw-r--r--. 1 root root 2.1M Jun 10 19:50 out.txt ... -rw-r--r--. 1 root root 5.3M Jun 10 19:51 out.txt
重新执行,使用带buffer的writer
./mysh.sh 1
在另外一个控制台,进入/data/io目录,继续监控out.txt的增长
cd /data/io ll -h
可以看到out.txt的增长速度明显更快
-rw-r--r--. 1 root root 290M Jun 10 19:54 out.txt .... -rw-r--r--. 1 root root 768M Jun 10 19:54 out.txt .... -rw-r--r--. 1 root root 1.4G Jun 10 19:55 out.txt
这个是表现,我们再观察一下使用buffer和未使用buffer的writer在执行的时候,系统调用的次数。
重新执行
./mysh.sh 0
执行大约10秒后,停止执行
由于mysh.sh中使用了strace, 可以用于跟踪和分析进程执行时中系统调用和耗时以及占用cpu的比例
查看生成的out文件列表:
[root@io io]# ll total 60708 -rwxr-xr-x. 1 root root 106 Jun 10 19:25 mysh.sh -rw-r--r--. 1 root root 3981 Jun 10 20:08 OSFileIO.class -rw-r--r--. 1 root root 4587 Jun 10 19:29 OSFileIO.java -rw-r--r--. 1 root root 9379 Jun 10 20:10 out.6916 -rw-r--r--. 1 root root 50363725 Jun 10 20:10 out.6917 -rw-r--r--. 1 root root 1027 Jun 10 20:10 out.6918 -rw-r--r--. 1 root root 885 Jun 10 20:10 out.6919 -rw-r--r--. 1 root root 850 Jun 10 20:10 out.6920 -rw-r--r--. 1 root root 948 Jun 10 20:10 out.6921 -rw-r--r--. 1 root root 885 Jun 10 20:10 out.6922 -rw-r--r--. 1 root root 885 Jun 10 20:10 out.6923 -rw-r--r--. 1 root root 850 Jun 10 20:10 out.6924 -rw-r--r--. 1 root root 1134 Jun 10 20:10 out.6925 -rw-r--r--. 1 root root 26835 Jun 10 20:10 out.6926 -rw-r--r--. 1 root root 1343 Jun 10 20:10 out.6927 -rw-r--r--. 1 root root 1210 Jun 10 20:10 out.6928 -rw-r--r--. 1 root root 2324 Jun 10 20:10 out.6929 -rw-r--r--. 1 root root 9954 Jun 10 20:10 out.6930 -rw-r--r--. 1 root root 9792 Jun 10 20:10 out.6931 -rw-r--r--. 1 root root 9477 Jun 10 20:10 out.6932 -rw-r--r--. 1 root root 8295 Jun 10 20:10 out.6933 -rw-r--r--. 1 root root 1190 Jun 10 20:10 out.6934 -rw-r--r--. 1 root root 485668 Jun 10 20:10 out.6935 -rw-r--r--. 1 root root 2008 Jun 10 20:10 out.7023 -rw-r--r--. 1 root root 11152490 Jun 10 20:10 out.txt
可以看到
-rw-r--r--. 1 root root 50363725 Jun 10 20:10 out.6917
是主线程生成的系统调用,查看这个文件,可以看到,系统调用write的次数很多
write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10 write(4, "123456789\n", 10) = 10
切换成带buffer的执行,大约执行10秒
./mysh.sh 1
同样可以通过ll查看
[root@io io]# ll total 388808 -rwxr-xr-x. 1 root root 106 Jun 10 19:25 mysh.sh -rw-r--r--. 1 root root 3981 Jun 10 20:17 OSFileIO.class -rw-r--r--. 1 root root 4587 Jun 10 19:29 OSFileIO.java -rw-r--r--. 1 root root 9526 Jun 10 20:18 out.7053 -rw-r--r--. 1 root root 3262847 Jun 10 20:18 out.7054 -rw-r--r--. 1 root root 1076 Jun 10 20:18 out.7055 -rw-r--r--. 1 root root 885 Jun 10 20:18 out.7056 -rw-r--r--. 1 root root 885 Jun 10 20:18 out.7057 -rw-r--r--. 1 root root 948 Jun 10 20:18 out.7058 -rw-r--r--. 1 root root 983 Jun 10 20:18 out.7059 -rw-r--r--. 1 root root 948 Jun 10 20:18 out.7060 -rw-r--r--. 1 root root 885 Jun 10 20:18 out.7061 -rw-r--r--. 1 root root 1099 Jun 10 20:18 out.7062 -rw-r--r--. 1 root root 3812 Jun 10 20:18 out.7063 -rw-r--r--. 1 root root 1259 Jun 10 20:18 out.7064 -rw-r--r--. 1 root root 1245 Jun 10 20:18 out.7065 -rw-r--r--. 1 root root 2337 Jun 10 20:18 out.7066 -rw-r--r--. 1 root root 6415 Jun 10 20:18 out.7067 -rw-r--r--. 1 root root 5486 Jun 10 20:18 out.7068 -rw-r--r--. 1 root root 6347 Jun 10 20:18 out.7069 -rw-r--r--. 1 root root 4972 Jun 10 20:18 out.7070 -rw-r--r--. 1 root root 1008 Jun 10 20:18 out.7071 -rw-r--r--. 1 root root 25438 Jun 10 20:18 out.7072 -rw-r--r--. 1 root root 2071 Jun 10 20:18 out.7073 -rw-r--r--. 1 root root 394725240 Jun 10 20:18 out.txt
其中
-rw-r--r--. 1 root root 3262847 Jun 10 20:18 out.7054
为主线程的系统调用,打开这个文件可以看到
write(4, "123456789\n123456789\n123456789\n12"..., 8190) = 8190 write(4, "123456789\n123456789\n123456789\n12"..., 8190) = 8190 write(4, "123456789\n123456789\n123456789\n12"..., 8190) = 8190 write(4, "123456789\n123456789\n123456789\n12"..., 8190) = 8190 write(4, "123456789\n123456789\n123456789\n12"..., 8190) = 8190 write(4, "123456789\n123456789\n123456789\n12"..., 8190) = 8190
不是每次写都调用系统的write,而是凑齐8190后再调用一次系统的write,所以速度更快。
源码:Github