上一篇,Guava库学习:学习Guava Files系列(一)中,咱们简单的学习了使用Files进行文件的读写等经常使用操做,本篇咱们继续进行Guava Files系列的学习。html
InputSupplier 和 OutputSupplier 算法
Guava提供了 InputSupplier 和 OutputSupplier接口,用于提供InputStreams/Readers 或OutputStreams/Writers的处理。咱们将在接下来的文章中,看到Guava为咱们提供的这些便利,Guava一般会在打开、刷新、关 闭资源时使用这些接口。 数组
Sources 和 Sinks数据结构
Guava I/O对应于文件的读写分别提出来Sources 和Sinks 的概念,Sources 和Sinks不是那些 流,readers或writers对象,可是提供相同的做用。Sources 和Sinks 对象能够经过下面两种方式使用:app
咱们能够经过提供者检索底层流。每次提供者返回一个流,它是一个彻底新的实例,独立于任何其余可能已经返回的实例。检索底层流对象的调用者负责关闭流。学习
提供了一些基本的便利的方法用于执行咱们指望的基本的操做,如读取流或写入流。当经过Sources 和 Sinks执行读取和写入时,打开和关闭流操做交由咱们处理。测试
Sources有两种类型:ByteSource 和 CharsSource。一样,Sinks也有两种类型:ByteSink 和 CharSink。各自的Sources和Sinks类提供相似的功能,它们方法的差别只取决于咱们使用的是字符仍是原始字节。Files类提供了几种方 法来经过ByteSink和CharSink类操做文件。咱们能够经过Files类提供的静态工厂方法来建立ByteSource、ByteSink、 CharSource、CharSink实例。在咱们的例子中,咱们将专一于ByteSource和ByteSink对象,CharSource和 CharSink对象与之类似,只是使用的是字符。ui
ByteSource编码
ByteSource类表示一个可读的字节。一般状况下,咱们指望的字节来源是一个文件,但它也能够从一个字节数组读取字节。spa
咱们能够经过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; }
这是一个比较大的例子,咱们简单的梳理一下所作的步骤:
咱们建立了四个File对象,包括三个须要链接的源文件和一个输出的文件
在咱们的测试中提供了一个全部的方法getInputSuppliers(),使用了Files.newReaderSupplier静态工厂方法为每一个源文件建立InputSupplier对象
以后咱们建立InputSupplier来链接InputSupplier集合到一个逻辑InputSupplier
使用咱们第一步中建立的4个File对象,调用Files.newWriterSupplier工厂方法,建立OutputSupplier
使用了另外一个private方法joinFiles,每一个文件都调用了Files.toString方法来构造咱们测试指望的值
调用CharStreams.copy方法将提供的InputSuppliers()中的内容写入到OutputSupplier
咱们验证目标文件是否与三个原始文件包含相同的内容
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类。