Guava库学习:学习Guava Files系列(二)

Stella981
• 阅读 864

    上一篇,Guava库学习:学习Guava Files系列(一)中,我们简单的学习了使用Files进行文件的读写等常用操作,本篇我们继续进行Guava Files系列的学习。

InputSupplier 和 OutputSupplier 

    Guava提供了 InputSupplier 和 OutputSupplier接口,用于提供InputStreams/Readers 或OutputStreams/Writers的处理。我们将在接下来的文章中,看到Guava为我们提供的这些便利,Guava通常会在打开、刷新、关 闭资源时使用这些接口。 

Sources 和 Sinks

    Guava I/O对应于文件的读写分别提出来Sources 和Sinks 的概念,Sources 和Sinks不是那些 流,readers或writers对象,但是提供相同的作用。Sources 和Sinks 对象可以通过下面两种方式使用:

  • 我们可以通过提供者检索底层流。每次提供者返回一个流,它是一个完全新的实例,独立于任何其他可能已经返回的实例。检索底层流对象的调用者负责关闭流。

  • 提供了一些基本的便利的方法用于执行我们期望的基本的操作,如读取流或写入流。当通过Sources 和 Sinks执行读取和写入时,打开和关闭流操作交由我们处理。

    Sources有两种类型:ByteSource 和 CharsSource。同样,Sinks也有两种类型:ByteSink 和 CharSink。各自的Sources和Sinks类提供类似的功能,它们方法的差异只取决于我们使用的是字符还是原始字节。Files类提供了几种方 法来通过ByteSink和CharSink类操作文件。我们可以通过Files类提供的静态工厂方法来创建ByteSource、ByteSink、 CharSource、CharSink实例。在我们的例子中,我们将专注于ByteSource和ByteSink对象,CharSource和 CharSink对象与之相似,只是使用的是字符。

ByteSource

    ByteSource类表示一个可读的字节。通常情况下,我们期望的字节来源是一个文件,但它也可以从一个字节数组读取字节。

我们可以通过Files提供的静态方法为一个File对象创建ByteSource:

@Test
public void createByteSourceFromFileTest() throws Exception {
    File f1 = new File("D:\\test2.txt");
    ByteSource byteSource = Files.asByteSource(f1);
    byte[] readBytes = byteSource.read();
    assertThat(readBytes,is(Files.toByteArray(f1)));
}

    在这个例子中,我们通过Files.asByteSource方法为File对象创建ByteSource。接下来,我们展示如何通过调用read方法将ByteSource的内容读入字节数组。最后,我们断言字节数组调用read方法返回的字节数组与Files.toByteArray方法相同。

ByteSink

    ByteSink类表示一个可写的字节。我们可以将字节写入一个文件或另一个字节数组。为File对象创建ByteSink,我们可以这样做:

@Test
public void testCreateFileByteSink() throws Exception {
    File dest = new File("D:\\test.txt");
    dest.deleteOnExit();
    ByteSink byteSink = Files.asByteSink(dest);
    File file = new File("D:\\test2.txt");
    byteSink.write(Files.toByteArray(file));
    assertThat(Files.toByteArray(dest), is(Files.toByteArray(file)));
}

    上面我们创建了一个file对象,然后以创建的file实例为参数,调用静态方法Files.asByteSink。然后我们调用write方法将字节写 入它们的最终目的地。最后,我们断言文件包含预期的内容。ByteSink类上还有一个方法,我们可以编写OutputStream对象。

从ByteSource 向ByteSink 复制

    现在,我们将通过ByteSource和ByteSink类展示一个从ByteSource实例到ByteSink实例复制底层字节的例子。虽然这可能看 起来很明显,但是有一些很重要的概念。首先,我们在一个抽象级别处理ByteSource和ByteSink实例,我们真的不需要知道原始来源;其次,整 个打开和关闭的资源的操作将由我们处理:

