JAVA NIO:如何在5秒内写入10G的文本数据

首先说本机的性能,采用AS SSD Benchmark进行评测,写入能力大约在422M每秒,计划连续写入文本数据,直到达到要求为止(5G数据与10G数据),测试环境如下:

环境版本
JDK1.8.0_131
操作系统Windows 10 专业版 x64
CPUInter i7-3740QM
内存16G
硬盘三星512G SSD

AS SSD Benchmark

1. FileOutputStream与BufferedWriter

原以为FileOutputStream的性能会很低,BufferedWriter会有一定的性能提升,但结果却让我大吃一惊,测试数据如下:

测试编次采用方式文件大小花费时间(秒)
1BufferedWriter4.5G10.678399057
2BufferedWriter4.5G10.808078377
3FileOutputStream4.5G9.755711962
4FileOutputStream4.5G9.457581885

BufferedWrirter竟然还稍稍慢于FileOutputStream,并且FileOutputStream的性能如此惊人,已经完全达到了硬盘的性能巅峰,这说明JAVA的IO优化还是令人非常满意的,相关代码如下:

//  FileOutputStream的写入方式类似,在此略
static void writeBuffer(File file) throws IOException {
    FileOutputStream fos = new FileOutputStream(file);
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos));
    int i = 1000000;
    while(i > 0) {
        //  word2048为字符串常量,刚好4800个字节
        writer.write(word2048);
        i --;
    }
    writer.close();
    fos.close();
}

2. ByteBuffer与直接缓冲区

几乎所有的人都推荐,nio性能极佳,那真实的性能到底怎样?

文件大小采用方式花费时间(秒)
4.5G采用直接内存8.598730553
4.5G不采用直接内存8.581370111

从上面的数据可以看出,采用ByteBuffer后,性能约有10%的提升,但令人惊讶的,采不采用直接缓冲区竟然没有差异,这与理论推测又有显著差异(具体请参见《JAVA NIO》第45页),在这里还有一个可优化的地方,如何选择ByteBuffer的大小是决定写入速度的关键,相关代码如下:

FileOutputStream fos = new FileOutputStream(file);
FileChannel fc = fos.getChannel();
//  此数字可优化
int times = 100;
//  word2048为字符串常量,刚好4800个字节
byte[] datas = word2048.getBytes();
ByteBuffer bbuf = ByteBuffer.allocate(4800 * times);
int i = 10000;
while(i > 0) {
    for(int j = 0; j < times; j++) {
        bbuf.put(datas);
    }
    bbuf.flip();
    fc.write(bbuf);
    bbuf.clear();
    i --;
}

从这里来看,跟BIO相比,NIO性能提升并不明显。

3. FileChannel与文件空洞

在nio中,FileChannel可以决定文件的写入位置,这也是能产生文件空洞的主要方法,那么这样,产生大量的文件空洞,是否能加快文件的创建速度呢?

文件大小采用方式花费时间
5.1G改变Channel Position 步进213.214593475S
11G改变Channel Position 步进225.849560829S

可以看出,写入速度主要还是受限与磁盘IO,即使少写数据,依旧不能提升速度,反而还有较大幅度的下降,相关代码如下:

FileOutputStream fos = new FileOutputStream(file);
FileChannel fc = fos.getChannel();
ByteBuffer bbuf = ByteBuffer.allocateDirect(1024);
long value = 10 * 1024 * 1024;
//  为什么不一步到位?直接将position设置10G
for(int i = 1; i < 1025; i = i * 2) {
    bbuf.put((byte)1);
    bbuf.flip();
    fc.position(i * value);
    fc.write(bbuf);
    bbuf.clear();
}
fc.close();
fos.close();

在这里需要强调的一点是,文件大小的速度不能增长太快,否则必然出现“IllegalArgumentException”错误,这也是上述代码中需要划分多次循环的原因。

4. 惊人的MappedByteBuffer

早就听说直接内存映射提升IO性能惊人,那到底有多惊人呢?请看测试数据:

文件大小采用方式花费时间(秒)
4.5G内存映射2.537650656
9G内存映射5.303423243

跟前面的最快的NIO方法相比,性能竟然提升了244%,写入速度竟然达到了1.8G每秒,这是怎么做到的?相关代码如下:

//  必须采用RandomAccessFile,并且是rw模式
RandomAccessFile acf = new RandomAccessFile(file, "rw");
FileChannel fc = acf.getChannel();
byte[] bs = word2048.getBytes();
int len = bs.length * 1000;
long offset = 0;
int i = 2000000;
while(i > 0) {
    MappedByteBuffer mbuf = fc.map(FileChannel.MapMode.READ_WRITE, offset, len );
    for(int j = 0; j < 1000; j ++) {
        mbuf.put(bs);
    }
    offset = offset + len;
    i = i - 1000;
}
fc.close();

当然,性能提升的代价也是很明显的,内存消耗至少增加了2G(直接内存,不是JAVA堆内存),而前面的方法内存消耗都很少,大多只在30M左右。

5. 其他的现象

  1. 同样的文件名,删除了再创建,速度又有10~20%的提升;
  2. 字符串转换为字节数组的速度极快,比直接写入字符串的速度更快,这也是BufferedWrirter比FileOutputStream慢的原因;

结论

直接内存映射、直接缓冲区都能提升IO写入的性能,背后的核心技术还是分页技术(请参加操作系统原理),如何组织分页的范围与写入的频次,是提升性能的关键,另外,写入对内存与CPU性能消耗都不高。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值