上一篇,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類。