Java高级文件操作之随机访问文件完整教学

 更新时间:2026年06月17日 09:25:44   作者:Cache技术分享  
在 Java 中,随机访问文件(Random Access Files)允许你 非顺序地 读写文件内容,也就是说,你可以跳到文件中的任意位置读取或写入数据,而不必从头到尾顺序操作,下面我们就来看看具体的实现方法吧

在 Java 中,随机访问文件(Random Access Files)允许你 非顺序地 读写文件内容,也就是说,你可以跳到文件中的任意位置读取或写入数据,而不必从头到尾顺序操作。

这种功能通常用于:

  • 大型文件处理(只读取部分内容)
  • 日志文件更新(在文件中间写入新数据)
  • 数据库文件或二进制文件操作

核心接口:SeekableByteChannel

SeekableByteChannelNIO 提供的 可寻址通道,它扩展了 Channel I/O,并增加了当前位置概念

常用方法:

方法功能
position()获取通道当前的位置(偏移量)
position(long)设置通道当前的位置(跳到文件某个偏移量)
read(ByteBuffer)从当前通道位置读取数据到缓冲区
write(ByteBuffer)将缓冲区的数据写入当前通道位置
truncate(long)截断文件到指定长度

小贴士:通过 Path.newByteChannel()Files.newByteChannel() 可以获得 SeekableByteChannel 实例。在默认文件系统下,也可以将其 强转为 FileChannel,这样可以使用更多高级功能,如文件映射、文件锁、绝对位置读写等。

示例讲解

假设我们有一个文件 file.txt,我们希望:

  1. 读取文件开头的 12 个字节
  2. 在开头写入 "I was here!"
  3. 将文件开头的 12 个字节复制到文件末尾
  4. 再次写入 "I was here!"
import java.nio.file.*;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
import java.io.IOException;

import static java.nio.file.StandardOpenOption.*;

public class RandomAccessDemo {
    public static void main(String[] args) {
        Path file = Paths.get("file.txt");

        String s = "I was here!\n";
        byte[] data = s.getBytes();
        ByteBuffer out = ByteBuffer.wrap(data);       // 包装要写入的数据
        ByteBuffer copy = ByteBuffer.allocate(12);    // 用于存储文件开头的 12 字节

        try (FileChannel fc = FileChannel.open(file, READ, WRITE)) {

            // 1️⃣ 读取文件前 12 个字节
            int nread;
            do {
                nread = fc.read(copy);
            } while (nread != -1 && copy.hasRemaining());

            // 2️⃣ 写入 "I was here!" 到文件开头
            fc.position(0);  // 跳到文件开头
            while (out.hasRemaining()) {
                fc.write(out);
            }
            out.rewind();  // 复位缓冲区,用于后续写入

            // 3️⃣ 移动到文件末尾,并复制前 12 个字节到末尾
            long length = fc.size();
            fc.position(length); // 文件末尾
            copy.flip();         // 切换为读模式
            while (copy.hasRemaining()) {
                fc.write(copy);
            }

            // 4️⃣ 如果需要再次写入 "I was here!" 到文件末尾,需要指定out.rewind();
            while (out.hasRemaining()) {
                fc.write(out);
            }

            System.out.println("文件操作完成!");
        } catch (IOException e) {
            System.err.println("I/O 异常: " + e.getMessage());
        }
    }
}

核心讲解点

随机访问的关键是 position()

  • 可以用 fc.position(偏移量) 定位到文件任意位置
  • 后续的 read()write() 都从这个位置开始

