JAVA IO专题四:java顺序IO原理以及对应的应用场景

其他相关专题

JAVA IO专题一:java InputStream和OutputStream读取文件并通过socket发送,到底涉及几次拷贝
JAVA IO专题二:java NIO读取文件并通过socket发送,最少拷贝了几次?堆外内存和所谓的零拷贝到底是什么关系
JAVA IO专题三:java的内存映射和应用场景
JAVA IO专题四:java顺序IO原理以及对应的应用场景

顺序I/O和随机I/O基本概念

本次 I/O 给出的初始扇区地址和上一次 I/O 的结束扇区地址是完全连续或者相隔不多的,则算作顺序 I/O。反之,如果相差很大,则算作一次随机 I/O

硬件层面理解一、机械硬盘

影响机械硬盘的性能因素主要由寻道时间、旋转延迟和数据传输时间三部分构成。

  1. 寻道时间

指将读写磁头移动至正确的磁道上所需要的时间。寻道时间越短,I/O操作越快,目前磁盘的平均寻道时间一般在3-15ms。

  1. 旋转延迟

指盘片旋转将请求数据所在的扇区移动到读写磁盘下方所需要的时间。旋转延迟取决于磁盘转速,通常用磁盘旋转一周所需时间> 的1/2表示。比如:7200rpm的磁盘平均旋转延迟大约为60*1000/7200/2 = 4.17ms,而转速为15000rpm的磁盘其平均旋转延迟为2ms。

  1. 数据传输时间

指完成传输所请求的数据所需要的时间,它取决于数据传输率,其值等于数据大小除以数据传输率。目前IDE/ATA能达到
133MB/s,SATA II可达到300MB/s的接口数据传输率,数据传输时间通常远小于前两部分消耗时间。简单计算时可忽略。

机械硬盘的顺序写性能之所以很好,主要是因为磁头移动到正确的磁道上需要时间,顺序读写时,磁头基本不需要移动。

硬件层面理解二、固态硬盘

固态硬盘是一块随机寻址芯片,它不存在寻道时间和旋转延迟。但是固态的每次数据更新并不是直接操作原来的位置,而是在一块新的位置写入数据,再将旧的位置删除,长此以往则会产生很多磁盘碎片,而固态硬盘实现了类似jvm gc一样的垃圾回收器,随机写越多,垃圾越多,垃圾回收消耗越大,因此顺序写相对而言效率就比较高。

顺序IO在操作系统层面的表现

操作系统对磁盘的读操作做了优化,即对磁盘数据做了缓存(linux的Page Cache和Buffer Cache),磁盘Cache有个预读功能,预读的具体过程是:

对于每个文件的第一个读请求,系统读入所请求的页面并读入紧随其后的少数几个页面,这时的预读称为同步预读。对于第二次读请求,如果所读页面不在Cache中,即不在前次预读的页中,则表明文件访问不是顺序访问,系统继续采用同步预读;如果所读页面在Cache中,则表明前次预读命中,操作系统把预读页的大小扩大一倍,此时预读过程是异步的。

因此如果是顺序读,则能够更好地利用异步预读,提高IO的响应速度。

java中的随机IO和顺序IO

  • 随机IO
    java中常见的apichannel.read/write,outputStream/inputStream,都是随机IO,其工作模式都是先分配内存,往内存中写数据,将内存丢给操作系统去读写,没有显式地指定读写的位置,操作系统会随机分配写入的位置。
  • 顺序IO
    java中可以利用MappedByteBuffer 实现顺序IO,与随机IO的区别在于,事先分配一段连续的文件空间,每次在这个空间中写完后,记录最后的偏移量,下次写的时候从该位置开始继续写,通过 MappedByteBuffer 提供的api,指定写入的位置,从而实现顺序IO。

示例代码,顺序写:

public static int write(String path) throws IOException {
    //要写入的数据
        List lines = Lists.newArrayList("第一行\n", "第二行\n", "第三行\n");
    //打开文件通道,并赋予读写权限
        FileChannel targetFileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.WRITE, 
        StandardOpenOption.READ);
        //映射文件buffer
        MappedByteBuffer map = targetFileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 64);
        for (String line : lines) {
            byte[] bytes = line.getBytes(StandardCharsets.UTF_8);
            //不需要指定位置,默认会写在文件尾部并更新position,当然也可以通过map.put(bytes, position, len)来指定写入的位置
            map.put(bytes);
        }
        //返回偏移量
        return map.position();
}

因此在代码层面,调用 inputStream.write 这类不指定具体位置的方法,会让操作系统去随机判断写入的位置,可以理解为随机写。
用MappedByteBuffer的方式实现开辟一块内存映射,并按顺序追加的方式,称为顺序写。当然如果你分配多个MappedByteBuffer并随机指定位置写入,那么就属于顺序写。因此笔者理解顺序写并不是操作系统或者某个已有的函数直接帮我们完成的,关键在于我们自己实现的方式。

顺序IO的应用场景

场景的消息队列都用到了顺序IO来提高吞吐量,如kafka、rocketMQ、QMQ,这里简单看一下QMQ对于顺序IO的应用(RocketMQ我还没看过),关于QMQ怎么实现顺序写可以看笔者另一篇文章JAVA IO专题三:java的内存映射和应用场景

参考文章

磁盘I/O那些事-美团技术团队

你可能感兴趣的:(JAVA IO专题四:java顺序IO原理以及对应的应用场景)