告别传统读写!RandomAccessFile让你的Java程序快人一步

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

Java的文件处理工具中,RandomAccessFile是一个独特而强大的存在。与其他顺序读写流不同,它允许我们在文件中任意位置进行读写操作,就像操作数组一样自由,程序可以直接跳转到文件的任意地方来读写数据.

这种特性使其在特定场景下成为不可替代的解决方案。

02 基本概念

RandomAccessFile主要完成随机读取的功能,可以读取指定位置的内容。不属于InputStreamOutputStream类系的。继承关系如下:

2.1 说明

RandomAccessFile是一个完全独立的类,完全脱离了InputStreamOutputStream,取而代之的事DataInputDataOutput。支持读取和写入随机访问文件。随机访问文件的行为类似于存储在文件系统中的大字节数组。有一种称为文件指针的游标或索引到隐含数组中; 输入操作从文件指针开始读取字节,并将文件指针推进到读取的字节之后。

如果随机访问文件是在读/ 写模式下创建的,则输出操作也可用; 输出操作从文件指针开始写入字节,并使文件指针前进到写入的字节之后。写入超过隐含数组的当前端的输出操作会导致数组被扩展。文件指针可由getFilePointer方法读取并由seek方法设置。

通常,对于此类中的所有读取例程,如果在读取所需的字节数之前达到文件结束,则会引发EOFException (一种IOException )。如果由于文件结束以外的任何原因无法读取任何字节,则会引发除EOFException以外的IOException。特别是,如果流已关闭,则可能引发IOException

2.2 基本方法

构造方法

arduino 复制代码
// 文件路劲和打开方式
public RandomAccessFile(String name, String mode)
​
// 文件和打开方式
public RandomAccessFile(File file, String mode)

mode 模式选择

  • "r":只读模式(不可写),其他模式会抛出异常
  • "rw":读写模式(文件不存在会自动创建)
  • "rws":同步读写模式,实时写入内容+元数据,并会自动持久化
  • "rwd":同步读写模式,实时写入内容(元数据延迟写入),只有再调用close()getFD().getsync()才会持久化

03 使用场景

  • 大文件局部修改

    当需要修改大型文件中间某部分内容时(如修改二进制文件特定偏移量数据),无需重写整个文件。

  • 日志追加写入

    在日志系统中高效追加内容,避免每次打开关闭文件。

  • 多线程断点续传

    实现下载工具时,不同线程可同时写入文件的不同位置。

  • 内存映射

    更加快速的处理大文件

04 实战代码案例

4.1 修改文件指定位置的内容

我是一名程序员

我们需要将内容改成:"我是一名没有头发的程序员"

因为RandomAccessFile的数据写属于覆盖写,所以我们只要找到"我是一名"的位置即可,一个汉字占3个字节,所以位置应该是第3*4=12个字节的地方。

java 复制代码
@Test
void test01() throws Exception {
    try (RandomAccessFile raf = new RandomAccessFile("src/main/file/data.txt", "rw")) {
        System.out.println(raf.length());
        raf.seek(12);
        raf.write("没有头发的程序员".getBytes(StandardCharsets.UTF_8));
    }
}

4.2 高效的日志追加

日志的追加就很简单了,直接获取所有内容的字节数,并定位到该位置,直接写就可以了。

java 复制代码
@Test
void test02() throws Exception {
    try (RandomAccessFile raf = new RandomAccessFile("src/main/file/data.txt", "rw")) {
        raf.seek(raf.length());
        String log = "\r\n" + DateUtils.format(new Date()) + " 新日志追加";
        raf.write(log.getBytes(StandardCharsets.UTF_8));
    }
}

日志追加后指针会自动后移,也就是只要打开一次文件,后面可以一直调用write()方法写,避免文件的重复打开。

4.3 断点续传

断点续传我们之前讨论过,我们用RandomAccessFile精确的控制断点续传。

ini 复制代码
@Test
void test03() throws Exception {
    try (FileInputStream inputStream = new FileInputStream(Path.of("src/main/file/upload/10最大图片.jpg").toFile());
         RandomAccessFile raf = new RandomAccessFile("src/main/file/temp/part.jpg", "rw")) {
        byte[] bytes = new byte[1024];
        int len;
        int count = 0;
        while ((len = inputStream.read(bytes)) != -1) {
            raf.write(bytes, 0, len);
            count++;
            if (count >= 2) {
                // 写入2M后抛出异常模拟服务器异常
                throw new RuntimeException("系统崩溃了");
            }
        }
    }
}

