Java使用FileReader与FileWriter读写同一文件时内容被清空问题
问题原因
FileWriter 仅传入文件对象/路径的默认构造方法,底层会调用 FileOutputStream(File file, boolean append),其中 append 参数默认值为 false,对应操作系统层面的文件打开标志为 O_TRUNC,只要以该模式打开已存在的普通文件,操作系统会直接将文件长度截断为0,原有内容立刻清空。这个行为和JVM锁、流的创建顺序完全无关,是文件打开模式的原生特性。
如果创建FileWriter时调用new FileWriter(testFile, true)的重载,将append参数设为true,打开文件时就不会清空原有内容,但这种模式下写入指针默认在文件末尾,无法实现覆写原有内容的需求,且同时打开读写流依然会有指针位置冲突的问题。
和FileDescriptor的关系
FileDescriptor 只是操作系统文件句柄的Java包装对象,本身不会决定文件打开后的行为,清空内容完全是由创建输出流时传入的覆写/追加模式决定的,和FileDescriptor没有直接关联。
同一文件同时读写的实现方案
方案1:小文件场景:先全量读取再写入
如果文件体积不大,最简单的方式是先把文件所有内容读到内存缓冲区,关闭读流后再打开写流写入目标内容,完全规避同文件同时打开读写流的冲突:
@Test public void testCopyStream() throws IOException { File workingDir = new File(System.getProperty("user.dir")); File testFile = new File(workingDir, "test.txt"); // 第一步:先全量读取内容 String content = Files.readString(testFile.toPath()); // 第二步:写入内容,需要追加就传入第二个参数true,需要覆写就用默认模式 FileWriter fos = new FileWriter(testFile); fos.write(content); // 可替换为处理后的内容 fos.close(); }
方案2:大文件/需要随机读写场景:使用RandomAccessFile
RandomAccessFile 支持以rw(读写)模式打开文件,打开时不会截断原有内容,还可以通过getFilePointer()、seek()方法控制读写指针的位置:
@Test public void testRandomAccess() throws IOException { File workingDir = new File(System.getProperty("user.dir")); File testFile = new File(workingDir, "test.txt"); try (RandomAccessFile raf = new RandomAccessFile(testFile, "rw")) { // 先读取所有内容 byte[] buf = new byte[(int) raf.length()]; raf.readFully(buf); String content = new String(buf, StandardCharsets.UTF_8); // 指针移到文件开头,需要追加就改为 raf.seek(raf.length()) raf.seek(0); // 写入处理后的内容 raf.write(content.getBytes(StandardCharsets.UTF_8)); // 如果新写入内容比原文件短,需要截断剩余内容 raf.setLength(raf.getFilePointer()); } }
如果一定要用字符流同时读写,也可以基于RandomAccessFile包装InputStreamReader、OutputStreamWriter实现,避免默认FileWriter打开即清空的问题。
内容的提问来源于stack exchange,提问作者ikaruga_oboro