缓冲区管理(ByteBuffer

  • 写入前用 ByteBuffer.wrap()
  • 多次写入需要 out.rewind()flip() 来复位缓冲区

FileChannel vs SeekableByteChannel

  • FileChannel 功能更强大,可实现 文件映射、锁定、绝对位置读写
  • SeekableByteChannel 简单操作即可满足随机访问需求

异常处理

  • 所有 I/O 操作都可能抛出 IOException
  • 使用 try-with-resources 自动关闭通道,保证资源释放

知识扩展

RandomAccessFile 是 Java 中一个功能强大但常被忽视的类,它允许你像访问一个大型字节数组一样随意跳转到文件的任意位置进行读写操作。与普通的顺序读写流(如 FileInputStream/FileOutputStream)不同,它支持随机访问——即可以在文件任意位置读取或写入数据,而无需从头开始。

基本读写操作

import java.io.RandomAccessFile;
import java.io.IOException;
public class RandomAccessFileDemo {
    public static void main(String[] args) {
        String filePath = "employee.dat";
        try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) {
            // 定义记录结构:ID(4字节)+ 姓名(UTF-8,最大20字节)+ 年龄(4字节)+ 工资(8字节)
            // 每条记录固定长度 = 4 + 20 + 4 + 8 = 36 字节(简化,实际UTF-8长度可变)
            // 为简化,这里用固定长度的字符串(20个字符)
            // 写入记录1
            raf.seek(0);
            raf.writeInt(1001);
            raf.writeUTF("张三");    // 实际长度不定,此处简化
            // 我们使用固定长度写入
            raf.writeInt(28);
            raf.writeDouble(5000.0);
            // 写入记录2
            raf.seek(36); // 跳到第二条记录
            raf.writeInt(1002);
            raf.writeUTF("李四");
            raf.writeInt(35);
            raf.writeDouble(6200.0);
            // 读取记录2(从开头偏移36字节)
            raf.seek(36);
            int id = raf.readInt();
            String name = raf.readUTF();
            int age = raf.readInt();
            double salary = raf.readDouble();
            System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 工资: %.2f%n", id, name, age, salary);
            // 更新记录1的工资
            raf.seek(0 + 4 + 20 + 4); // 跳过ID(4) + 姓名(20) + 年龄(4)
            raf.writeDouble(5500.0);
            // 再次读取记录1验证
            raf.seek(0);
            id = raf.readInt();
            name = raf.readUTF();
            age = raf.readInt();
            salary = raf.readDouble();
            System.out.printf("更新后: ID: %d, 姓名: %s, 年龄: %d, 工资: %.2f%n", id, name, age, salary);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:上述示例中 writeUTF 写入的是变长字符串,实际长度不固定,因此计算偏移不准确。实际应用应使用固定长度字段或额外存储长度信息。

实现固定长度记录(推荐)

import java.io.RandomAccessFile;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FixedLengthRecord {
    private static final int ID_SIZE = 4;
    private static final int NAME_SIZE = 20; // 字节数
    private static final int AGE_SIZE = 4;
    private static final int SALARY_SIZE = 8;
    private static final int RECORD_SIZE = ID_SIZE + NAME_SIZE + AGE_SIZE + SALARY_SIZE;
    // 写入记录
    public static void writeRecord(RandomAccessFile raf, int id, String name, int age, double salary) throws IOException {
        raf.writeInt(id);
        byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
        byte[] nameBuf = new byte[NAME_SIZE];
        System.arraycopy(nameBytes, 0, nameBuf, 0, Math.min(nameBytes.length, NAME_SIZE));
        raf.write(nameBuf);
        raf.writeInt(age);
        raf.writeDouble(salary);
    }
    // 读取记录
    public static void readRecord(RandomAccessFile raf, int recordNum) throws IOException {
        raf.seek((long) recordNum * RECORD_SIZE);
        int id = raf.readInt();
        byte[] nameBuf = new byte[NAME_SIZE];
        raf.readFully(nameBuf);
        String name = new String(nameBuf, StandardCharsets.UTF_8).trim();
        int age = raf.readInt();
        double salary = raf.readDouble();
        System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 工资: %.2f%n", id, name, age, salary);
    }
    public static void main(String[] args) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile("fixed.dat", "rw")) {
            // 写入三条记录
            writeRecord(raf, 1, "张三", 25, 5000);
            writeRecord(raf, 2, "李四", 30, 6000);
            writeRecord(raf, 3, "王五", 28, 5500);
            // 读取第二条记录
            readRecord(raf, 1); // 索引从0开始
            // 更新第二条记录的工资
            raf.seek(1 * RECORD_SIZE + ID_SIZE + NAME_SIZE + AGE_SIZE);
            raf.writeDouble(6800.0);
            System.out.println("更新后:");
            readRecord(raf, 1);
        }
    }
}

断点续传模拟(多线程下载位置保存)

import java.io.RandomAccessFile;
import java.io.IOException;
public class DownloadStatus {
    private static final String STATUS_FILE = "download.status";
    // 保存已下载的字节数
    public static void saveProgress(long bytes) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(STATUS_FILE, "rw")) {
            raf.setLength(0); // 清空
            raf.writeLong(bytes);
        }
    }
    // 读取已下载的字节数
    public static long loadProgress() throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(STATUS_FILE, "r")) {
            if (raf.length() < 8) {
                return 0;
            }
            return raf.readLong();
        }
    }
    public static void main(String[] args) throws IOException {
        long progress = loadProgress();
        System.out.println("上次下载进度: " + progress + " 字节");
        // 模拟下载,每10秒更新一次进度
        for (long i = progress; i < 1024 * 1024 * 10; i += 1024 * 10) {
            // 模拟下载
            saveProgress(i);
            System.out.println("当前进度: " + i);
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
        System.out.println("下载完成!");
    }
}

