Java IO學習筆記一:為什么帶Buffer的比不帶Buffer的快


作者:Grey

原文地址:Java IO學習筆記一:為什么帶Buffer的比不帶Buffer的快

Java中為什么BufferedReader,BufferedWriter要比FileReader 和 FileWriter高效?

問題來自於:https://www.zhihu.com/question/29351698

其中R大的一個回答:

image

我們可以通過實驗來說明這個問題:

環境: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 

其中,

strace常用來跟蹤進程執行時的系統調用和所接收的信號。

lsof用於查看你進程開打的文件

pmap用於報告進程的內存映射關系

tcpdump是Linux下的抓包工具

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

這個腳本主要的作用是將OSFileIO這個Java程序執行過程中所涉及到的系統調用記錄下來。

賦予mysh.sh執行權限

chmod +x /data/io/mysh.sh

先監控帶bufferwriter和不帶bufferwriter的寫效率,

不帶bufferwriter效率,在控制台執行:

./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

重新執行,

使用帶bufferwriter的方式來重新執行mysh.sh腳本,先停止之前執行的腳本(防止生成文件太多),然后執行

./mysh.sh 1

在另外一個控制台,進入/data/io目錄,繼續監控out.txt的增長

cd /data/io
ll -h

可以非常直觀的感受到out.txt的增長速度相比之前不帶buffer的增長速度更快。

-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和未使用bufferwriter在執行的時候,系統調用的次數。

重新執行

./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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM