randomaccessfile.seek() 参数是字节偏移而非逻辑单位,分片时需精确计算每片起始位置(startoffset = i * chunksize),最后一片长度须显式计算为 (int)(file.length() - startoffset),避免字符截断或漏片。

RandomAccessFile.seek() 为什么总写错偏移量
因为 seek() 的参数是字节偏移,不是“第几片”,更不是“第几行”。大文件分片时,如果用 file.length() / chunkSize 算出片数再循环调用 seek(),却没校验每一片的实际起始位置,就容易跳到中间某个字符的中间(比如 UTF-8 的多字节字符被截断),或者漏掉最后一片——尤其当 file.length() 不能被 chunkSize 整除时。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 每次分片前先用
raf.seek(startOffset)定位,startOffset必须是上一片结束后的精确位置(即startOffset = i * chunkSize) - 最后一片长度不是
chunkSize,而是(int) (file.length() - startOffset),必须显式计算,不能硬写Math.min(chunkSize, remaining)后还直接readFully() - 别在循环里反复
new RandomAccessFile(..., "r"),打开一次复用,否则操作系统句柄和磁盘寻道开销会陡增
分片读取时 read() 和 readFully() 的行为差异
read(byte[]) 可能只读到部分数据(比如网络抖动、磁盘忙、或缓冲区限制),而分片上传要求“这一片的数据必须完整”,否则服务端校验失败。但 readFully() 又可能阻塞或抛 EOFException ——它只在**没读满指定长度且流已到末尾**时才报错,不是所有场景都适用。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 对确定长度的分片(如非最后一片),优先用
raf.readFully(buffer, 0, expectedLen),它比手动 while 循环更可靠 - 对最后一片,改用
int actual = raf.read(buffer, 0, buffer.length),然后只把actual字节传给上传逻辑 - buffer 大小别设成 1MB 以上,JVM 堆外内存或 GC 压力会明显上升;推荐 512KB 或 256KB,兼顾吞吐与稳定性
并发上传多个分片时 RandomAccessFile 是否线程安全
不安全。RandomAccessFile 的 seek() 和 read() 共享同一个文件指针,如果多个线程共用一个实例去读不同分片,指针会被互相覆盖,结果就是读到错乱的数据。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 每个分片对应一个独立的
RandomAccessFile实例(new RandomAccessFile(path, "r")),不要共享 - 不要试图用
synchronized锁住整个 RAF 实例来“保护指针”——锁粒度太粗,反而串行化,失去并发意义 - 如果担心频繁 open/close 开销,可配合
FileChannel.map()配合MappedByteBuffer,但注意:映射区域不能动态扩展,且force()不适用于只读场景
上传中断后如何续传:靠 offset 还是靠分片编号
靠分片编号 + 预先计算好的 startOffset。服务端返回的“已存在分片”信息通常只含编号或 hash,无法反推本地文件中的真实偏移。如果本地重试时重新算 seek() 位置,但原始文件被修改过(比如追加了内容),就会错位。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 分片元数据(编号、
startOffset、length、md5)应在上传前一次性生成并缓存,而不是每次临时计算 - 续传时直接查本地缓存,用编号找到对应
startOffset,再seek()读取,避免二次解析文件大小 - 不要依赖
raf.getFilePointer()去“恢复”位置——它只是当前指针,不是分片边界,容易因异常中断而失准
真正麻烦的是分片边界和编码边界重合问题:比如你按字节切,但服务端按 UTF-8 行解析,就可能把一行切成两半。这种不属于 RandomAccessFile 能解决的范畴,得在应用层加边界对齐逻辑,或者换用支持 record-level 分片的协议。










