在Java中对系统文件进行写入是一种很常见的操作,本文总结了写文件的一些常用实现,并对其进行对比。
BufferedWriter
首先使用BufferedWriter向新文件中写入简单的字符串:
@Test
public void writeStringsWithBufferedWriter() throws IOException{
String str = "test";
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
writer.write(str);
writer.close()
}
复制代码
执行程序后,文件中的内容为:
test
复制代码
我们同样可以在已有的文件中追加字符串:
@Test
public void appendStringsWithBufferedWriter() throws IOException{
String str = " Hello,World";
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName true));
writer.append(str);
writer.close();
}
复制代码
执行之后,文件内容变为:
test Hello,World
复制代码
PrintWriter
使用PrintWriter可以向文件中写入格式化的文本:
@Test
public void writeStringWithPrintWriter() throws IOException{
FileWriter writer = new FileWriter(fileName);
PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.print("test1");
printWriter.printf("string %s, num %d", "formatString", 10);
printWriter.close();
}
复制代码
文件内容为
test1
string formatString,num 10
复制代码
我们可以利用FileWriter、BufferedWriter甚至是System.out来构造PrintWriter,通过PrintWriter不仅可以向文件中写入原生字符串,而且可以利用printf方法写入格式化的文本。
FileOutputStream
使用FileOutputStream可以向文本中写入二进制数据,示例如下:
@Test
public void writeFileWithFileOutputStream() throws IOException{
String str = "test";
FileOutputStream stream = new FileOutputStream(fileName);
byte[] bytes = str.getBytes();
stream.write(bytes);
stream.close();
}
复制代码
DataOutputStream
@Test
public void writeFileWithDataOutputStream() throws IOExeption{
String str = "test";
FileOutputStream fos = new FileOutputStream(fileName);
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(fos));
dos.write(str);
dos.close();
}
复制代码
RandomAccessFile
目前为止,我们都只是写全新的文件或者是在已有文件中追加新的内容,但是很多时候我们需要在已有文件中写入内容或者编辑内容,也就是要对文件进行随机访问。RandomAccessFile在给定偏移值后,就可以向文件中的特定位置写入二进制内容,下例中的代码可以在文件的特定位置写入一个数字:
void writeIntOnPosition(String fileName, int data, long position) throws IOException{
RandomAccessFile writer = new RandomAccessFile(fileName, "rw");
writer.seek(position);
writer.writeInt(data);
writer.close();
}
复制代码
可以使用类似的方式实现从特定位置读取数字:
int readIntOnPosition(String fileName, long position) throws IOException{
int result = 0;
RandomAccessFile reader = new RandomAccessFile(fileName, "r");
reader.seek(position);
result = reader.readInt();
reader.close();
return result;
}
复制代码
可以通过下面的方法进行简单的测试:
@Test
public void writeFileWithRandomAccessFile() throws IOException{
int origin_int = 1;
int change_int = 2;
long position = 12;
writeIntOnPosition(fileName, origin_int, position);
assertEquals(origin_int, readIntOnPosition(fileName, position));
writeIntOnPosition(fileName, change_int, position);
assertEquals(change_int, readIntOnPosition(fileName, position));
}
复制代码
FileChannel
对于大文件的操作,FileChannel比标准IO接口更高效。使用示例如下:
@Test
public void writeFileWithFileChannel() throws IOException{
RandomAccessFile stream = new RandomAccessFile(fileName, "rw");
FileChannel channel = stream.getChannel();
String str = "test";
byte[] bytes = str.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
channel.write(buffer);
stream.close();
channel.close();
RandomAccessFile reader = new RandomAccessFile(fileName, "r");
assertEquals(str, reader.readLine());
reader.close()
}
复制代码
使用Java 7
在Java 7中,引入了新的工具类Files,提供了一种操作文件系统的新方式。通过该工具类,可以对文件和目录进行新增、复制、删除、移动等操作,同样也可以用来读写文件内容:
@Test
public void writeFileWithJava7() throws IOException{
String str = "test";
Path path = Paths.get(fileName);
byte[] bytes = str.getBytes();
Files.write(path, bytes);
String read = Files.readAllLines(path).get(0);
asserEquals(str, read);
}
复制代码
写入临时文件
接下来,我们尝试对临时文件进行写操作:
@Test
public void writeToTmpFile() throws IOException{
String str = "Hello";
File tmpFile = File.createTempFile("test", ".tmp");
FileWriter writer = new FileWriter(tmpFile);
writer.write(str);
writer.close();
BufferedReader reader = new BufferedReader(new FileReader(tmpFile));
assertEquals(str, reader.readLine());
reader.close();
}
复制代码
可以看出,除了临时文件的创建之外,其余操作都是大同小异的。
写操作之前对文件加锁
有时候,我们在对文件进行写操作之前,需要保证其他人不会同时对该文件进行修改,也就是说我们需要在写文件时对其进行加锁。
在对文件写入之前,我们可以使用FileChannel尝试对文件进行加锁:
@Test
public void tryLockFileBeforeWrite() throws IOException{
RandomAccessFile stream = new RandomAccessFile(fileName, "rw");
FileChannel channel = stream.getChannel();
FileLock lock = null;
try{
lock = channel.tryLock();
}catch(final OverlappingFileLockException e){
stream.close();
channel.close();
}
stream.writeChars("test");
lock.release();
stream.close();
channel.close();
}
复制代码
注意,如果我们尝试获取锁时,文件处于已加锁状态,则会抛出OverlappingFileLockException。
总结
以上的方法都能实现写文件的目的,但是仍有一些需要注意的地方:
如果尝试读一个不存在的文件,会抛出FileNotFoundException。
如果尝试写一个不存在的文件,会先创建该文件,不会抛出异常。
在使用完流对象之后,一定要及时关闭以释放相关系统资源,这一点非常重要,因为流对象不会隐式自动关闭。
在输出流中,close()方法在释放系统资源之前会首先调用flush()方法,强制将缓存的数据写入流中。
通常来说,PrintWriter用于编写格式化文本,FileOutputStream用于写二进制数据,DataOutputStream用于写基本数据类型,RandomAccessFile用于在特定位置对文件进行操作,FileChannel对于大型文件的操作更加高效。虽然这些API都可以提供很多功能,但是根据不同的使用场景选择合适的方案会更加便利。