怎么利用 RandomAccessFile 的文件空洞特性实现多线程断点续传的快速预分配

老宇同学_3186

老宇同学_3186

2026-04-21

924人浏览

原创

randomaccessfile写空洞文件不真正写磁盘是因为seek后write仅标记元数据为稀疏文件,实际空间按需分配;预分配需单次原子完成,多线程须各自独立raf实例并用"rws"模式,恢复依赖magic标记而非文件长度。

怎么利用 randomaccessfile 的文件空洞特性实现多线程断点续传的快速预分配

RandomAccessFile 写空洞文件时为什么不会真正写磁盘

因为 RandomAccessFile 调用 seek() 跳到远端再 write() 一个字节,操作系统(尤其 ext4/xfs/NTFS)会标记中间区域为“未分配”,只记录元数据——这就是稀疏文件(sparse file)。实际磁盘空间等到真正写入数据才分配。

这对断点续传预分配极有用:你不需要循环写 0 填满整个文件,seek() 到末尾 + write(0) 一行就搞定几十 GB 的“占位”。但注意:必须确保后续写入是随机位置落盘,且不触发自动填充(比如某些 JVM 或文件系统在 close 前强制补零)。

  • Linux 下用 ls -lsh 查看,第一列是实际占用块数,第二列才是逻辑大小;若远小于后者,说明空洞生效
  • Windows 上需用 fsutil sparse setflag 显式开启稀疏支持,否则 RandomAccessFile 可能静默填零
  • JVM 本身不干预,但部分 JDK 版本(如早期 OpenJDK 8u)在 close() 时对未写区域做隐式 zero-fill,建议用 jdk-11.0.2+ 或手动验证

多线程写入前必须先完成预分配,否则会竞争导致文件损坏

多个线程直接 RandomAccessFile 打开同一文件并各自 seek()+write(),如果文件还没预分配到目标长度,某线程写到最后触发扩容,可能引发文件系统重映射,其他线程的 seek() 地址瞬间失效或覆盖错位。

所以预分配必须是原子、单次、线程安全的前置动作。不要让每个线程自己判断“是否要分配”。

  • 用一个独立线程或主线程调用 raf.seek(fileSize - 1); raf.write(new byte[]{0}); 完成预分配
  • 预分配后,所有工作线程都以 "rws" 模式打开(而非 "rw"),确保每次 write() 立即落盘,避免缓存不一致
  • 绝对不要在多线程中用 raf.length() 判断当前大小来决定写位置——它可能返回旧值,尤其在扩容瞬间

分块写入时 seek() + write() 的顺序和异常处理很关键

每个线程负责一个固定区间(如 [start, end)),必须严格按“先 seek(start),再 write(buf, offset, len)”执行。中间不能穿插其他线程的 seek(),否则指针偏移错乱。

Yarn - Control and Access Threads.com via the CLI
Yarn - Control and Access Threads.com via the CLI

通过 yarn-threads-cli 与 Threads(Meta)交互。当用户想要阅读首页动态、点赞、收藏的帖子或特定帖子时使用;查看...

下载

RandomAccessFile 不是线程安全的,即使文件已预分配,多个线程共用同一个 raf 实例必然出问题。必须每个线程持有一个独立 raf 实例(相同文件路径、相同 "rws" 模式)。

  • 每次 write() 后检查返回值,必须等于预期长度,否则抛异常终止——write() 返回 -1 表示失败,但不会自动重试
  • 写入失败时,不能简单跳过该块;要记录失败偏移,并在恢复时重新调度,否则产生空洞(非预期的、不可读的 0 区域)
  • 避免用 writeFully():它内部循环调用 write(),但异常中断后无法知道已写多少,难以精确恢复

断点续传恢复时怎么安全判断哪些块已完成

不能依赖文件长度(raf.length()),因为预分配后它恒等于目标大小;也不能靠校验和——太慢。实用做法是维护一个外部状态文件(如 JSON),记录每个分块的 offset 和 status: "done" | "failed" | "pending"。

更轻量的做法是:每个线程写完一块后,在对应位置额外写 4 字节 magic(如 0xDEADBEEF),恢复时用另一个 RandomAccessFile 以 "r" 模式快速扫描这些 magic 标记位。

  • magic 必须写在块数据之后(如 offset + blockLen),且长度固定,避免和业务数据混淆
  • 扫描时用 raf.seek(offset + blockLen); int flag = raf.readInt();,比读整块快两个数量级
  • 注意字节序:Java 默认大端,确保写入和读取一致;推荐用 ByteBuffer.order(ByteOrder.LITTLE_ENDIAN) 统一转小端防歧义

空洞预分配本身不保存业务语义,所有“哪里写完了”的判断,都得靠外部状态或显式标记。这点容易被忽略——以为文件长度够了就等于数据全了。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

access

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

9417

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

6582

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

5852

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1024

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

868

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1236

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2469

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19811

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1115

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 7万人学习

安装 Xdebug 并配置 phpstorm
安装 Xdebug 并配置 phpstorm

共1课时 | 178人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 20.2万人学习