问题背景
- 在 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 文件锁
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |