Kotlin中的IO

博客地址sguotao.top/Kotlin-2018…html

IO流是Java中很重要的一部份內容,經常使用的數據傳輸,文件的上傳和下載都和它分不開。那麼與Java中的IO相比,Kotlin中的IO又是怎樣的呢?api

Java中的IO

Java中的IO根據處理數據的方式,能夠分爲字節流和字符流,同時根據傳輸方向的不一樣,又能夠分爲輸入流和輸出流。先來看一張Java IO的框架圖。 數組

20181102154112543525520.png
在這張圖中,整理了在Java 8中根據上述分類的IO流,其中字節輸入流有28種,字節輸出流有18種,字符輸入流有9種,字符輸出流有8種,看到這麼多的流,實際開發中常用到的只是其中的一部分。好比字節輸入流中的FileInputStream、BufferedInputStream,字節輸出流中的FileOutputStream、BufferedOutputStream,字符輸入流中的BufferedReader、InputStreamReader、FileReader,字符輸出流中的BufferedWriter、OutputStreamWriter、FileWriter等。在圖中已用黑色框圖進行了突出標註。

在Java中對流的處理,須要注意異常的處理,同時注意流的關閉操做,不然可能會引發內存溢出。好比使用BufferedReader讀取項目目錄下的build.gradle文件。app

public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader(new File("build.gradle")));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
複製代碼

Kotlin中的IO

先給出上面使用BufferedReader讀取build.gradle文件的Kotlin的幾種寫法,而後再來總結一下Kotlin中的IO。框架

