对程序开发人员来说,文件的读写是很重要的一项技能。但是令人惊讶的是,尽管Java提供了一个丰富而健壮的I/O库,进行一些基本的文件操作却显得很繁 琐。不过在Java 7中已经发生了一些改变,但那些使用Java 6的就不那么好运了。幸运的是,Guava做了一些我们期望I/O库做的事情,提供了一系列的工具,让我们能够更方便的进行I/O操作。本篇,我们就开始来学习如何使用Guava Files进行一些I/O操作。
尽管Java 7做了一些改进,并解决了一些Guava的小的问题,但我们发现Guava提供的工具在进行I/O操作时仍然非常有用。本Guava Files系列中,我们将要学习一下内容:
使用Files类来执行那些基本的任务,比如:移动或复制文件,或读取文件内容到一个字符串集合
Closer类,提供了一种非常干净的方式,确保Closeable实例被正确的关闭
ByteSource 和 CharSource类,提供了不可变的输入流(Input)和读(Reader)
ByteSink 和 CharSink类,提供了不可变的输出流(Output)和写(Writer)
CharStreams和ByteStreams类,为读Readers、写Writers、输入流InputStreams、输出流OutputStreams 提供了一些静态的实用方法
BaseEncoding类,提供了编码和解码字节序列和ASCII字符的方法
文件的复制
Files类提供了一些有用的方法来操作File对象,对Java开发人员来说,复制一个文件到另一个文件是件有挑战的工作。但是在Guava里,我们来看怎样通过Files类完成同样的工作:
@Test
public void testCopyFile() throws IOException {
File original = new File("D:\\test.txt");
File copy = new File("D:\\test2.txt");
Files.copy(original, copy);
}
文件的移动/重命名
同样,Java中移动文件也和复制一样繁琐。在Guava里,则非常的简单,代码如下:
@Test
public void testMoveFile() throws IOException {
File original = new File("D:\\test.txt");
File newFile = new File("D:\\test2.txt");
Files.move(original, newFile);
}
像字符串一样处理文件
有些时候我们需要操作或使用文件的字符串表示。Files类提供了一些方法,能够将文件读取到一个字符串集合,返回文件的第一行字符串,将一个完整的文件 的内容读入一个字符串。下面的例子,会介绍通过调用Files.readLines方法将文件读取到一个string集合中:
@Test
public void readFileIntoListOfStringsTest() throws IOException {
File file = new File("D:\\test2.txt");
List<String> expectedLines = Lists.newArrayList("hello world", "this is realfighter", "www.xx566.com");
List<String> readLines = Files.readLines(file,
Charsets.UTF_8);
assertThat(expectedLines, is(readLines));
}
上面的例子中,我们使用了一个单元测试来确认从简单文件中读取的三行内容与我们的期望相同。每行内容中的换行符被删除,但其他空白的字符则保留。Files.readLines还可以接收LineProcessor实例作为额外的附加参数。每一行内容都参数LineProcessor.processLine方法,该方法返回一个布尔值。LineProcessor实例会持续读取文件中的行,直到文件读取完毕或LineProcessor.processLine方法返回false。假设,我们有包含如下信息的一个文件,是一些书本的信息:
"Savage Tom",Being A Great Cook,Acme Publishers,ISBN- 123456,29.99,1
"Smith Jeff",Art is Fun,Acme Publishers,ISBN-456789,19.99,2
"Vandeley Art",Be an Architect,Acme Publishers,ISBN- 234567,49.99,3
"Jones Fred",History of Football,Acme Publishers,ISBN- 345678,24.99,4
"Timpton Patty",Gardening My Way,Acme Publishers,ISBN- 4567891,34.99,5
我们想要抽取出每行数据中的书本的标题。为了完成这项任务,我们需要对LineProcessor接口做如下的实现:
class ToListLineProcessor implements LineProcessor<List<String>> {
private static final Splitter splitter = Splitter.on(",");
private List<String> bookTitles = Lists.newArrayList();
private static final int TITLE_INDEX = 1;
@Override
public boolean processLine(String line) throws IOException {
bookTitles.add(Iterables.get(splitter.split(line), TITLE_INDEX));
return true;
}
@Override
public List<String> getResult() {
return bookTitles;
}
}
在这里我们将使用逗号分隔每行,获取这本书的标题,是每行中的第二项,并将标题添加到一个字符串集合中。注意,我们使用了Iterables类,使用了静态的Iterables.get方法,来获取书本的标题。processLine方法总是返回true,因为我们需要获取所有文件中的书本名,下面是对LineProcessor实例的单元测试:
@Test
public void readLinesWithProcessor() throws Exception {
File file = new File("D:\\test2.txt");
List<String> expectedLines = Lists.newArrayList("Being A Great Cook", "Art is Fun",
"Be an Architect", "History of Football", "Gardening My Way");
List<String> readLines = Files.readLines(file, Charsets.UTF_8,
new ToListLineProcessor());
assertThat(expectedLines, is(readLines));
}
在这个例子中,我们简单的获取了读取了所有的输入,但是我们可以很容易的通过一些条件只获取n行或过滤一些数据。
文件的哈希值
在Java中生成文件的哈希值似乎需要很多的代码操作,但在Guava中,它变得非常简单。Files类拥有一个hash方法,使用代码如下:
@Test
public void testFilesHashing() throws Exception{
File file = new File("D:\\test2.txt");
HashCode hashCode = Files.hash(file, Hashing.md5());
System.out.println(hashCode);
}
上面的例子中,为了使用Files.hash方法,我们提供了File对象和HashFuction实例,我们使用了一个实现MD5算法的hash函数,并且方法返回一个HashCode对象。Hash函数将在下一个系列中介绍,敬请期待。
文件写
当我们使用输入/输出流时,我们经常需要编写以下几步的代码:
打开输入/输出流。
将字节读入/读出。
读取完毕,确保所有的资源都在finally代码块中关闭。
当我们不得不一遍遍的重复这个过程,就很容易出错,并会使得代码越来越不清晰和难以维护。Files类为我们提供了方便,能够很容易的 在文件的写/追加数据或读取文件内容到字节数组。大部分那些我们需要特别关注的打开或关闭资源的代码,只需要简单的一行代码。
文件写和追加数据
一个简单的文件的写和追加数据例子,代码如下:
@Test
public void appendingWritingToFileTest() throws IOException {
File file = new File("D:\\test2.txt");
file.deleteOnExit();
String hamletQuoteStart = "To be, or not to be";
Files.write(hamletQuoteStart, file, Charsets.UTF_8);
assertThat(Files.toString(file, Charsets.UTF_8), is(hamletQuoteStart));
String hamletQuoteEnd = ",that is the question";
Files.append(hamletQuoteEnd, file, Charsets.UTF_8);
assertThat(Files.toString(file, Charsets.UTF_8), is(hamletQuoteStart + hamletQuoteEnd));
String overwrite = "Overwriting the file";
Files.write(overwrite, file, Charsets.UTF_8);
assertThat(Files.toString(file, Charsets.UTF_8), is(overwrite));
}
在这个例子中,我们使用单元测试做了如下几件事情:
创建一个测试的文件,并确保JVM中不存在同名的文件
我们使用Files.write方法向文件写入一个字符,并确保写入成功
之后我们使用了Files.append方法追加了另一个字符到文件,并同样确认文件中已经存在追加的内容
最后,我们再次使用Files.write方法去覆盖文件,并确保文件已被覆盖
虽然这是一个简单的例子,但请注意,我们三次对文件进行写,我们不曾编写任何打开或关闭资源的代码。因此,我们的代码变得简单易读,更重要的是,不容易出现错误。