RandomAccessFile适合断点续传,因其支持字节级随机定位(seek)、关闭后可凭文件长度续写、需"rw"模式且须sync落盘;多线程需加锁,大文件或旧系统应优先用FileChannel。

RandomAccessFile 为什么适合断点续传
因为它能跳到文件任意位置读写,不依赖流式顺序处理。普通 FileInputStream 或 FileOutputStream 一旦关闭就得重头来,而 RandomAccessFile 持有文件指针(getFilePointer()),关掉再打开也能用 seek() 接上上次的位置。
关键点在于:它不是“流”,是“随机访问句柄”——操作系统层面支持 seek,所以真正做到了字节级定位。
- 必须用
"rw"模式打开,只读("r")或只写("w")都不行;"w"还会清空文件,绝对不能用于续传 - 多线程写同一文件时,
RandomAccessFile本身不保证线程安全,需自行加锁或用FileChannel配合lock() - 注意文件系统缓存:写入后调用
raf.getFD().sync()才能确保数据落盘,否则断电可能丢最后几 KB
怎么用 seek() 和 write() 实现续传逻辑
核心就是:上传前先查本地文件长度,用 seek() 跳到末尾,再把新数据 write() 进去。不是覆盖,是追加。
示例片段(简化版):
立即学习“Java免费学习笔记(深入)”;
RandomAccessFile raf = new RandomAccessFile("part.zip", "rw");
long offset = raf.length(); // 当前已下载字节数
raf.seek(offset); // 定位到末尾
raf.write(new byte[]{0x01, 0x02, 0x03}); // 写入新数据
raf.close();
-
raf.length()返回的是当前文件大小(字节数),即下次该从哪开始写,这是断点的唯一依据 - 不要用
raf.getFilePointer()判断续传位置——它只反映当前指针,和文件实际长度无关 - 如果服务端返回的是“从第 N 字节开始传”,就直接
raf.seek(N),而不是靠length();二者要分清场景
常见错误:文件变大、内容错乱、写不进去
这些问题几乎都出在模式、seek 时机或 close 时机上。
- 用
"r"模式打开后调write()→ 抛IOException: Permission denied - 打开时用了
"w",旧文件被清空 → 下载一半重启,全得重来 - 写完没
close()或没sync(),进程崩溃后文件大小没更新,下次length()还是老值 - 多个线程同时对同一个
RandomAccessFile实例调seek()+write()→ 数据互相覆盖,因为指针是共享的
和 NIO 的 FileChannel 对比,什么情况该换
纯 Java 层断点续传,RandomAccessFile 够用;但遇到大文件、高并发或需要精确控制缓冲/超时,就得考虑 FileChannel。
-
RandomAccessFile.getChannel()可以拿到FileChannel,然后用position()替代seek(),语义更清晰 -
FileChannel.write(ByteBuffer, position)是无副作用的:指定位置写,不影响当前文件指针,天然适合多线程 - Android 早年版本对
RandomAccessFile的seek()有 bug(尤其 FAT32 SD 卡),这时必须切FileChannel
真正容易被忽略的点:RandomAccessFile 的 seek() 在某些嵌入式 Linux 或旧 Android 上,对超过 2GB 的文件可能返回错误的偏移——不是 Java 问题,是底层 lseek() 的 32 位限制。这时候连 length() 都不准,必须用 FileChannel.size() + position() 组合。