到此这篇关于Java高级文件操作之随机访问文件完整教学的文章就介绍到这了,更多相关Java随机访问文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JavaFx UI控件与代码间的绑定方法

    JavaFx UI控件与代码间的绑定方法

    这篇文章主要为大家详细介绍了JavaFx UI控件与代码间如何绑定,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • SpringBoot之集成Druid数据库连接池方式

    SpringBoot之集成Druid数据库连接池方式

    这篇文章主要介绍了SpringBoot之集成Druid数据库连接池方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2026-06-06
  • java中sleep方法和wait方法的五个区别

    java中sleep方法和wait方法的五个区别

    这篇文章主要介绍了java中sleep方法和wait方法的五个区别,sleep 方法和 wait 方法都是用来将线程进入休眠状态,但是又有一些区别,下面我们就一起来看看吧
    2022-05-05
  • 使用dom4j递归解析节点内还含有多个节点的xml

    使用dom4j递归解析节点内还含有多个节点的xml

    这篇文章主要介绍了使用dom4j递归解析节点内还含有多个节点的xml,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • spring使用@Async注解导致循环依赖问题异常的排查记录

    spring使用@Async注解导致循环依赖问题异常的排查记录

    这篇文章主要介绍了spring使用@Async注解导致循环依赖问题异常的排查记录,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 聊聊springboot中整合log4g2的问题

    聊聊springboot中整合log4g2的问题

    这篇文章主要介绍了springboot中整合log4g2的方法,自定义文件名需要在application.yml中配置,在config中配置log4g2.xml文件,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2021-11-11
  • Springboot上传文件与物理删除功能

    Springboot上传文件与物理删除功能

    文章介绍了在Springboot中实现文件上传和删除的功能,实现了图片的上传与删除,删除数据库记录时,同步删除了文件,结合实例代码给大家讲解的非常详细,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • Mybatis Plus 实现批量插入的示例代码

    Mybatis Plus 实现批量插入的示例代码

    本文主要介绍了Mybatis Plus 实现批量插入的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • java判断远程服务器上的文件是否存在的方法

    java判断远程服务器上的文件是否存在的方法

    java判断远程服务器上的文件是否存在的方法,需要的朋友可以参考一下
    2013-03-03
  • 优化常见的java排序算法

    优化常见的java排序算法

    这篇文章主要介绍了Java编程中快速排序算法的实现及相关算法优化,快速排序算法的最差时间复杂度为(n^2),最优时间复杂度为(n\log n),存在优化的空间,需要的朋友可以参考下
    2021-07-07

最新评论