Java 面试必问:文件读取如何保证读完?未写完就被读怎么解决?
问题背景
- 在 Java 文件操作的面试中,经常会问到两个核心问题:
- 代码层面,如何判断一个文件已经完全读完?
业务层面,如何避免文件还在写入就被读取,读到不完整内容?
本文从基础判断到生产可用方案,一次性总结清楚
一、Java 中如何判断文件已读完?
不同 IO 方式,判断结束的标志不同,记住这一套即可:
1. 字节流 InputStream
read() 或 read(byte[]) 返回 -1 表示到达文件末尾。
int len;
byte[] buffer = new byte[1024];
while ((len = inputStream.read(buffer)) != -1) {
// 处理数据
}
---------------------------
1.字节流的 read() 方法返回 -1 时,表示文件读取到末尾(读完)。
import java.io.FileInputStream;
import java.io.IOException;
public class ReadFileEndCheck {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("test.txt")) {
int byteData;
// 核心:read()返回-1时,说明文件读完
while ((byteData = fis.read()) != -1) {
// 处理读取到的字节(比如转字符)
System.out.print((char) byteData);
}
System.out.println("\n文件已完全读取");
} catch (IOException e) {
e.printStackTrace();
}
}
}
---------------------------
2.批量读取(更高效):read(byte[] buffer) 返回读取到的字节数,返回 -1 时表示读完:
try (FileInputStream fis = new FileInputStream("test.txt")) {
byte[] buffer = new byte[1024];
int len;
// len为-1时,文件读完
while ((len = fis.read(buffer)) != -1) {
// 处理buffer中前len个字节
System.out.print(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
2. 字符流 BufferedReader
readLine() 返回 null 表示读完。
String line;
while ((line = br.readLine()) != null) {
// 处理行数据
}
--------------------
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderEndCheck {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
// 核心:readLine()返回null时,文件读完
while ((line = br.readLine()) != null) {
System.out.println(line);
}
System.out.println("文件已完全读取");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. NIO FileChannel
channel.read(ByteBuffer) 返回 -1 表示读完。
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileRead {
public static void main(String[] args) {
try (RandomAccessFile raf = new RandomAccessFile("test.txt", "r");
FileChannel channel = raf.getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead;
// 返回-1时读完
while ((bytesRead = channel.read(buffer)) != -1) {
buffer.flip(); // 切换为读模式
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear(); // 清空缓冲区
}
System.out.println("\n文件已完全读取");
} catch (Exception e) {
e.printStackTrace();
}
}
}
二、核心问题:文件还没写完,就被读取了怎么办?
这类问题本质是:写读竞争 → 读到半拉文件。
常见场景:大文件上传、日志采集、跨进程文件传输。
下面是 4 种标准解决方案,按生产推荐度排序。
三、方案 1:临时文件 + 重命名(最推荐、最稳定)
思路
- 写入方先写 .tmp / .temp 临时文件
- 完全写完后,原子重命名为正式文件名
- 读取方只处理正式文件
四、方案 2:约定结束标记(简单通用)
思路
- 读写双方约定一个结束标记,例如:###EOF###
读取方只有读到该标记,才认为文件完整。
优点
实现简单
跨语言、跨平台
缺点
写入进程异常退出时,可能不会写结束符。
五、方案 3:判断文件大小 / 修改时间稳定
思路
记录当前文件大小
等待 500ms ~ 1s
再次获取大小,不变则认为写入完成
long size1 = file.length();
Thread.sleep(1000);
long size2 = file.length();
if (size1 == size2) {
// 认为写入完成
}
优点
无需改造写入端
适合第三方文件采集
缺点
写入暂停时可能误判
六、方案 4:NIO 文件锁 FileLock
思路
写入时加独占锁
读取时尝试加共享锁,加锁成功表示可读取
FileLock lock = channel.tryLock(0, Long.MAX_VALUE, true);
if (lock != null) {
// 文件未被占用,可以安全读取
lock.release();
}
优点
JVM 标准机制
适合多进程、多线程并发
缺点
不同操作系统行为略有差异
七、面试标准回答(可直接背诵)
1. 判断文件读完:
字节流返回 -1,字符流 BufferedReader readLine 返回 null。
2. 防止未写完就读:
本质解决写读竞争问题,常用四种方案:
优先使用:临时文件 + 重命名,最稳定、生产最常用
简单方案:约定结束标记
无侵入方案:判断文件大小稳定
标准并发方案:NIO FileLock 文件锁

浙公网安备 33010602011771号