@Test
public void copyToByteSinkTest() throws Exception {
    File dest = new File("D:\\test.txt");
    dest.deleteOnExit();
    File source = new File("D:\\test2.txt");
    ByteSource byteSource = Files.asByteSource(source);
    ByteSink byteSink = Files.asByteSink(dest);
    byteSource.copyTo(byteSink);
    assertThat(Files.toByteArray(dest), is(Files.toByteArray(source)));
}

    这里我们通过Files类中相似的静态方法创建了一个ByteSource和ByteSink实例。之后我们调用ByteSource.copyTo方法 向byteSink对象中write字节。然后我们断言新文件的内容与源文件的内容相同。同样的,ByteSink类也有一个copyTo()方法,复制字节到OutputStream。

ByteStreams 和 CharStreams

    ByteStreams是一个实用的程序类,用来处理InputStream和OutputStream实例,CharStreams则是用来处理Reader和Writer实例的程序类。ByteStreams 和 CharStreams提供了一系列的方法来直接操作文件,与Files类提供的类似。一些方法提供能够将stream或reader的全部内容复制到另 一个OutputSupplier、OutputStream或Writer实例中。在这里有很多详细的方法,接下来我们来学习几个有意思的方法。

限制inputstream的大小

ByteSteams.limit方法接收一个InputStream参数和一个long类型的值作为长度,返回一个只能读取指定长度字节的被装饰的InputStream。来看下面的例子:

@Test
public void limitByteStreamTest() throws Exception {
    File binaryFile = new File("D:\\test2.txt");
    BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(binaryFile));
    InputStream limitedInputStream = ByteStreams.limit(inputStream, 10);
    assertThat(limitedInputStream.available(), is(10));
    assertThat(inputStream.available(), is(218882));
}

    上面的例子中,我们为测试文件text2.txt创建了一个InputStream,然后我们通过ByteStreams.limit方法创建了一个限制 长度为10字节的InputStream,然后我们断言验证我们的新创建的有限InputStream正确读取的字节数是否为10,我们还断言了原始流的 大小是否更高。

连接CharStreams

    CharStreams.join方法接多个InputSupplier实例并连接它们,这样在逻辑上它们表现为一个InputSupplier实例,并写出它们的内容到一个OutputSupplier实例:

