Guava翻译系列之File

使用guava处理文件

读写文件是一个程序员的核心能力! 令人意外的事,虽然java有非常丰富的并且强壮的I/O接口,但是却不怎么好用。 虽然在java7中已经有了一些改善。 但是我们还是要学一下guava的I/O相关的工具。 这一章我们要学习一下内容:
-- 使用Files类处理文件的移动和复制,或者从文件中读取内容到字符串中
-- Closer 类 给我们提供非常简洁干净的方式去确保文件被正确关闭
-- ByteSource 和 CharSource 类,是inputStream和readers的不可变实现类
-- ByteSink 和 CharSink 类,是 outputStreams 和 writers的不可变实现类
-- CharStreams 和 ByteStreams 类 提供了静态方法去分别处理 Readers,Writers,InputStreams,和OutputStreams
-- BaseEncoding 类,提供方法处理 byte序列和ASCII码

复制文件

Files 类提供了很多非常有用的方法处理文件对象,对于任何一个java开发者,copy文件也算是一个比较有挑战性的过程,但是我们看一下在guava的帮助下,怎样方便的额copy一个文件:

File original = new File("path/to/original");
File copy = new File("path/to/copy");
Files.copy(original, copy);

移动/重命名文件

移动文件和复制文件一样在java中也是非常笨重,但是使用guava就会变的比较简单:

public class GuavaMoveFileExample {
public static void main(String[] args) {
File original = new File("src/main/resources/copy.txt");
File newFile = new File("src/main/resources/newFile.txt");
try{
Files.move(original, newFile);
}catch (IOException e){
e.printStackTrace();
}
}
}

上面的例子中,我们将copy.txt文件重命名为newFile.txt文件

处理字符串一样处理文件

Files类可以读取将文件的内容读取到字符串数组中,并返回文件的第一行, 下面的例子中我们将看到怎样将文件读取到字符串数组中:

@Test
public void readFileIntoListOfStringsTest() throws Exception{
File file = new File("src/main/resources/lines.txt");
List<String> expectedLines = Lists.newArrayList("The quick
brown","fox jumps over","the lazy dog");
List<String> readLines = Files.readLines(file,
Charsets.UTF_8);
assertThat(expectedLines,is(readLines));
}

在这个例子中,我们使用单元测试的方式从文件中读取内容并和期望读取到的内容进行比较。 数组中字符串的分行符都已经被去除,但是其他的空白字符串都被保留。 还有另外一个版本的Files.readLines方法,接受一个LineProcessor实例作为额外的参数,每一行都会经过LineProcessor.processLine方法处理,这个方法返回一个boolean值,当processLine方法返回false,或者文件读取完成,文件处理会终止。 下面我们来看一下下面的一个CSV文件, 文件包含的内容如下:

"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实例:

public 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 List<String> getResult() {
return bookTitles;
}
@Override
public boolean processLine(String line) throws IOException {
bookTitles.add(Iterables.get(splitter.split(line),TITLE_
INDEX));
return true;
}

这里我们以逗号分隔读取到的字符串,并将title放到List 中,这里我们都是返回true,因为我们想获得所有的书的标题。 下面是一个单元测试确保我们上面的LineProcessor的逻辑处理是正确的:

@Test
public void readLinesWithProcessor() throws Exception {
File file = new File("src/main/resources/books.csv");
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));
}

Hashing a file

给一个文件产生HashCode,使用原有的java代码的情况下,会产生很多标准化的代码。 但是使用Guava就可以很容易的给文件产生一个Hash码.

