Java输出流的选择

问题

最近遇到一个场景:

程序运行过程中有多个节点对象,其中有三个节点的状态需要持久化,其余节点的状态不需要持久化,所有的节点在运行过程中的状态都会不断变化,程序运行过程中需要保证三个需要持久化节点的数据在大部分场景下崩溃后,下次重启可以读入上次程序崩溃前的状态,此外每个节点有个单独的ID。

由于是单机程序,所以使用了一个Map来存储这些数据

对于单机程序,为了简单,自然想到使用文件来进行数据的持久化,由于有现成的XML工具包使用,所以刚开始时选择了使用XML文件来持久化数据,也就是每次数据变化后,将需要持久化的三个节点的数据一同写到文件中。这样实现的方式就是简单,但是问题来了,在写文件的过程中,由于使用XML格式,所以先要将Map中的数据取出,然后new出XML格式数据的节点,最后写入到文件中。这个过程没什么问题,但是效率跟不上,当程序中需要持久化的三个节点改变状态的次数较多时,每次都将这些数据写到文件中影响了整个程序的运行进度。

看见写文件这么慢,首先想到的自然是去掉xml,直接写将节点数据写入到文件中,既然这样的话,那么三个节点有三个不同的ID,写入同一个文件就需要分成三段,如果每次都将三种数据一同写入到文件中,那么就使用一个分隔符,但是在程序运行过程中,每次只改变一个节点的数据,就要将三个节点的数据写入文件,这样显然比较浪费。所以再去掉xml格式之后,再将这三个节点的数据分别写到三个不同的文件中,并且以节点ID作为文件名用以区分三个不同的节点。接下来就要找一种更快将数据写入文件的方法了。

Java中到底哪个类是写数据是最快的呢?看到有人说MappedByteBuffer读写文件都比较快,所以就想使用MappedByteBuffer试试,于是写了一段测试代码,顺便熟悉MappedByteBuffer的接口。同时也将MappedByteBuffer的性能与FileOutputStream对比,代码如下:

public static void main(String[] args) throws FileNotFoundException, IOException {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < 1000; i++) {
            sb.append('a');
        }
        String s = sb.toString();
        long astart = System.currentTimeMillis();
        RandomAccessFile file = new RandomAccessFile("D:\\ran", "rwd");
        file.write(s.getBytes());
        file.close();
        long start = System.currentTimeMillis();
        RandomAccessFile raf = new RandomAccessFile("D:\\ran_map", "rwd");
        MappedByteBuffer mbb = raf.getChannel().map(MapMode.READ_WRITE, 0, s.getBytes().length);
        mbb.put(s.getBytes());
        raf.close();
        long end = System.currentTimeMillis();
        OutputStream out = new FileOutputStream("D:\\filout");
        out.write(s.getBytes());
        out.close();
        long endd = System.currentTimeMillis();
        System.out.println((start-astart) + ":" + (end-start) + ":" + (endd-end));

输出结果为:

五次执行结果为:

34:10:0
13:21:0
29:16:1
32:17:0
21:17:0

没想到,FileOutputStream写文件竟然比MappedByteBuffer和RandomAccessFile快这么多。照这样看,网上的那些说法都不科学呀。

进入到FileOutputStream的write方法:

public void write(byte b[]) throws IOException {
    writeBytes(b, 0, b.length);
}

private native void writeBytes(byte b[], int off, int len) throws IOException;

FileOutputStream通过write(byte[] b)方法直接调用了native方法writeBytes(byte b[], int off, int len)将数据写入到文件中。

而MappedByteBuffer.put(byte[] src)方法依次调用了:

public final ByteBuffer put(byte[] src) {
    return put(src, 0, src.length);
    }

public ByteBuffer put(byte[] src, int offset, int length) {
    if ((length << 0) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD) {
        checkBounds(offset, length, src.length);
        int pos = position();
        int lim = limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);
        if (length > rem)
        throw new BufferOverflowException();
        Bits.copyFromArray(src, arrayBaseOffset, offset << 0,ix(pos), length << 0);
        position(pos + length);
        } else {
            super.put(src, offset, length);
        }
    return this;
}

这两个方法调用都是通过DirectByteBuffer类的对象调用的,DirectByteBuffer是直接内存的方式,也就是直接使用内存,而没有通过JVM的堆,方法中先检查是否越界,然后获取到当前的position,再将数据进行拷贝到直接内存。

为什么会比较耗时呢?这段直接内存的创建和销毁通常比JVM对耗时一些,它不熟垃圾手机的控制,因为它在JVM外部。

JDK中的FileChannel.map()方法的注释最后一段是这样的:

For most operating systems, mapping a file into memory is more expensive than reading or writing a few tens of kilobytes of data via the usual
read and write methods. From the standpoint of performance it is generally only worth mapping relatively large files into memory.

MappedByteBuffer类将一块内存与文件的某个部分进行映射,然后你就可以通过这个MappedByteBuffer对该文件的这部分进行读写操作了。这样不用加载整个文件,也可以进行读写。所以MappedByteBuffer使用场景是针对大文件,并且有连续的读写操作的场景。