代码内容是将src/main/file/upload/10最大图片.jpg以每次1M的缓存写入到src/main/file/temp/part.jpg, 并模拟系统异常,刚好传到2M的地方崩溃。

我们可以看到此时文件已经生成,并且文件无法打开。

我们继续模拟重新上传,看看最终的文件完整不完整:

ini 复制代码
@Test
void test04() throws Exception {
    try (RandomAccessFile sraf = new RandomAccessFile("src/main/file/upload/10最大图片.jpg", "r");
         RandomAccessFile raf = new RandomAccessFile("src/main/file/temp/part.jpg", "rw")) {
        long length = raf.length();
        System.out.println("已经上传的文件大小:" + length);
        sraf.seek(length);
        raf.seek(length);
        byte[] bytes = new byte[1024];
        int len;
        while ((len = sraf.read(bytes)) != -1) {
            raf.write(bytes, 0, len);
        }
        System.out.println("文件上传完毕......");
    }
}

从结果可以看出:文件写入的起始位置为2048的字节处,最终成功展示完整图片。

4.4 内存映射

RandomAccessFile可以通过MappedByteBuffer通过内存映射读取文件,速度更快。Kafka就使用了内存映射。我们来对比一下普通的流读写10M的数据和内存映射的读写速度对比。

ini 复制代码
// 普通的读写
StopWatch watch = new StopWatch();
watch.start();
try (FileInputStream fileInputStream = new FileInputStream(Path.of("src/main/file/upload/10最大图片.jpg").toFile());
     FileOutputStream outputStream = new FileOutputStream(Path.of("src/main/file/temp/part.jpg").toFile())) {
    byte[] buffer = new byte[1024];
    int len = 0;
    while ((len = fileInputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, len);
    }
}
watch.stop();
System.out.println(watch.getTotalTimeMillis());
​
// 内存映射
StopWatch watch2 = new StopWatch();
watch2.start();
try (RandomAccessFile sraf = new RandomAccessFile("src/main/file/upload/10最大图片.jpg", "r");
     RandomAccessFile raf = new RandomAccessFile("src/main/file/temp/part1.jpg", "rw")) {
    long length = sraf.length();
    System.out.println("length:" + length);
    MappedByteBuffer sBuffer  = sraf.getChannel().map(MapMode.READ_ONLY, 0, length);
    MappedByteBuffer mBuffer  = raf.getChannel().map(MapMode.READ_WRITE, 0, length);
​
    for (int i = 0; i < length; i += 1024) {
        byte b = sBuffer.get(i);
        mBuffer.put(i, b);
    }
}
watch2.stop();
System.out.println(watch2.getTotalTimeMillis());

从运行结果来看,整整快了4倍。

05 小结

RandomAccessFile为Java开发者提供了底层文件控制能力,特别适合需要随机定位修改的场景。当与内存映射技术结合时,能够实现超大规模文件的高效处理,性能提升可达4倍以上。

相关推荐
老任与码3 分钟前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
小兵张健3 分钟前
武汉拿下 23k offer 经历
java·面试·ai编程
FreeBuf_13 分钟前
Apache组件遭大规模攻击:Tomcat与Camel高危RCE漏洞引发数千次利用尝试
java·tomcat·apache
无妄-202424 分钟前
软件架构升级中的“隐形地雷”:版本选型与依赖链风险
java·服务器·网络·经验分享
qqxhb28 分钟前
零基础数据结构与算法——第四章:基础算法-排序(上)
java·数据结构·算法·冒泡·插入·选择
华子w90892585934 分钟前
基于 SpringBoot+VueJS 的农产品研究报告管理系统设计与实现
vue.js·spring boot·后端
皮皮林5511 小时前
90 后程序员辞职搞灰产,不到一年获利超 700 万,结局很刑!
程序员
猴哥源码1 小时前
基于Java+SpringBoot的在线小说阅读平台
java·spring boot
lingRJ7771 小时前
从混沌到掌控:基于OpenTelemetry与Prometheus构建分布式调用链监控告警体系
java·springboot·prometheus·backend·opentelemetry·jaeger·microservices
星辰离彬1 小时前
Java 与 MySQL 性能优化:Java应用中MySQL慢SQL诊断与优化实战
java·后端·sql·mysql·性能优化