小文件读写,千万不要用NIO

都知道NIO在读取大文件的时候都比较快。但是在小文件的写入就不是这样了(这个例子源于使用1G的内存如何找到10G大小的文件出现频率最高的数字,后来觉得NIO读写大文件有优势,那么小文件读写也应该比较快吧!)。

下面是一个使用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();
        }

    }

    /**
     * 不采用递归是因为如果字符串过大而缓存区过小引发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

从输出的结果看,果然是在创建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

发现,耗费多出的1毫秒多应该是JMH自己本身耗费的时间。但奇怪的是测试其他普通方式读写却没有发现这种情况,为什么会这样尚不得而知。不过,不要使用NIO读取小文件肯定是正确的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,488评论 1 143
  • 转自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的猫阅读 2,223评论 0 22
  • 这两天了解了一下关于NIO方面的知识,网上关于这一块的介绍只是介绍了一下基本用法,没有系统的解释NIO与阻塞、非阻...
    Ruheng阅读 7,061评论 5 48
  • 今天凉风习习,心情愉悦适合参加任何活动,包括走亲戚还有户外活动,如果不是刻意在提醒自己,现在在游磁器口古镇,我是真...
    独善其修阅读 856评论 53 49