@Test
public void joinTest() throws Exception {
    File f1 = new File("D:\\test.txt");
    File f2 = new File("D:\\test1.txt");
    File f3 = new File("D:\\test2.txt");
    File joinedOutput = new File("D:\\test3.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. 我们创建了四个File对象,包括三个需要连接的源文件和一个输出的文件

  2. 在我们的测试中提供了一个所有的方法getInputSuppliers(),使用了Files.newReaderSupplier静态工厂方法为每个源文件创建InputSupplier对象

  3. 之后我们创建InputSupplier来连接InputSupplier集合到一个逻辑InputSupplier

  4. 使用我们第一步中创建的4个File对象,调用Files.newWriterSupplier工厂方法,创建OutputSupplier

  5. 使用了另一个private方法joinFiles,每个文件都调用了Files.toString方法来构造我们测试期望的值

  6. 调用CharStreams.copy方法将提供的InputSuppliers()中的内容写入到OutputSupplier

  7. 我们验证目标文件是否与三个原始文件包含相同的内容

Closer

    Guava中的Closer类被用来确保所有注册Closeable对象在Closer.close方法被调用时正确的关闭。Closer类与Java 7 中 try-with-resources语法行为类似,但可以在Java 6环境中使用。使用Closer类非常简单,通过如下方式:

@Test
public void testCloser() throws IOException {
    Closer closer = Closer.create();
    try {
        File destination = new File("D:\\test.txt");
        destination.deleteOnExit();
        BufferedReader reader = new BufferedReader(new FileReader("D:\\test2.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();
    }
}

    在上面的例子中, 我们简单的设置复制一个文本文件。首先,我们创建了一个Closer实例,之后创建BufferedReader和BufferedWriter,然后将 这些对象注册给创建的Closer实例。我们需要注意在这里提到的所有方法都使用InputSupplier和OutputSupplier,通过 Closer类来管理底层I/O资源的关闭,Guava建议,在进行原始I/O流、readers、writers操作时,最好使用Sources和 Sinks。

BaseEncoding

    在处理二进制数据时,我们有时需要将表示数据的字节转换成可打印的ASCII字符。当然,我们也需要能够将转成原始解码编码字节形式。BaseEncoding是一个抽象类,包含许多静态工厂方法,能够为不同编码方法创建实例。最简单的形式,我们可以通过如下方式使用BaseEncoding类:

@Test
public void encodeDecodeTest() throws Exception {
    File file = new File("D:\\test2.txt");
    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));
}

这里我们使用二进制文件和将字节编码为一个base64 编码的字符串。我们断言这是完全由ASCII字符组成的字符串。然后我们将编码的字符串转换回字节,并断言其等于我们原始的字节。但 BaseEncoding类为我们提供了更多的灵活性,不仅仅是简单的编码和解码字节数组。我们可以装饰OutputSuplier、ByteSink、 和Writer实例,这样字节会被编码成和它们写入时的一样。反过来,我们也可以将IntputStream、ByteSource和Reader实例解 码成字符串。看下面一个例子:

@Test
public void encodeByteSinkTest() throws Exception {
    File file = new File("D:\\test2.txt");
    File encodedFile = new File("D:\\test3.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)));
}

    上面的例子中,我们创建了一个File对象,一个提供二进制文件,另外的是我们准备复制的原始副本。接下来通过file对象创建了一个CharSink实 例。之后,创建了BaseEncoding实例进行base64算法的编码和解码。我们使用BaseEncoding实例来装饰之前在ByteSink构 造的CharSink,因此字节将自动的被编码成和它们写入时一样。之后为我们的目标文件创建了ByteSource实例并复制字节到我们的 ByteSink。然后我们断言,我们原始文件的字节编码与目标文件转换成的字符串一致。

Summary

    我们学习了Guava是如何通过使用InputSupplier和OutputSupplier打开或关闭我们的I/O资源。也看到了如何使用 ByteSource、ByteSink、CharSource和CharSink类。最后,我们学习了使用BaseEncoding类将二进制数据转为 文本。在下一个系列中,我们将事情由散列类以及BloomFilter数据结构,以及避免空指针的Optional类。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
PPDB:今晚老齐直播
【今晚老齐直播】今晚(本周三晚)20:0021:00小白开始“用”飞桨(https://www.oschina.net/action/visit/ad?id1185)由PPDE(飞桨(https://www.oschina.net/action/visit/ad?id1185)开发者专家计划)成员老齐,为深度学习小白指点迷津。
Souleigh ✨ Souleigh ✨
3年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
Stella981 Stella981
3年前
Guava库学习:学习Guava EventBus(二)EventBus 事件订阅示例
    原文地址:Guava库学习:学习GuavaEventBus(二)EventBus事件订阅示例(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fwww.xx566.com%2Fdetail%2F215.html)上一篇Guava库学习:学习GuavaEventBus(一
Stella981 Stella981
3年前
Guava库学习:Guava 零碎知识
    这将是Guava库学习系列的最后一篇,但是仍然包含许多零零碎碎的知识。虽然不可能覆盖所有Guava涉及的知识,但我们会竭尽所能。本篇将会介绍一些Guava中有用的工具,并不需要再开一个系列。本篇学习的一些工具可能并不会经常使用,但当你需要时,它是必不可少的。接下来,开始本篇的学习。本篇,我们将主要学习以下内容:Hashing、BloomFilter
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
3年前
Guava库学习:学习Guava Files系列(一)
  对程序开发人员来说,文件的读写是很重要的一项技能。但是令人惊讶的是,尽管Java提供了一个丰富而健壮的I/O库,进行一些基本的文件操作却显得很繁琐。不过在Java7中已经发生了一些改变,但那些使用Java6的就不那么好运了。幸运的是,Guava做了一些我们期望I/O库做的事情,提供了一系列的工具,让我们能够更方便的进行I/O操作。本篇,我们就开
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这