1. Tai nguyen: vi sao can dong file
Khi ban doc file bang readText() hoac readLines(), cam giac nhu do la “phep thuat” kieu “dua toi mot dong”. Nhung o ben trong he thong, mot viec rat “vat ly” dang xay ra: he dieu hanh cap cho tien trinh cua ban mot tai nguyen — file descriptor/stream dang mo. Trong khi tai nguyen con mo, OS giu ket noi “chuong trinh ↔ file” va cho chuong trinh lich su noi: “xong roi, ban co the dong”.
Tai nguyen co mot tinh chat kho chiu: neu ban khong dong no, no khong bien mat “tu nhien” dung vao luc ban thay tien. No co the dong muon hon (khi garbage collector chay), hoac khong dong du nhanh de gay ra van de ngay bay gio. Vi vay lam viec voi file luon la cau chuyen ve ky luat: mo → lam viec → dong, va tot nhat la khong co “oi, minh quen”.
De “dong dinh” y tuong bang hinh anh, ban co the nghi nhu sau:
flowchart TD
A["Mo file (Reader/Writer/Stream)"] --> B["Doc / ghi du lieu"]
B --> C["Dong tai nguyen (close)"]
B -->|Neu xay ra loi| D["Ngoai le"]
D --> C
Y chinh: viec dong phai xay ra ca khi “thanh cong” lan khi “bi loi”.
2. Hai cach dong tai nguyen
Goi close() thu cong
O nhung buoc dau, rat de tin rang ban co the lam the nay: mo BufferedReader, doc, roi cuoi cung goi close(). Tren giay thi dep. Trong code thuc te — bat dau co “cac nhanh so phan”: return som, ngoai le, break, continue, them mot return nua vi “o day loi roi, khoi can di tiep”.
Va roi o mot nhanh nao do, ban thoat khoi ham som hon truoc khi kip toi close(). Hoac giua luc doc tu nhien bay ra ngoai le — va close() cung khong duoc goi. Ket qua la file bi treo mo, con ban nhin loi va nghi: “Ai do da an cap descriptor cua minh, nhung la ai?”
Cach tin cay, nhung “dai dong” hon — la try/finally. No dam bao finally se chay ke ca khi co ngoai le, va day la y tuong nen tang.
Vi du nho voi “tai nguyen hoc tap”:
class FakeResource {
fun work() {
println("Working...") // Working...
error("Boom") // mo phong su co
}
fun close() {
println("Closed") // Closed
}
}
fun main() {
val r = FakeResource()
try {
r.work()
} finally {
r.close()
}
}
Chay duoc khong? Co. Dep khong? Tam coi: giong nhu mang theo tua-vit de mo chai. Lam duoc, nhung muon co “nut bam”.
“Nut bam” do trong Kotlin chinh la use { ... }.
use {}: no la gi va dam bao dieu gi
use { ... } la cach lam “idiomatic” trong Kotlin (giong Java try-with-resources, chi la khong can cu phap rieng). Y tuong khong doi: ban dua cho Kotlin mot tai nguyen va mot khoi code, Kotlin chay khoi code do va sau do tu dong dong tai nguyen — bat ke khoi ket thuc binh thuong hay bi nem ngoai le.
Mot chi tiet quan trong nua: use la ham tong quat (generic), ap dung duoc cho cac tai nguyen co the dong. Tức la no hoat dong voi cac kieu thoa man “hop dong dong duoc” (vi du Closeable).
Ve mat cam nhan, ban co the doc code nay nhu sau: “day la tai nguyen, hay dung no trong block, roi dong lai”.
So sanh try/finally va use (khong phai “tot/xau”, ma la “ban phai viet tay gi”):
| Lam gi | |
|
|---|---|---|
| Dam bao dong | Co | Co |
| Code dai hon | Thuong la co | Thuong la khong |
| Dong o dau | Trong finally | Tu dong sau khi ket thuc block |
| Rui ro “quen close” | Co | Gan nhu khong (neu ban viet ngay .use { ... }) |
Vi du nho voi file that (y tuong, khong xu ly loi):
import java.io.File
fun main() {
val firstLine = File("data/input.txt")
.bufferedReader()
.use { br -> br.readLine() }
println(firstLine) // vi du: Hello
}
O day BufferedReader se duoc dong tu dong sau khi thoat khoi block use.
3. Doc file bang bufferedReader().use { ... }
Truoc toi nay, ban da doc file bang nhung “phuong thuc ngan” nhu readText() va readLines(). Chung rat tuyet khi file nho va ban thuc su muon lay toan bo noi dung vao bo nho. Nhung khi ban muon “doc dan” (vi du: tim dong dau tien phu hop hoac tinh thong ke), thi se tien hon neu mo reader va doc tung dong.
De lam dieu do can bufferedReader(): no cho ban mot doi tuong co readLine(). Va de khong phai lo viec dong — ta boc ngay bang use.
Vi du: doc dong dau tien khong rong
import java.io.File
fun main() {
val firstNonBlank = File("data/input.txt").bufferedReader().use { br ->
var line: String?
while (true) {
line = br.readLine() ?: break
if (line.isNotBlank()) return@use line.trim()
}
null
}
println(firstNonBlank) // vi du: Title
}
Chu y return@use: day la “return khoi lambda”, khong phai khoi main. Toi co y dung nhan de hanh vi ro rang toi da, ke ca khi ban da biet ve inline va non-local return.
Vi du: dem so dong ma khong load ca file
import java.io.File
fun main() {
val count = File("data/input.txt").bufferedReader().use { br ->
var lines = 0
while (br.readLine() != null) lines++
lines
}
println("lines=$count") // lines=42
}
Phong cach then chot: ben trong use ta chi lam viec voi reader. Ngay khi ban bat dau xay dung nua phan business logic cua ung dung ngay trong use, code se nang hon de doc va debug. use la “hanh lang ngan dan toi tai nguyen”, khong phai “phong hop 3 gio”.
4. Ghi file bang bufferedWriter().use { ... }
writeText() va appendText() cung rat on: ngan va de hieu. Nhung doi khi ban muon ghi theo tung doan: tieu de, roi cac dong, roi tong ket, roi xuong dong. Ban co the ghep chuoi truoc (vi du qua StringBuilder), nhung doi khi don gian va “that tha” hon la ghi theo luc tao ra.
Khi do, tien nhat la lay bufferedWriter() va lai boc no trong use. Trong Kotlin, use dong tai nguyen tu dong, va viec dong writer thuong kem theo “ghi not” buffer cuoi (ban khong can tu goi flush() trong cac vi du hoc tap). Y tuong “da dong thi chac chan da ghi xong” rat de chiu o day.
Vi du: ghi mot bao cao nho theo tung dong.
import java.io.File
fun main() {
val out = File("data/reports/summary.txt")
out.parentFile?.mkdirs()
out.bufferedWriter().use { w ->
w.write("Report\n")
w.write("items=3\n")
w.write("ok=true\n")
}
}
Neu trong luc ghi bat ngo co ngoai le, use van se co gang dong writer. Day chinh la dam bao “cleanup khi co loi” ma vi no chung ta moi bat dau bai nay.
Mot chi tiet rieng ve xuong dong: write() khong tu them "\n", nen ban tu kiem soat dinh dang. Dieu nay tot: khong co xuong dong “ma thuat”, nhung trach nhiem la cua ban (dung, lap trinh vien la nguoi tu dat "\n" de sau nay khong khoc).
5. useLines{}: dong chuoi lazy va ranh gioi cua block
Doi khi ban muon su tien loi cua xu ly tung dong, nhung khong muon tu tay lam BufferedReader va while. Khi do co useLines { ... }. No mo file, dua cho ban “dong chay cac dong” (ve y nghia — mot sequence lazy), va sau khi block ket thuc se dam bao dong file.
No giong nhu: “minh muon lam viec voi cac dong nhu voi mot collection, nhung khong muon giu ca file trong RAM”. Ben trong block, ban thuong lam cac thao tac nhu count, filter, map, firstOrNull... va nhan ve gia tri ket qua.
Vi du: dem cac dong khong rong.
import java.io.File
fun main() {
val nonBlank = File("data/input.txt").useLines { lines ->
lines.count { it.isNotBlank() }
}
println("nonBlank=$nonBlank") // nonBlank=10
}
Vi du: tim dong dau tien giong tieu de (vi du bat dau bang "#").
import java.io.File
fun main() {
val header = File("data/input.txt").useLines { lines ->
lines.firstOrNull { it.trim().startsWith("#") }
}
println(header) // vi du: # Shopping list
}
Bay gio — y quan trong nhat, xung dang de dung lai.
useLines dong file sau khi block ket thuc. Nghia la ban khong the tra “lines” ra ben ngoai va tiep tuc doc chung sau do — file da dong. Day la loi logic rat thuong gap: ban tuong la minh tra ve mot Sequence, nhung Sequence khong co du lieu “ben trong”; no keo du lieu tu file khi iteration. Con file, tai thoi diem do, da dong.
Chien luoc dung rat don gian: hoac tinh toan ngay ben trong useLines va tra ve ket qua san (so/chuoi/danh sach), hoac dung readLines() neu ban thuc su can danh sach dong trong bo nho.
6. Vi du thuc te: luu va tai cac khoan chi
Tu do tro di, trong ung dung hoc tap, viec lam mot kho luu tru text don gian tro nen rat tu nhien. Khong phai “co so du lieu”, khong phai “dinh dang hoan hao”, ma la mot file that co the mo bang Notepad va doc bang mat. Ta khong ban ve encoding va dinh dang phuc tap — hom nay muc tieu la phong cach I/O an toan.
Gia su trong project da co model khoan chi:
data class Expense(
val amount: Int,
val category: String,
val note: String
)
Ta tao dinh dang dong rat don gian: "amount;category;note". Dung, dau cham phay khong “ly tuong”, nhung de parse bang split(";").
Serialize (doi tuong → chuoi):
fun serializeExpense(e: Expense): String {
val safeNote = e.note.replace(";", ",")
return "${e.amount};${e.category};$safeNote"
}
Parse (chuoi → doi tuong hoac null neu dong loi):
fun parseExpenseOrNull(line: String): Expense? {
val parts = line.split(";")
if (parts.size < 3) return null
val amount = parts[0].toIntOrNull() ?: return null
return Expense(amount, parts[1], parts[2])
}
Gio toi phan hay nhat: tai danh sach khoan chi bang useLines, de file tu dong dong, va viec xu ly giong lam voi collection.
import java.io.File
fun loadExpenses(file: File): List<Expense> {
if (!file.exists() || !file.isFile) return emptyList()
return file.useLines { lines ->
lines.mapNotNull { parseExpenseOrNull(it.trim()) }.toList()
}
}
Chu y phong cach: ta tra ra ben ngoai mot List<Expense> da san sang. File dong ngay sau block, va do chinh la hanh vi ta can.
Ghi danh sach khoan chi bang bufferedWriter().use { ... }:
import java.io.File
fun saveExpenses(file: File, items: List<Expense>) {
file.parentFile?.mkdirs()
file.bufferedWriter().use { w ->
for (e in items) w.write(serializeExpense(e) + "\n")
}
}
Va mot doan nho trong main de thay moi thu “ket noi” voi nhau (khong lam phuc tap giao dien dong lenh):
import java.io.File
fun main() {
val storage = File("data/expenses.txt")
val items = loadExpenses(storage)
println("Loaded: ${items.size}") // Loaded: 0 (neu file chua ton tai)
saveExpenses(storage, items + Expense(120, "food", "coffee"))
}
O day “phep thuat” khong phai la ta luu duoc khoan chi (truoc do ta cung lam duoc bang list), ma la code I/O khong tao ra “qua bom hen gio”: tai nguyen se luon duoc dong, ke ca neu gi do bi sap giua chung. Chinh dieu nay lam use tro thanh mot phan quan trong cua phong cach lap trinh truong thanh.
7. Loi thuong gap khi dung use {} va Reader/Writer
Loi so 1: mo bufferedReader() va quen .use { ... }.
Dieu nay thuong xay ra khi ban “thu nhanh y tuong” va viet val br = file.bufferedReader(), roi bat dau doc. Khi chuong trinh con nho — ban chua thay van de. Sau do co ngoai le hoac thoat som, close() khong duoc goi, va bat dau co cac hien tuong la: file “bi khoa”, du lieu “chua ghi xong”, tren Windows thi “khong xoa duoc file”. Cach chua la tao thoi quen: thay bufferedReader()/bufferedWriter() la tay tu dong viet them .use { ... }.
Loi so 2: co gang tra ve lines tu useLines ra ben ngoai.
Day la bay logic: tuong nhu ban da tra ve “cac dong”, nhung thuc ra ban tra ve co che doc tu file. Con file bi dong ngay sau block useLines. Cach dung — ngay trong useLines hay dem/loc/gom ket qua va tra ve gia tri da hoan chinh, vi du Int, String hoac List<String>.
Loi so 3: viet qua nhieu business logic ben trong use-block.
use nen la “diem tiep xuc hep voi tai nguyen”: doc/ghi — roi thoat. Neu ben trong block ban bat dau cap nhat hang dong cau truc, parse lenh, tao bao cao va con print ra console, thi khi debug ban mat di quy tac don gian: “trong use — chi I/O”. Code se tro nen dai dan va kho test hon.
Loi so 4: quen "\n" khi ghi qua write().
Sau khi dung appendText(), nhieu nguoi quen rang “the nao cung tu xu ly”. Nhung writer.write() ghi chinh xac nhung gi ban dua vao. Neu ban quen "\n", cac dong se dinh vao nhau thanh mot dong, va sau do parser cua ban se trung thuc noi: “khong phai 3 phan, ma la 1” — va no dung. Vi vay khi ghi theo tung dong, hoac la them "\n" bang tay, hoac la dung quy uoc dinh dang (vi du “moi thuc the la mot dong rieng”).
Loi so 5: nham lan giua “dong tai nguyen” va “xu ly loi”.
use dam bao dong tai nguyen. Nhung no khong bien loi thanh thanh cong va khong tu dong “nuot” ngoai le. Neu file khong ton tai hoac khong du quyen — ban se nhan ngoai le tai noi thuc thi I/O. Vi vay hay ghi nho tach bach trach nhiem: use chi chiu trach nhiem dong, con xu ly loi I/O la mot chu de va mot thiet ke rieng (va ban da biet nen tang qua try/catch, nhung dung tron het vao mot cho).
GO TO FULL VERSION