可是对于本文刚开始的那个场景,单纯的写文件,FileOutputStream不比MappedByteBuffer慢。缓冲写的本质是使用一块内存区来保存将要写出到磁盘的数据,然后等待缓冲区满后将缓冲区中的数据一起写到文件中,假设缓冲区大小为n个字节,那么相对于对于每个字节都调用一次IO,使用缓冲区时就对n个字节调用一次IO,所以这样就提升了写的效率了。而对于文章中的这个场景,因为是将一个字节数组的数据一次性写入到文件中,所以就算是调用FIleOutputStream.write(byte[] b)方法,也是通过一次调用native的write方法写入文件的,在此种场景下无需使用缓冲。

对于测试代码中RandomAccessFile写数据时调用的方法为:

public void write(byte b[]) throws IOException {
    writeBytes(b, 0, b.length);
}

private native void writeBytes(byte b[], int off, int len) throws IOException;

RandomAccessFile.writeBytes(byte b[], int off, int len)方法与FileOutputStream.writeBytes(byte b[], int off, int len)方法都是调用native方法写文件,为什么效率就不一样呢?难道RandomAccessFile写文件时还有额外的操作?留个坑,以后再来看。

Reference

花1K内存实现高效I/O的RandomAccessFile类

DirectByteBuffer更快吗?

java之HeapByteBuffer&DirectByteBuffer以及回收DirectByteBuffer

Java输出流的选择,布布扣,bubuko.com

时间: 2024-11-18 07:30:43

Java输出流的选择的相关文章

java 输出流

//输出流    @Test    public void testOutStream() throws Exception{        OutputStream  out =new FileOutputStream("abc.txt");        String content="hello word/nwww.baidu.com ";        byte[]contentBytes=content.getBytes();        out.wri

java输出流实现文件下载

//导出Excel try { HSSFWorkbook wb = carService.export(list); //调用service方法~! response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-disposition", "attachment;filename=carsList.xls"); OutputStream ouput

【Java SE】如何用Java实现直接选择排序

摘要:直接选择排序属于选择排序的一种,但是它的排序算法比冒泡排序的速度要快一些,由于它的算法比较简单,所以也比较适合初学者学习掌握. 适宜人群:有一定Java SE基础,明白Java的数据类型,数组的定义.初始化以及常用数组的方法,还有Java的循环操作. 前期准备:最好有一个开发工具比如说:eclipse或者myeclipse都可以,当然你使用DOS系统进行编译运行都可以,只不过改bug会麻烦一点. 排序原理:直接选择排序的原理是将指定排序位置与其他数组元分别对比,如果满足条件就交换位置的值,

java线程练习 选择城市

随便选择两个城市作为预选旅游目标.实现两个独立的线程分别显示10次城市名, 每次显示后休眠一段随机时间(1000ms以内),哪个先显示完毕,就决定去哪个城市.分别用Runnable接口和Thread类实现. package com.xiancheng; import java.util.Random; public class Test6 extends Thread { @Override public void run() { test(); } public void test() { f

几种Java库的选择策略

为公司做了小任务,需要用到Java Json库,Json库我几个月之前就用过,不过那时候是跟着项目来的,延续了项目的使用习惯直接用了jackson Json,而这次我觉得好好比较一下几个常见的Json库,然后选一个最快的. 看了几篇blog,觉得其实可选的就三种,jackson, gson, json.simple.我最初用了json.simple,然后写出来了这样的函数 从URL中读取Json数据,并且在Request中添加身份信息 public static JSONObject readJ

【转】Java异常:选择Checked Exception还是Unchecked Exception?

Java包含两种异常:checked异常和unchecked异常.C#只有unchecked异常.checked和unchecked异常之间的区别是: Checked异常必须被显式地捕获或者传递,如Basic try-catch-finally Exception Handling一文中所说.而unchecked异常则可以不必捕获或抛出. Checked异常继承java.lang.Exception类.Unchecked异常继承自java.lang.RuntimeException类. 有许多支

Java:如何选择最为合适的Web开发框架

摘自:http://www.shangxueba.com/jingyan/87054.html 如何选择Web开发框架 开发框架的选择,始终是个仁者见仁.智者见智的事情.尤其是Web层的开发框架,数量非常多,而且各有特色,如:Struts.WebWork.Spring MVC.Tapestry.JSF.WebPage3.0……等等. 下面先来看看为什么要使用Web开发框架 一 使用框架的必然性 框架,即framework.其实就是某种应用的半成品,把不同应用程序中有共性的一些东西抽取出来,做成一

Java编程基础-选择和循环语句

一.选择结构语句 选择结构:也被称为分支结构.选择结构有特定的语法规则,代码要执行具体的逻辑运算进行判断,逻辑运算的结果有两个,所以产生选择,按照不同的选择执行不同的代码. Java语言提供了两种选择结构语句:if语句和switch语句 1.if语句 if语句有三种语法格式. (1).if语句第一种语法格式(适合一种判断) if(条件表达式){ 语句体; } 执行流程:判断条件表达式的结果,当为为true时,{}中的执行语句体才会执行,否则不执行语句体. 注意: 条件表达式的结果必须是布尔类型:

Java 输出流中的flush方法

转自:http://blog.csdn.net/jiyangsb/article/details/50984440 java中的IO流中的输出流一般都有flush这个操作,这个操作的作用是强制将缓存中的输出流(字节流,字符流等)强制输出. 为什么会有这么个方法啊? 因为输出流在进行输出时,比如像某个文件中写入内容,其实是先将输出流写入到缓冲区,当缓冲区写满后才将缓冲区的内容输出到文件中.但是当主机完成输出流的输出后,有可能缓冲区这个时候还没有被填满,这样的话,就会一直等待主机发送内容,这时候,就