告别传统读写!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倍以上。

相关推荐
一只叫煤球的猫7 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9657 小时前
tcp/ip 中的多路复用
后端
bobz9658 小时前
tls ingress 简单记录
后端
皮皮林5519 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友9 小时前
什么是OpenSSL
后端·安全·程序员
bobz9659 小时前
mcp 直接操作浏览器
后端
前端小张同学11 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook12 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康12 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在13 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net