Unix系统下如何实现阻塞式文件写入操作?
如何让Java在Unix系统中阻塞式写入文件(等待数据真正落盘后再继续)
当然有办法解决这个问题!你现在遇到的情况,本质是Java的IO缓冲+操作系统的页缓存共同作用的结果——你的println()其实只是把数据写到了Java的用户态缓冲区,就算调用flush()也只是把数据刷到操作系统的内核缓冲区,真正的磁盘写入被操作系统延后批量处理,所以日志刷得飞快,但最后要等所有数据落盘才会结束程序。
要实现每次写入操作完成后(数据真正写到磁盘)再执行下一行代码,你需要两步操作:
- 确保Java层面的缓冲被刷到内核(或者直接使用无缓冲的IO类);
- 强制操作系统把内核缓冲区的数据写入磁盘,并且等待这个操作完成。
具体实现方案
Java提供了两种主要方式来触发操作系统的磁盘同步:
方案1:使用FileChannel.force()
FileChannel.force(boolean metaData)方法会调用POSIX标准的fsync()(传true)或fdatasync()(传false)系统调用,阻塞直到数据(和可选的元数据)写入磁盘。
修改后的代码示例:
import java.io.*; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.util.ArrayList; public class BlockingFileWriter { public static void main(String[] args) throws IOException { String inFile = "input.txt"; String outFile = "output.txt"; ArrayList<String> lines = new ArrayList<>(); BufferedReader in = new BufferedReader(new FileReader(inFile)); String line; while((line = in.readLine()) != null){ lines.add(line); } in.close(); int written = 0; // 使用try-with-resources自动关闭资源 try (FileOutputStream fos = new FileOutputStream(outFile); FileChannel channel = fos.getChannel(); OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { for(String l : lines){ written++; writer.write(l); writer.write(System.lineSeparator()); // 强制刷盘:true表示同时刷新文件元数据(如修改时间、大小) // 若不需要元数据,传false对应fdatasync(),性能略好 channel.force(true); if(written % 10000 == 0) { Message.info("lines written "+written); } } } catch (IOException e) { e.printStackTrace(); } } }
方案2:使用FileDescriptor.sync()
如果你习惯用RandomAccessFile,可以通过它的getFD().sync()方法触发fsync():
import java.io.*; import java.util.ArrayList; public class BlockingFileWriter2 { public static void main(String[] args) throws IOException { String inFile = "input.txt"; String outFile = "output.txt"; ArrayList<String> lines = new ArrayList<>(); BufferedReader in = new BufferedReader(new FileReader(inFile)); String line; while((line = in.readLine()) != null){ lines.add(line); } in.close(); int written = 0; try (RandomAccessFile raf = new RandomAccessFile(outFile, "rw")) { for(String l : lines){ written++; raf.writeBytes(l + System.lineSeparator()); // 触发系统fsync(),阻塞直到数据落盘 raf.getFD().sync(); if(written % 10000 == 0) { Message.info("lines written "+written); } } } catch (IOException e) { e.printStackTrace(); } } }
关键注意事项
- 性能损耗:这种阻塞式刷盘会大幅降低写入性能,因为每次写入都要等待慢速的磁盘IO操作——这是你要“日志输出时数据已落盘”必须付出的代价。
- 避免带缓冲的Writer:不要用
PrintWriter、BufferedWriter这类带用户态缓冲的类(除非你每次写完都手动flush(),但依然要配合force()/sync()),否则数据会先在Java缓冲区堆积,无法实时触发刷盘。 - 文件系统兼容性:
fsync()是POSIX标准调用,在Ubuntu等Unix-like系统上完全生效,不管你用EXT4还是XFS等文件系统,都能保证数据真正写入磁盘(即使系统崩溃也不会丢失)。
内容的提问来源于stack exchange,提问作者Thomas Ludwig
相关产品推荐
相关产品推荐

