+1

File I/O Trong Go

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_CREATEnghĩ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ệpfile.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.txtos.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é: image.png

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.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.