博客地址sguotao.top/Kotlin-2018…html
IO流是Java中很重要的一部份內容,經常使用的數據傳輸,文件的上傳和下載都和它分不開。那麼與Java中的IO相比,Kotlin中的IO又是怎樣的呢?api
Java中的IO根據處理數據的方式,能夠分爲字節流和字符流,同時根據傳輸方向的不一樣,又能夠分爲輸入流和輸出流。先來看一張Java IO的框架圖。 數組
在這張圖中,整理了在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();
}
}
}
複製代碼
先給出上面使用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中都提供了哪些擴展函數呢?這些擴展函數實現的效果又是怎樣的?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對字節流的擴展主要位於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對字符流的擴展主要位於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的擴展主要位於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的編碼方式,寫入文件,若是文件已經存在,則會被覆寫。 |
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在命令行打印輸出。