randomaccessfile 的 seek() 方法是多线程断点续传与并发分片写入的核心,需每个线程独立实例、显式定位、long 偏移计算、边界对齐及状态持久化,否则易覆盖或错乱。

RandomAccessFile 的 seek() 方法是多线程断点续传与并发分片写入的核心支撑点,它让每个线程能独立、精确地定位到目标文件的任意字节偏移位置,从而绕过顺序写入限制,实现真正的“各写各的”。但这个能力不会自动生效——必须配合正确的实例管理、偏移计算和状态持久化,否则极易写乱或覆盖。
seek() 是分片写入的定位基础
多线程写入同一目标文件时,不能依赖“追加”语义(RandomAccessFile 不是 FileOutputStream),必须显式控制落点:
- 每个线程需创建自己独立的 RandomAccessFile 实例(模式为 "rw"),不能共享一个实例
- 写入前必须调用
raf.seek(startOffset),将指针精准跳转到本分片起始位置 - startOffset 和 endOffset 需用
long类型计算,避免 int 溢出(尤其 >2GB 文件) - 分片边界要严格对齐:例如 4 线程处理 10MB 文件,线程 0 写 [0, 2500000),线程 1 写 [2500000, 5000000),最后一段用
Math.min(start + chunkSize, file.length())算准
seek() 支撑断点续传的关键动作
断点续传的本质是“恢复上次中断的位置”,seek() 提供了这个位置的设置能力,但需外部配合记录:
- 每次写入一段后,应将当前已写总长度(或该分片最新 offset)写入外部存储(如临时文件、Redis、数据库)
- 重启时先读取该记录值,再用
raf.seek(recordedPos)定位,接着从网络请求 Range: bytes=recordedPos- - 注意:HTTP 响应码必须是 206 Partial Content,否则服务端可能返回全量数据,导致写入错位
- 首次下载前若目标文件已存在,"rw" 模式不会清空内容,需手动
new File(path).delete()或raf.setLength(0)确保干净起点
seek() 的安全边界与常见陷阱
seek() 本身开销极小,但误用会导致数据错乱甚至静默损坏:
- seek() 后未 write 就 close(),或 write 前未 seek,都会导致从 0 开始覆盖
- 多个线程共用同一个 RandomAccessFile 实例,即使加 synchronized,JVM 层也不保证 seek+write 的原子性
- 不要用
getFilePointer()判断“是否写到位”,它返回的是当前指针,不是你期望的逻辑位置 - seek 超出当前文件长度时,后续 write 会自动扩展文件(补零),但磁盘空间立即占用——这不是“稀疏文件空洞”,Java 层无法控制底层是否真正稀疏
更稳妥的替代思路(可选)
如果对并发写稳定性要求极高,可考虑用 NIO 的 FileChannel 替代:
- 用
Files.newByteChannel(path, CREATE, WRITE)打开通道 - 每个线程调用
channel.position(offset).write(buffer),position 设置即刻生效且天然支持并发不同 offset - 避免 RandomAccessFile 实例生命周期管理问题,也减少同步负担
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











