Kotlin入門(27)文件讀寫操作

Java的文件處理用到了io庫java.io,該庫雖然功能強大,但是與文件內容的交互還得通過輸入輸出流中轉,致使文件讀寫操作頗為繁瑣。因此,開發者通常得自己重新封裝一個文件存取的工具類,以便在日常開發中調用。下面是一個文件工具類的簡單Java代碼:

public class FileUtil {

//保存文本文件
public static void saveText(String path, String txt) {
try {
FileOutputStream fos = new FileOutputStream(path);
fos.write(txt.getBytes());
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

//讀取文本文件
public static String openText(String path) {
String readStr = "";
try {
FileInputStream fis = new FileInputStream(path);
byte[] b = new byte[fis.available()];
fis.read(b);
readStr = new String(b);
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
return readStr;
}
}

從上述代碼看到,僅僅是文本文件的內容保存和讀取,就得規規矩矩寫這麼多行代碼,並且還不太容易理解,對於新手來說著實不夠友好。哪裡有痛點,哪裡就有優化,所以Kotlin在文件API這塊也下了一番功夫,它以Java的io庫為基礎,利用擴展函數,添加了一些常用的文件內容讀寫方法,並且往往是一行代碼便搞定功能,絕不拖泥帶水。

比如把一段文本寫入文本文件,只消調用File對象的writeText方法,即可實現寫入文本的功能。真的只要一行代碼,就像下面這樣:

//把文本寫入文件
File(file_path).writeText(content)

如此簡潔又好用的代碼,想必是許多開發者夢寐以求的。當然了,Kotlin同樣支持其它格式的數據寫入,前面的writeText方法是覆蓋寫入文本,如果要往源文件追加文本,則可調用appendText方法。另外像圖片等二進位格式的文件,可通過位元組數組的形式寫入文件,Kotlin提供了writeBytes方法用於覆蓋寫入位元組數組,也提供了appendBytes方法用於追加位元組數組。不過由於圖像存儲比較特殊,牽涉到壓縮格式與壓縮質量,因此還得通過輸出流來處理(這是Bitmap的compress方法要求的),具體的圖片文件寫入代碼如下所示:

fun saveImage(path: String, bitmap: Bitmap) {
try {
val file = File(path)
//outputStream獲取文件的輸出流對象
//writer獲取文件的Writer對象
//printWriter獲取文件的PrintWriter對象
val fos: OutputStream = file.outputStream()
//壓縮格式為JPEG圖像,壓縮質量為80%
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos)
fos.flush()
fos.close()
} catch (e: Exception) {
e.printStackTrace()
}
}

看過了文件的寫入操作,再來看看文件的讀取操作。有了writeText方法帶好頭,Kotlin又提供了以下幾個好看且好用的文件內容讀取方法:

readText : 讀取文本形式的文件內容。

readLines : 按行讀取文件內容。返回一個字元串的List,文件有多少行,隊列中就有多少個元素。

readBytes : 讀取位元組數組形式的文件內容。

這幾個方法理解起來毫不費力,從文件中讀取全部的文本,也只要下面一行代碼便成:

//讀取文件的文本內容
val content = File(file_path).readText()

若想從圖片文件中讀取點陣圖信息,按上面的函數說明,應能調用readBytes方法。該辦法確實可行,因為Android的點陣圖工廠BitmapFactory剛好提供了decodeByteArray函數,用於從位元組數組中解析點陣圖,具體代碼如下所示:

//方式一:利用位元組數組讀取點陣圖
//readBytes讀取位元組數組形式的文件內容
val bytes = File(file_path).readBytes()
//decodeByteArray從位元組數組解析圖片
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)

之前提到將點陣圖保存為圖片文件時,通過輸出流進行處理;那麼反過來,從圖片文件讀取點陣圖數據,也可通過輸入流來完成。當然多虧了BitmapFactory的decodeStream方法,使得輸入流解析點陣圖能夠變成現實,以下便是輸入流方式讀取圖片的代碼例子:

//方式二:利用輸入流讀取點陣圖
//inputStream獲取文件的輸入流對象
val fis = File(file_path).inputStream()
//decodeStream從輸入流解析圖片
val bitmap = BitmapFactory.decodeStream(fis)
fis.close()

前兩種讀取圖片文件的方式,其實都包含兩個步驟:先從File對象獲得文件內容,再利用點陣圖工廠解碼成點陣圖。儘管這麼做也只需兩行代碼,還是不如讀取文本的一行代碼來得精鍊,對於精益求精的開發者來說,此處仍然有著改善的空間。幸好點陣圖工廠留了一手終極大招,名叫decodeFile,只要給出圖片文件的完整路徑,文件讀取和點陣圖解析的操作都一齊搞定了,具體代碼見下:

//方式三:直接從文件路徑獲取點陣圖
//decodeFile從指定路徑解析圖片
val bitmap = BitmapFactory.decodeFile(file_path)

真是想不到,光光從圖片讀取點陣圖數據這個小功能,就有至少三種方式,不但學到了Kotlin的文件讀取API,而且溫習了Android的BitmapFactory類。開發者的口味各不相同,不管個人的偏好寫法是啥,以上三種方式總有一款適合你。

寫文件和讀文件是處理單個文件,沒有太複雜的需求。倘若要求遍歷某個目錄下面的所有文本文件或者圖片文件,那可麻煩了,因為該功能的需求點可豐富了,例如要不要到子目錄和孫子目錄下搜索、文件跟文件夾都要匹配還是只匹配其中之一、篩選條件的文件擴展名都有哪些?想想這些詳細的功能點都覺得頭大,就算好不容易把符合條件的文件都挑出來,末了還得再來一個for循環完成處理操作。如果遍歷功能採用Java編碼,新手絕對無法自己寫出實現代碼,饒是高手也要頗費一番折騰。

現在有了Kotlin就方便多了,因為Kotlin把目錄遍歷這個功能重新梳理了一下,歸納為FileTreeWalk文件樹,通過給文件樹設置各式各樣的參數與條件,即可化繁為簡,輕輕鬆鬆獲取文件的搜索結果。文件樹的使用很簡單,首先調用File對象的walk方法得到FileTreeWalk實例,接著依次為該實例設置具體的條件,包括遍歷深度、是否匹配文件夾、文件擴展名,以及最後的文件隊列循環處理。心動不如行動,快來看看Kotlin的文件遍歷是怎麼實現的,下面是搜尋指定目錄下面所有文本文件的示例代碼:

var fileNames: MutableList<String> = mutableListOf()
//在該目錄下走一圈,得到文件目錄樹結構
val fileTree: FileTreeWalk = File(mPath).walk()
fileTree.maxDepth(1) //需遍歷的目錄層級為1,即無需檢查子目錄
.filter { it.isFile } //只挑選文件,不處理文件夾
.filter { it.extension == "txt" } //選擇擴展名為txt的文本文件
.forEach { fileNames.add(it.name) } //循環處理符合條件的文件

注意到以上代碼判斷文件擴展名使用了「it.extension == "txt"」,如果符合條件的擴展名只有一種那還好辦,如果符合條件的擴展名有多個又該如何是好?譬如圖片文件的擴展名既可能是png,也可能是jpg,此時若用傳統的或語句判斷固然可行,但並不雅觀,更好的辦法是利用Kotlin的in條件,即判斷文件的擴展名是否位於擴展名隊列中,形如「it.extension in listOf("png","jpg")」這樣,完整的圖片文件搜索代碼如下所示:

var fileNames: MutableList<String> = mutableListOf()
//在該目錄下走一圈,得到文件目錄樹結構
val fileTree: FileTreeWalk = File(mPath).walk()
fileTree.maxDepth(1) //需遍歷的目錄層級為1,即無需檢查子目錄
.filter { it.isFile } //只挑選文件,不處理文件夾
.filter { it.extension in listOf("png","jpg") } //選擇擴展名為png和jpg的圖片文件
.forEach { fileNames.add(it.name) } //循環處理符合條件的文件

見識了Kotlin強大的文件操作API,真教人耳目一新,如果你厭倦了Java的繁文縟節,不妨來Kotlin這裡小試身手。

推薦閱讀:

TAG:Java | Kotlin | Android |