fun main(args: Array<String>) {
    val file = File("build.gradle")
    val bufferedReader = BufferedReader(FileReader(file))
    var line: String

    try {
        while (true) {
            line = bufferedReader.readLine() ?: break
            println(line)
        }
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        try {
            bufferedReader.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
複製代碼

這種寫法彷佛與Java的寫法沒有什麼區別,怎麼體現Kotlin的優點呢?好在Kotlin封裝了不少高階函數,下面給出一個使用高階函數的版本。函數

fun main(args: Array<String>) {
    val file = File("build.gradle")
    BufferedReader(FileReader(file)).use {
        var line: String
        while (true) {
            line = it.readLine() ?: break
            println(line)
        }
    }
}
複製代碼

代碼精簡了很多,省略了一些對異常的捕獲,這是由於這樣的模板代碼kotlin已經封裝好了,因此不再用擔憂忘記對流進行close了。對一些小文件的讀取,還有更簡便的寫法:學習

fun main(args: Array<String>) {
    File("build.gradle").readLines().forEach(::println)
}
複製代碼

Kotlin中關於IO的擴展函數

那麼Kotlin中都提供了哪些擴展函數呢?這些擴展函數實現的效果又是怎樣的?Kotlin全部與IO相關的都放在kotlin.io這個包中,能夠看到Kotlin並無對Java中已經存在的IO流進行重複實現,而是對常常用到的一些字節流,字符流進行了擴展。這裏咱們能夠將擴展對象的不一樣,將這些擴展函數分紅如下幾類。gradle

關於擴展函數的使用,能夠將擴展函數看做是被擴展對象的成員方法,好比bufferedReader(charset: Charset)函數的被擴展對象是InputStream,那麼我麼就能夠在InputStream及其子類上調用該方法,好比:ui

val inputStream: InputStream = FileInputStream("build.gradle")
    inputStream.bufferedReader().forEachLine { line ->
        println(line)
    }
複製代碼

Kotlin對字節流的擴展

Kotlin對字節流的擴展主要位於ByteStreamKt.class中,下面分別介紹一下:this

序號 擴展函數名 被擴展對象 描述
1 buffered((bufferSize: Int) InputStream 對字節輸入流進行包裝,獲得一個帶緩衝區的字節輸入流BufferedInputStream,緩衝區默認大小爲8*1024字節。
2 bufferedReader(charset: Charset) InputStream 對字節輸入流進行包裝獲得一個帶緩衝區的字符輸入流BufferedReader,默認的字符編碼是UTF-8。
3 copyTo(out: OutputStream, bufferSize: Int ) InputStream 將字節輸入流複製到給定的輸出流,返回複製的字節數,緩衝區大小默認爲8*1024字節,須要注意的是兩個流都須要手動的close。
4 readBytes(estimatedSize: Int) InputStream 將字節輸入流讀入到一個大小不超過8*1024的字節數組中。
5 reader(charset: Charset) InputStream 對字節輸入流進行包裝獲得一個字符輸入流InputStreamReader,默認的字符編碼是UTF-8。
6 buffered(bufferSize: Int) OutputStream 對字節輸入流進行包裝獲得一個帶緩衝區的字節輸出流BufferedOutputStream,緩衝區的默認大小爲8*1024字節。
7 bufferedWriter(charset: Charset) OutputStream 對字節輸出流進行包裝獲得一個帶緩衝區的字符輸出流BufferedWriter,字符的默認編碼是UTF-8。
8 writer(charset: Charset) OutputStream 對字節輸出流進行包裝獲得一個字符輸出流OutputStreamWriter,默認的字符編碼是UTF-8。
9 inputStream() ByteArray 爲給定的字節數組建立一個字節輸入輸入流ByteArrayInputStream,來讀取該字節數組。
10 inputStream(offset: Int, length: Int) ByteArray 爲給定的字節數組建立一個字節輸入流ByteArrayInputStream,來讀取該數組,其中offset是讀取位置,這個位置是相對起始位置的偏移量,length是讀取長度。
11 byteInputStream(charset: Charset) String 爲給定的字符串建立一個字節輸入流ByteArrayInputStream,默認按UTF-8編碼。

Kotlin對字符流的擴展

Kotlin對字符流的擴展主要位於TextStreamKt.class中,咱們對這些擴展函數逐個介紹:

序號 擴展函數名 被擴展對象 描述
1 buffered(bufferSize: Int) Reader 對字符輸入流進行包裝獲得一個帶緩衝區的字符輸入流BufferedReader,緩衝區默認大小爲8*1024字節。
2 copyTo(out: Writer, bufferSize: Int) Reader 將字符輸入流複製給一個給定的字符輸出流,返回複製的字符數,緩衝區默認大小爲8*1024字節。須要注意的是兩個流須要手動的close。
3 forEachLine(action: (String) -> Unit) Reader 遍歷字符輸入流Reader讀取的每一行,同時對每一行調用傳入的函數,處理完成後會關閉流。這個傳入函數帶一個String類型的參數,沒有返回值。
4 readLines() Reader 將字符輸入流讀取的每一行數組,存入List,讀取完成後返回該List。須要注意的是不能用該函數讀取比較大的文件,不然會引發內存溢出。
5 readText() Reader 將字符輸入流讀到的內容以字符串的形式返回。須要手動關閉流。
6 useLines(block: (Sequence) -> T) Reader 將字符輸入流Reader讀取的內容存儲在一個字符序列中,在字符序列上執行傳入的lambda表達式,處理完後後會關閉流 ,將lambda表達式的返回值做爲函數的返回值。
7 buffered(bufferSize: Int) Writer 對字符輸出流進行包裝,獲得一個帶緩衝區的字符輸出流BufferedWriter,緩衝區默認大小爲8*1024字節。
8 readBytes() URL 將URL返回的內容讀取到字節數組,字節數組默認大小爲8*1024字節,須要注意不能讀取大文件,不然可能會引發內存溢出。
9 readText(charset: Charset) URL 將URL返回的內容以字符串的形式返回,默認的字符編碼是UTF-8,須要注意不能讀取大文件,不然可能會引發內存溢出。
10 reader() String 爲給定的字符串建立一個字符輸入流StringReader。

Kotlin對File的擴展

Kotlin對File的擴展主要位於FileKt.class中,下面介紹一下這些擴展函數:

序號 擴展函數 被擴展對象 描述
1 appendBytes(array: ByteArray) File 對文件追加指定字節數組大小的內容。
2 appendText(text: String, charset: Charset File 對文件追加指定內容,默認的字符編碼爲UTF-8。
3 bufferedReader(charset: Charset, bufferSize: Int ) File 對文件進行包裝,獲取一個帶緩衝區的字符輸入流,輸入流的默認編碼是UTF-8,緩衝區默認大小爲8*1024字節。
4 bufferedWriter(charset: Charset, bufferSize: Int) File 對文件進行包裝,獲取一個帶緩衝區的字符輸出流,輸出流的默認編碼是UTF-8,緩衝區默認大小爲8*1024字節。
5 copyRecursively(target: File,overwrite: Boolean, onError: (File, IOException)) File 遞歸地複製文件,該函數接收三個參數,copy文件的目的地址target,是否進行覆蓋overwrite,默認值是false不覆蓋,異常處理的onError,默認拋出異常。函數的返回值true 複製完成,複製過程當中被中斷都會返回false。若是指定的目的地址沒有文件,則建立文件;若是File指向的是單個文件,則直接複製文件到target目錄下;若是File指向的是目錄,則遞歸的複製目錄下全部的子目錄及文件到target目錄下;若是target指定的File已經存在,根據overwrite來控制是否進行覆寫操做;文件的一些屬性信息,如建立日期,讀寫權限,複製時是不進行保存的。接受一個表達式來處理異常,默認是拋出異常。須要注意的是,若是複製失敗,可能會出現部分複製的狀況。
6 copyTo(target: File, overwrite: Boolean, bufferSize: Int ) File 複製文件到指定路徑,若是指定路徑不存在文件則建立;若是存在根據overwrite參數控制是否進行覆寫;若是target指向的是一個目錄,而且overwrite設置爲true,則只有該目錄爲空時,文件纔會被複制。若是File指向的是一個目錄,那麼調用該方法,只會建立target指定的目錄,不會複製目錄的內容。最後文件的一些屬性信息,如建立日期,讀寫權限,複製時是不進行保存的。
7 deleteRecursively() File 遞歸刪除文件,若是文件指向的是目錄,會遞歸刪除目錄下的內容。須要注意的是,若是遞歸刪除失敗,可能會出現部分刪除的狀況。
8 endsWith(other: File) File 判斷文件的路徑是否以給定的文件other的路徑結尾。
9 endsWith(other: String) File 判斷文件的路徑是否與給定字符串指向的路徑在同一根目錄下,而且文件路徑以字符串結尾。
10 forEachBlock(action: (buffer: ByteArray, bytesRead: Int) -> Unit) File 該函數接收一個表達式,表達式有兩個參數,字節數組buffer和Int型bytesRead,表達式沒有返回值。函數按照字節數組(默認大小爲4096字節)的長度來讀取文件,每當讀取一字節數組的內容,就調用一次傳入的表達式。好比文件大小爲7409字節,那麼就會調用兩次表達式,第一次是在讀取4096字節時,第二次是在讀取餘下的3313字節。
11 forEachBlock(blockSize: Int, action: (buffer: ByteArray, bytesRead: Int) -> Unit) File 該函數實現的功能與第10個函數功能相同,區別是能夠指定字節數組buffer的大小。
12 forEachLine(charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit) File 該函數接收一個字符編碼參數charset和一個表達式,表達式接收一個String類型參數,沒有返回值。該函數實現按照指定字符編碼(默認是UTF-8)按行來讀取文件,每讀取一行,就調用一次傳入的表達式。該函數能夠用來讀取大文件。
13 inputStream() File 對文件進行包裝,獲得一個字節輸入流InputStream。
14 normalize() File 移除文件路徑中包含的. 而且解析..好比文件路徑爲File("/foo/./bar/gav/../baaz")那麼調用normalize()以後的結果爲File("/foo/bar/baaz")。
15 outputStream() File 對文件進行包裝,獲得一個字節輸出流OutputStream。
16 printWriter(charset: Charset) File 對文件進行包裝,獲得一個字符輸出流PrintWriter。默認字符編碼是UTF-8 。
17 readBytes() File 將文件內容讀取到字節數組中,而且返回該字節數組。該函數不建議讀取大文件,不然會引發內存溢出。函數內部限制字節數組的大小不超過2GB。
18 reader(charset: Charset) File 對文件進行包裝,獲得一個字符輸入流InputStreamReader。默認字符編碼是UTF-8。
19 readLines(charset: Charset) File 按照指定字符編碼,將文件按照行,讀入到一個LIst中,而且返回該List。默認字符編碼是UTF-8。該方法不建議讀取大文件,可能會引發內存溢出。
20 readText(charset: Charset) File 按照指定字符編碼,讀取整個文件,而且以String的類型返回讀取的內容。**該方法不建議讀取大文件,可能會引發內存溢出。**函數內部限制文件大小爲2GB。
21 relativeTo(base: File) File 計算與指定文件base的相對路徑。這裏的參數base被當作一個文件目錄,若是文件與base的路徑相同,返回一個空路徑。若是文件與base文件具備不一樣的根路徑,會拋出IllegalArgumentException。
22 File.relativeToOrNull(base: File) File 計算與指定文件base的相對路徑,若是文件路徑與base相同,返回一個空路徑,若是文件路徑與base具備不一樣的根路徑,返回null。
23 relativeToOrSelf(base: File) File 計算與指定文件base的相對路徑,若是文件路徑與base相同,返回一個空路徑,若是文件路徑與base具備不一樣的根路徑,返回文件自身。
24 resolve(relative: File) File 將指定文件relatived的路徑添加到當前文件的目錄中,若是relative文件有根路徑,返回relative文件自己,不然返回添加後的文件路徑。好比當前文件路徑File("/foo/bar")調用.resolve(File("gav"))後的結果爲File("/foo/bar/gav")。
25 resolve(relative: String) File 將指定路徑relative添加到當前文件的目錄中。
26 resolveSibling(relative: File) File 將指定文件relative的路徑添加到當前文件的上一級目錄中,若是relative文件有根路徑,返回relative文件自身,不然返回添加以後的文件路徑。好比當前文件路徑File("/foo/bar")調用.resolve("gav")後的結果爲File("/foo/bar/gav")。
27 resolveSibling(relative: String) File 將指定路徑relative添加到當前文件目錄的上一級目錄中。
28 startsWith(other: File) File 判斷當前文件是否與給定文件other是否有相同的根目錄,而且當前文件的路徑是否以指定的文件路徑開頭。
29 startsWith(other: String) File 判斷當前文件是否與跟定的路徑具備相同的根目錄,而且當前文件的路徑是否以給定的路徑開頭。
30 toRelativeString(base: File) File 計算當前文件與指定文件base的相對路徑,若是當前文件路徑與base路徑相同,返回一個空字符串,若是當前文件與base具備不一樣的根路徑,拋出IllegalArgumentException。
31 useLines(charset: Charset = Charsets.UTF_8, block: (Sequence) -> T) File 該函數接收兩個參數:一個字符編碼charset和一個表達式。默認字符編碼是UTF-8,表達式接收一個字符序列,而且返回一個泛型,表達式的返回值做爲該函數的返回值。這個函數與forEachline()函數很類似,區別是該函數返回一個字符序列,而且返回在返回字符序列時會關閉流。
32 walk(direction: FileWalkDirection = FileWalkDirection.TOP_DOWN) File 按照指定的順序(top-down、bottom-up),使用深度優先遍歷當前目錄及目錄下的內容,默認的順序是自頂向下即top-down,獲得一個文件訪問序列。
33 walkBottomUp() File 按照自底向上的順序遍歷當前目錄及目錄下的內容,該函數使用深度優先遍歷,獲得一個文件訪問序列,即先訪問文件,後訪問文件目錄的序列。
34 walkTopDown() File 按照自頂向下的順序遍歷當前目錄及目錄下的內容,該函數使用深度優先遍歷,獲得一個文件訪問序列,即先訪問文件目錄,後訪問文件的序列。
35 writeBytes(array: ByteArray) File 將指定字節數組的內容寫入文件,若是文件已經存在,則被覆寫。
36 writer(charset: Charset = Charsets.UTF_8) File 對文件包裝,獲得一個字符輸出流OutputStreamWriter,默認字符編碼是UTF-8。
37 writeText(text: String, charset: Charset = Charsets.UTF_8) File 將指定字符串內容,按照默認UTF-8的編碼方式,寫入文件,若是文件已經存在,則會被覆寫。

Kotlin其它的IO擴展

Java中IO 流就繼承了Closeable接口,在kotlin.io中的CloseableKt.class中,有一個use的擴展函數:

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}
複製代碼

use函數封裝了try...catch...finally模板代碼,這就是在kotlin中,在IO流上使用use時,不用對流進行關閉的緣由,由於kotlin已經對其進行了封裝。

在kotlin.io中,ConsoleKt.class中封裝瞭如System.out.print等終端IO的操做,在Kotlin中能夠直接使用print、println在命令行打印輸出。

學習資料

  1. Kotlin Bootcamp for Programmers
  2. Kotlin Koans
相關文章
相關標籤/搜索