I. File I/O trong Go
Trong Go, có một số package cung cấp các công cụ và tiện ích để làm việc với file và I/O. Chúng ta sẽ tìm hiểu về các package chính và cách sử dụng chúng thông qua một số ví dụ.
Package os
Package os
cung cấp một giao diện cho các chức năng cơ bản của hệ điều hành, bao gồm việc tạo, đọc, ghi và xóa file. Nó cũng cho phép quản lý thư mục và các tác vụ liên quan. Chúng ta có thể lấy ví dụ đơn giản về việc tạo một file mới:
file, err := os.Create("data.txt")
if err != nil { // Xử lý lỗi
}
defer file.Close()
Chúng ta thử xem kĩ hơn hàm Create
trong package os
func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
Có thể thấy rằng hàm này nhận vào một chuỗi name
đại diện cho tên (hoặc đường dẫn) của tệp cần tạo, gọi đến hàm OpenFile
trả về một pointer đến một đối tượng File
và một giá trị error
.
O_RDWR|O_CREATE|O_TRUNC
: Đây là các hằng số định nghĩa cách mở tệp.O_RDWR
nghĩa là mở tệp để đọc và ghi.O_CREATE
nghĩa là tạo tệp nếu nó không tồn tại. O_TRUNC
nghĩa là nếu tệp đã tồn tại, hãy cắt bỏ nội dung của nó (xóa hay ghi đè lên).
0666
: Đây là quyền truy cập tệp. 0666 nghĩa là tất cả người dùng có thể đọc và ghi vào tệp.
Chúng ta cũng có thể mở một file bằng cách sử dụng hàm Open
file, err := os.Open("data.txt") if err != nil { // Xử lý lỗi
}
defer file.Close()
Một lần nữa hãy xem hàm Open
của package os
func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0)
}
Có thể thấy Open
cũng gọi đến hàm OpenFile
, nhưng chúng truyền vào các tham số O_RDONLY, có nghĩa là tệp sẽ được mở chỉ để đọc. Nếu tệp không tồn tại, OpenFile
sẽ trả về lỗi.
Đóng tệp
file.Close()
giúp tránh vượt quá giới hạn tệp mở của hệ điều hành, đảm bảo dữ liệu được ghi từ bộ đệm vào tệp, và ngăn chặn lỗi khi các quá trình khác truy cập tệp.
Package io
Package io
cung cấp các giao diện cơ bản cho việc đọc và ghi dữ liệu từ/đến các nguồn khác nhau, bao gồm cả file. Nó cũng cung cấp các công cụ hữu ích như io.Reader và io.Writer, ..
Ví dụ 1:
sourceFile, err := os.Open("source.txt")
if err != nil { // Xử lý lỗi
}
defer sourceFile.Close() destFile, err := os.Create("dest.txt")
if err != nil { // Xử lý lỗi
}
defer destFile.Close() _, err = io.Copy(destFile, sourceFile)
if err != nil { // Xử lý lỗi
}
Trong ví dụ này, chúng ta sử dụng hàm os.Open
để mở file nguồnsource.txt
và os.Create
để tạo file đích dest.txt
. Sau đó, chúng ta sử dụng hàm io.Copy
để sao chép dữ liệu từ file nguồn sang file đích.
Ví dụ 2:
file, err := os.Open("data.txt")
if err != nil { // Xử lý lỗi
}
data, err := io.ReadAll(file)
fmt.Println("Data: ", string(data))
Đoạn mã này mở file data.txt
và đọc toàn bộ nội dung của nó vào bộ nhớ sử dụng io.ReadAll
. Tuy nhiên, có một nhược điểm lớn với cách làm này, hãy xem hàm ReadAll của package io
này nhé:
Hàm ReadAll
đọc toàn bộ dữ liệu từ một Reader
vào một slice byte. Nó tạo một slice byte với cap (dung lượng) 512
, sau đó liên tục đọc dữ liệu từ Reader
vào slice cho đến khi không còn dữ liệu hoặc xảy ra lỗi. Nếu slice đầy, nó tăng dung lượng. Khi gặp EOF, hàm coi đó như kết thúc bình thường và trả về nil cho lỗi.
Vậy nên nếu tệp rất lớn (ví dụ: 10GB hoặc 1TB), việc đọc toàn bộ nội dung vào bộ nhớ sẽ dẫn đến lỗi tràn bộ nhớ và làm crash chương trình. Để giải quyết vấn đề này, chúng ta có thể sử dụng package bufio
để đọc tệp theo từng phần. Điều này cho phép chúng ta xử lý từng phần của tệp mà không cần đọc toàn bộ nội dung của nó vào bộ nhớ cùng một lúc.
file, err := os.Open("data.txt")
if err != nil { // Xử lý lỗi
}
defer file.Close() scanner := bufio.NewScanner(file)
for scanner.Scan() { fmt.Println("Data: ", scanner.Text())
} if err := scanner.Err(); err != nil { fmt.Println("Error: ", err)
}
Trong đoạn mã này, chúng ta sử dụngbufio.NewScanner
để tạo một Scanner mới cho tệp. Sau đó, chúng ta sử dụng phương thức Scan của Scanner để đọc từng dòng của tệp. Mỗi lần Scan trả về true, chúng ta in dòng đó ra console. Khi Scan trả về false, chúng ta kiểm tra xem có lỗi nào xảy ra trong quá trình quét tệp hay không bằng cách gọi scanner.Err().
**II. Kết Luận **
Như vậy, chúng ta đã sử dụng các package khác nhau cho File I/O trong Go. Từ tạo file mới với os
, ghi nội dung với io
, đọc file một cách hiệu quả với bufio
, hiểu rõ hơn về các package này trong việc làm việc với file. Cám ơn các bạn đã đọc bài viết này.