小文件讀寫任洞,千萬不要用NIO

都知道NIO在讀取大文件的時候都比較快座云。但是在小文件的寫入就不是這樣了(這個例子源于使用1G的內存如何找到10G大小的文件出現頻率最高的數字,后來覺得NIO讀寫大文件有優(yōu)勢旁理,那么小文件讀寫也應該比較快吧7恪)。

下面是一個使用NIO來像文件寫入String的例子:

package nio;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOWriter {

    private FileChannel fileChannel;
    private ByteBuffer buf;

    @SuppressWarnings("resource")
    public NIOWriter(File file, int capacity) {
        try {
            TimeMonitor.start();
            fileChannel = new FileOutputStream(file).getChannel();
            TimeMonitor.end("fileChannel = new FileOutputStream(file).getChannel()");
            buf = ByteBuffer.allocate(capacity);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 不采用遞歸是因為如果字符串過大而緩存區(qū)過小引發(fā)StackOverflowException
     * 
     * @param str
     * @throws IOException
     */
    public void write(String str) throws IOException {
        TimeMonitor.start();
        int length = str.length();
        byte[] bytes = str.getBytes();
        int startPosition = 0;
        do {
            startPosition = write0(bytes, startPosition);
        } while (startPosition < length);
        TimeMonitor.end("write(String str)");
    }

    public int write0(byte[] bytes, int position) throws IOException {
        if (position >= bytes.length) {
            return position;
        }
        while (buf.hasRemaining()) {
            if (position < bytes.length) {
                buf.put(bytes[position]);
                position++;
            } else {
                break;
            }
        }
        buf.flip();
        fileChannel.write(buf);

        buf.clear();
        return position;
    }
    /**
     * 強制寫入數據。并且關閉連接
     */
    public void close() {
        try {
            fileChannel.close();
        } catch (IOException e) {

            e.printStackTrace();
        }
    }   
}

然后使用JMH與其他小文件寫入方式來作比較驻襟。按理說夺艰,使用NIO的方式每次都有緩存,速度應該會很快沉衣,但實際卻不是這樣郁副。下面做了一個比較,分別使用FileWriter豌习,buffer存谎,直接寫,以及NIO的方式:

package nio;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

public class WriterTest {

    static int writeCount = 1;
    static String inputStr = "very woman is a" + " treasure but way too often we forget"
            + " how precious they are. We get lost in daily "
            + "chores and stinky diapers, in work deadlines and dirty dishes, in daily errands and occasional breakdowns.";

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder().include(WriterTest.class.getSimpleName()).forks(1).build();
        new Runner(opt).run();

    }

    @Benchmark
    @BenchmarkMode(Mode.SingleShotTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void TestFileWriter() throws IOException {
        File file = new File("/Users/xujianxing/Desktop/fileWithWriter.txt");
        FileWriter fileWriter = new FileWriter(file);
        for (int i = 0; i < writeCount; i++) {
            fileWriter.write("JAVA TEST");
        }

    }

    @Benchmark
    @BenchmarkMode(Mode.SingleShotTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void TestBuffer() throws IOException {
        File file = new File("/Users/xujianxing/Desktop/buffer.txt");
        BufferedOutputStream buffer = new BufferedOutputStream(new FileOutputStream(file));
        for (int i = 0; i < writeCount; i++) {
            buffer.write(inputStr.getBytes());
        }
        buffer.flush();
        buffer.close();
    }

    @Benchmark
    @BenchmarkMode(Mode.SingleShotTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void TestNormal() throws IOException {
        File file = new File("/Users/xujianxing/Desktop/normal.txt");
        FileOutputStream out = new FileOutputStream(file);
        for (int i = 0; i < writeCount; i++) {
            out.write(inputStr.getBytes());

        }
        out.close();

    }

    @Benchmark
    @BenchmarkMode(Mode.SingleShotTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void TestNIO() throws IOException {
        File file = new File("/Users/xujianxing/Desktop/nio.txt");
        NIOWriter nioWriter = new NIOWriter(file, 2048);
        for (int i = 0; i < writeCount; i++) {
            nioWriter.write(inputStr);

        }
    nioWriter.close();

    }

}

然而測試的結果卻大跌眼鏡肥隆,看一看測試的結果:

Benchmark                  Mode  Cnt  Score   Error  Units
WriterTest.TestBuffer        ss       0.265          ms/op
WriterTest.TestFileWriter    ss       0.232          ms/op
WriterTest.TestNIO           ss       8.479          ms/op
WriterTest.TestNormal        ss       0.217          ms/op

NIO的測試結果是最差的既荚,結果卻花了8ms之多!而且寫次數越多栋艳,差距越大恰聘。

這到底是為什么呢?

寫了一個簡單的測試類來記錄花費的時間:

package nio;

public class TimeMonitor {
    private static long start = 0;

    private static long end = 0;

    public static void start() {

        start = System.currentTimeMillis();
        end = 0;

    }

    public static void end(String  tag) {
        end = System.currentTimeMillis();
        System.out.println("time  coast:"+tag+"---------->" + (end - start));
        end = 0;
        start = 0;
    }

}

然后在覺得可能會耗時的地方上(比如channel的獲得吸占,channel的寫入等)輸出結果是這樣的:

time  coast:fileChannel = new FileOutputStream(file).getChannel()---------->5
time  coast:write0---------->0
time  coast:fileChannel.write(buf)---------->2

從輸出的結果看晴叨,果然是在創(chuàng)建channel的時候已經寫channel的時候花費了大量的時間。但是多余的一毫秒多哪去了旬昭?

改正一下測試類篙螟,結果是這樣的:

@Benchmark
    @BenchmarkMode(Mode.SingleShotTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void TestNIO() throws IOException {
        TimeMonitor.start();
        File file = new File("/Users/xujianxing/Desktop/nio.txt");
        NIOWriter nioWriter = new NIOWriter(file, 2048);
//      TimeMonitor.end("NIOWriter nioWriter = new NIOWriter(file, 2048);");
        for (int i = 0; i < writeCount; i++) {
//          TimeMonitor.start();
            nioWriter.write(inputStr);
//          TimeMonitor.end("nioWriter.write(inputStr)");
        }
//      TimeMonitor.start();
        nioWriter.close();
        TimeMonitor.end("   nioWriter.close()");
    }

}

輸出結果是這樣的(不同的機器可能會不一樣,每次耗費的時間也不一樣):

 time  coast:   nioWriter.close()---------->6

發(fā)現问拘,耗費多出的1毫秒多應該是JMH自己本身耗費的時間遍略。但奇怪的是測試其他普通方式讀寫卻沒有發(fā)現這種情況,為什么會這樣尚不得而知骤坐。不過绪杏,不要使用NIO讀取小文件肯定是正確的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末纽绍,一起剝皮案震驚了整個濱河市蕾久,隨后出現的幾起案子,更是在濱河造成了極大的恐慌拌夏,老刑警劉巖僧著,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異障簿,居然都是意外死亡盹愚,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門站故,熙熙樓的掌柜王于貴愁眉苦臉地迎上來皆怕,“玉大人,你說我怎么就攤上這事∮冢” “怎么了憋活?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虱黄。 經常有香客問我悦即,道長,這世上最難降的妖魔是什么礁鲁? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任盐欺,我火速辦了婚禮,結果婚禮上仅醇,老公的妹妹穿的比我還像新娘冗美。我一直安慰自己,他們只是感情好析二,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布粉洼。 她就那樣靜靜地躺著,像睡著了一般叶摄。 火紅的嫁衣襯著肌膚如雪属韧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天蛤吓,我揣著相機與錄音宵喂,去河邊找鬼。 笑死会傲,一個胖子當著我的面吹牛锅棕,可吹牛的內容都是我干的。 我是一名探鬼主播淌山,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼裸燎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泼疑?” 一聲冷哼從身側響起德绿,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎退渗,沒想到半個月后移稳,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡会油,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年秒裕,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钞啸。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出体斩,到底是詐尸還是另有隱情梭稚,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布絮吵,位于F島的核電站弧烤,受9級特大地震影響,放射性物質發(fā)生泄漏蹬敲。R本人自食惡果不足惜暇昂,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伴嗡。 院中可真熱鬧虱咧,春花似錦耿芹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幽污,卻和暖如春躺彬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背麻惶。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工馍刮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窃蹋。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓卡啰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脐彩。 傳聞我的和親對象是個殘疾皇子碎乃,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現惠奸,斷路器梅誓,智...
    卡卡羅2017閱讀 134,702評論 18 139
  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java I...
    JackChen1024閱讀 7,556評論 1 143
  • 轉自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,313評論 0 22
  • 這兩天了解了一下關于NIO方面的知識佛南,網上關于這一塊的介紹只是介紹了一下基本用法梗掰,沒有系統(tǒng)的解釋NIO與阻塞、非阻...
    Ruheng閱讀 7,129評論 5 48
  • 今天涼風習習嗅回,心情愉悅適合參加任何活動及穗,包括走親戚還有戶外活動,如果不是刻意在提醒自己绵载,現在在游磁器口古鎮(zhèn)埂陆,我是真...
    獨善其修閱讀 878評論 53 49