public class HashFileExample {
public static void main(String[] args) throws IOException {
File file = new File("src/main/resources/sampleTextFileOne.
txt");
HashCode hashCode = Files.hash(file, Hashing.md5());
System.out.println(hashCode);
}
}

上面的例子中,我们使用的Files的hash方法,传入File对象,和 HashFunction实例,这里的HashFunction我们使用的是HashFunction的MD5实现。

写文件

处理input/output streams时,我们一般会有下面的几个步骤:
1. 打开文件的输入、输出流
2. 从文件中读取字节流
3. 操作完成后,在finally块中保证所有的资源关闭

当我们在代码中一遍遍重复这样的过程后,代码将变得不好维护, Files类提供了非常方便的方法去写或者追加内容到文件中。 一般情况只需要一行代码就可以搞定。

写、追加 文件内容

下面是一个追加文件内容的例子:

@Test
public void appendingWritingToFileTest() throws IOException {
File file = new File("src/test/resources/quote.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));
}

上面的例子中,我们使用了一个单元测试做了如下的事情:

  1. 创建一个文件,并且保证这个文件如果存在的话,就将其删除
  2. 使用File.write方法写文件,并且保证写入是成功的
  3. 使用File.append方法追加内容到字符串中,并且同样保证追加的内容是成功的
  4. 使用File.write方法覆盖之前的内容,并保证之前的内容被覆盖了

虽然这是一个比较简单的例子,但是注意到我们这里并没有任何打开或关闭文件的操作,这些基本的操作已经由guava帮助我们完成了。

InputSupplier and OutputSupplier

Guava 有InputSupplier 和 OutputSupplier 接口作为InputStreams/Readers 和 OutputStream/Writers的门面。 下面章节中,我们将看到我们是怎样从InputSuppliers和OutputSuppliers中受益,使用这些接口guava会自动帮助我们open,flush,close用到的资源。

Sources and Sinks

Guava I/O中有source和sink分别对应为reading,writing文件,Sources,Sinks不是streams readers writers 但是提供了相同的功能。 Source 和 Sink对象可以按照下面两个方式使用:

ByteSource

ByteSource 代表了可读的bytes类型的数据源,典型的是我们可以从文件中读取byte类型的数据,下面我们从文件中读取一个ByteSource。

@Test
public void createByteSourceFromFileTest() throws Exception {
File f1 = new File("src/main/resources/sample.pdf");
byteSource = Files.asByteSource(f1);
byte[] readBytes = byteSource.read();
assertThat(readBytes,is(Files.toByteArray(f1)));
}

这里我们使用Files.asByteSource方法创建一个ByteSource。 接着我们调用read方法读取字节数组。 最后我们假设读取到的bytes和调用的Files.toByteArray方法得到的值是一致的.

ByteSink

ByteSink类代表了一个可以写的byte source。 我们可以写入bytes到文件或者byte数组。 从文件中创建一个ByteSink.我们可以按照如下的方式:

@Test
public void testCreateFileByteSink() throws Exception {
File dest = new File("src/test/resources/byteSink.pdf");
dest.deleteOnExit();
byteSink = Files.asByteSink(dest);
File file = new File("src/main/resources/sample.pdf");
byteSink.write(Files.toByteArray(file));
assertThat(Files.toByteArray(dest),is(Files.
toByteArray(file)));
}

和上面的读取类似这里就不再描述.

Copying from a ByteSource class to a ByteSink class

下面我们将学习怎样将ByteSource和ByteSink整合起来使用,这样就可以屏蔽具体的细节,只要关注 ByteSource和ByteSink。

@Test
public void copyToByteSinkTest() throws Exception {
File dest = new
File("src/test/resources/sampleCompany.pdf");
dest.deleteOnExit();
File source = new File("src/main/resources/sample.pdf");
ByteSource byteSource = Files.asByteSource(source);
ByteSink byteSink = Files.asByteSink(dest);
byteSource.copyTo(byteSink);
assertThat(Files.toByteArray(dest),
is(Files.toByteArray(source)));
}

上面的例子中我们通过使用Files.asByteSource 和 Files.asByteSink方法 创建了 ByteSource和ByteSink实例。 然后我们调用ByteSource.copyTo方法 将bytes写入到ByteSink对象中。 然后验证一下写入的是否正确。 ByteSink 也有copyTo方法,接受一个outputStream将字节写入到目标文件中。

ByteStreams and CharStreams

ByteStreams 是与InputStream和OutputStream的工具类,CharStreams是Reader和Writer的工具类, 这两个工具类中有很多方法,我们这里只关注一些比较有趣的方法.

Limiting the size of InputStreams

ByteStreams,limit方法接受一个InputStream和一个长整形的参数,返回一个包装了好了的 InputStream 仅仅包含指定长度的的字节数。下面我们看一个例子:

@Test
public void limitByteStreamTest() throws Exception {
File binaryFile = new
File("src/main/resources/sample.pdf");
BufferedInputStream inputStream = new
BufferedInputStream(new FileInputStream(binaryFile));
InputStream limitedInputStream =
ByteStreams.limit(inputStream,10);
assertThat(limitedInputStream.available(),is(10));
assertThat(inputStream.available(),is(218882));
}

Joining CharStreams

使用CharStreams.join方法可以将多个文件的内容一起写入到一个文件中去.

@Test
public void joinTest() throws Exception {
File f1 = new
File("src/main/resources/sampleTextFileOne.txt");
File f2 = new
File("src/main/resources/sampleTextFileTwo.txt");
File f3 = new File("src/main/resources/lines.txt");
File joinedOutput = new
File("src/test/resources/joined.txt");
joinedOutput.deleteOnExit();
List<InputSupplier<InputStreamReader>> inputSuppliers() =
getInputSuppliers()(f1,f2,f3);
InputSupplier<Reader> joinedSupplier =
CharStreams.join(inputSuppliers());
OutputSupplier<OutputStreamWriter> outputSupplier =
Files.newWriterSupplier(joinedOutput, Charsets.UTF_8);
String expectedOutputString = joinFiles(f1,f2,f3);
CharStreams.copy(joinedSupplier,outputSupplier);
String joinedOutputString = joinFiles(joinedOutput);
assertThat(joinedOutputString,is(expectedOutputString));
}
private String joinFiles(File ...files) throws IOException {
StringBuilder builder = new StringBuilder();
for (File file : files) {
builder.append(Files.toString(file,Charsets.UTF_8));
}
return builder.toString();
}
private List<InputSupplier<InputStreamReader>>
getInputSuppliers()(File ...files){
List<InputSupplier<InputStreamReader>> list =
Lists.newArrayList();
for (File file : files) {
list.add(Files.newReaderSupplier(file,Charsets.UTF_8));
}
return list;
}

我们看一下上面一大段代码的意思:
1. 创建了4个文件对象,其中3个位输入文件对象,1个为输出文件对象
2. 使用 Files.newReaderSupplier的静态方法创建InputSupplier实例
3. 将3个InputSupplier逻辑上变成一个InputSupplier
4. 调用Files.newWriterSupplier方法创建OutputSupplier
5. 最后调用Files.toString方法获取要比较的数据
6. 调用CharStreams.copy方法将InputSupplier数据写入到OutputSupplier
7. 最后验证我们写入的数据是否和想象的一样

Closer

Closer类在guava中的作用是保证所有实现了Closeable接口的对象都能够调用Closer.close方法合理的关闭。 这个功能在java7中也有类似的实现 try-with-resources. 但是使用Closer的方式更加直观,具体的例子如下:

public class CloserExample {
public static void main(String[] args) throws IOException {
Closer closer = Closer.create();
try {
File destination = new File("src/main/resources/copy.
txt");
destination.deleteOnExit();
BufferedReader reader = new BufferedReader(new
FileReader("src/main/resources/sampleTextFileOne.txt"));
BufferedWriter writer = new BufferedWriter(new
FileWriter(destination));
closer.register(reader);
closer.register(writer);
String line;
while((line = reader.readLine())!=null){
writer.write(line);
}
} catch (Throwable t) {
throw closer.rethrow(t);
} finally {
closer.close();
}
}
}

BaseEncoding

当我们处理二进制数据时,我们有时候需要把二进制数据转换成可打印的ASCII码,我们当然也需要将已经编码的数据转换成原来的编码方式,BaseEncoding是一个抽象类包含一些静态工厂方法来创建不同编码方式的实例,下面是一个简单的例子:

@Test
public void encodeDecodeTest() throws Exception {
File file = new File("src/main/resources/sample.pdf");
byte[] bytes = Files.toByteArray(file);
BaseEncoding baseEncoding = BaseEncoding.base64();
String encoded = baseEncoding.encode(bytes);
assertThat(Pattern.matches("[A-Za-z0-
9+/=]+",encoded),is(true));
assertThat(baseEncoding.decode(encoded),is(bytes));
}

上面的例子中,我们获取了一个pdf文件,并且将其用Base64编码, 我们假设所有的字节都被编辑成了ASCII码, 然后又将获得到的base64编码的数据decode,BaseEncoding 类除了给我们简单的encode和decode,我们还可以包装outputSupplier bytesink,writer实例,这样在写入时,就可以使用我们指定的编码。 一样的我们也可以包装InputStream,bytesource,reader实例,再读取文件时进行decode. 下面我们看一个具体的例子:

@Test
public void encodeByteSinkTest() throws Exception{
File file = new File("src/main/resources/sample.pdf");
File encodedFile = new
File("src/main/resources/encoded.txt");
encodedFile.deleteOnExit();
CharSink charSink = Files.asCharSink(encodedFile,
Charsets.UTF_8);
BaseEncoding baseEncoding = BaseEncoding.base64();
ByteSink byteSink = baseEncoding.encodingSink(charSink);
ByteSource byteSource = Files.asByteSource(file);
byteSource.copyTo(byteSink);
String encodedBytes = baseEncoding.encode(byteSource.read());
assertThat(encodedBytes,is(Files.
toString(encodedFile,Charsets.UTF
_8)));
}

总结

我们学习了怎样使用InputSupplier和OutputSupplier处理文件的打开和关闭,然后我们还学习了怎样是用ByteSource,ByteSink,CharSource,CharSink 类,最后我们学习了使用BaseEncoding类将二进制数据转换为文本数据,

时间: 2024-09-10 08:01:00

Guava翻译系列之File的相关文章

guava翻译系列之Joiner

基本的guava工具 在前面的章节,我们已经讨论了什么是GUAVA和怎样去安装GUAVA,在这一章我们将开始使用guava,我们将展示guava提供的基本功能,并且了解一下这些基本功能是怎样帮助我们简化日常工作遇到的的问题 在这一章节中,我们将覆盖一下几个方面的内容: 使用Joiner Class 将字符串以指定的分隔符连接起来. 我们也会涉及到使用MapJoiner Splitter Class,和Joiner的作用相反,将一个字符串以给定的分隔符分隔开 使用CharMatcher 和 Str

guava翻译系列之Cache

Guava Cache 在软件开发的过程,缓存是一个非常重要的话题. 在稍微复杂的开发过程中,我们基本上是不可能不使用到缓存的. 至少我们会使用Map去存储一些东西. 这其实就是一个最简单的缓存. Guava给我们提供了比简单的使用HashMap更强大更灵活的功能,但是和专业的缓存工具相比,(EHCache,Memcached)功能还有些不足, 那么这一章,我们将覆盖Guava cache的下面几个方面: -- 使用MapMaker类创建ConcurrentMap实例 -- 使用CacheBui

Guava翻译系列之EventBus

EventBus 类解析 当我们开发软件时,各个对象之间的数据共享和合作是必须的. 但是这里比较难做的是 怎样保证消息之间的传输高效并且减少各个模块之间的耦合. 当组件的职责不清楚时,一个组件还要承担另一个组件的职责,这样的系统我们就认为是高耦合. 当我们的系统变得高耦合时,任何一个小的改动都会对系统造成影响. 为了解决设计上的问题,我们设计了基于事件的设计模型. 在事件驱动编程模型中,对象可以发布/订阅 事件. 事件监听者就是监听事件的发生,我们在第六章中已经看到过RemovalListene

guava翻译系列之Collections

引言 集合对于任何一门语言都是必须的.没有集合我们写不出一些复杂的逻辑.Guava继承扩展了Google Collections的一些功能. 从 com.google.common.collect包下面的类的数量,我们就可以看出Collections的重要性. 虽然已经有了这么多的工具类,但是还是有很多场景我们没有覆盖到,我们希望我们能够覆盖到日常使用的哪些. 下面我们就在每天的编程中经常会使用的类做一下介绍. 这一章节中我们将涉及以下几个方面: lists,maps,sets 等这些包含非常有

guava翻译系列之Splitter

使用Splitter类 另一个常见的操作就是解析一个以固定分隔符分隔的字符串,并返回一个包含这个String的数组,如果你需要去阅读一个text文件,你会经常要处理这种情况.但是String.split方法还有一些可以改进的地方,就像下面的例子所展示的:String testString = "Monday,Tuesday,,Thursday,Friday,,"; //parts is [Monday, Tuesday, , Thursday,Friday] String[] part

Oracle ASM 翻译系列第八弹:ASM Internal ASM file extent map

当ASM创建一个文件时(例如数据库实例要求创建一个数据文件),它会以extent为单位分配空间.一旦文件被创建,ASM会传递extent映射表给数据库实例,后续数据库实例能在不和ASM实例交互的情况下访问这个文件.如果一个文件的extent需要被重新定位,比如磁盘组进行rebalance操作,ASM会告知数据库实例关于extent映射表的变更. 可以通过查询ASM实例的X$KFFXP视图来获取ASM文件extent映射表的内容.X$KFFXP视图中的每一行对应着所有处于mount状态磁盘组中每一

Oracle ASM 翻译系列第二十弹:ASM Internal ASM file number 7

ASM file number 7 ASM元信息7号文件,是ASM的逻辑卷目录,用于跟踪与ADVM有关的文件. ASM动态逻辑卷设备是由ASM动态逻辑卷构建的.一个磁盘组中可以配置一个或多个ASM动态逻辑卷设备.ASM集群文件系统通过ADVM接口构建在ASM磁盘组之上.ADVM像数据库一样,也是ASM的一个客户端.当一个逻辑卷被访问时,相应的ASM文件会被打开并且ASM extent的信息会被发送到ADVM驱动. 有两种与ADVM逻辑卷相关的文件类型: · ASMVOL:逻辑卷文件,作为逻辑卷存

Oracle ASM 翻译系列第二十二弹:ASM Internal ASM file number 8

ASM file number 8 ASM元信息8号文件是磁盘空间使用目录Used Space Directory,简称USD,它记录了每个ASM磁盘组中每个磁盘的每个zone上被使用的AU数.一个磁盘的zone包含hot zone-热区(磁盘外圈,译者注)和cold zone-冷区(磁盘内圈,译者注).USD目录为每个磁盘提供了一个条目,条目信息记录了2个zone(COLD和HOT)的AU使用数. USD结构是在11.2版本中引入的,并且与智能数据存放特性有关.USD元数据文件在ASM兼容性参

Oracle ASM 翻译系列第十五弹:ASM Internal ASM File Directory

本篇主要介绍ASM的1号文件,ASM的1号文件是ASM的文件目录,它记录了磁盘组中的所有文件信息,由于在ASM中,每一个磁盘组都是独立的存储单元,所以每一个磁盘组都会有属于它自己的文件目录. 虽然这是一个内部的文件,但ASM实例会把它当做其它ASM文件一样管理,在ASM的文件目录中也会有它自己的条目(指向了它自己),在一个normal和high冗余的磁盘组中,它也会做镜像,随着新文件的产生,文件目录的大小也会相应地增长. 每一个ASM文件目录的条目都会包含如下的信息: · 文件大小 · 文件块大