Ghi và đọc file là một trong những hoạt động phổ biến nhất sử dụng trong các ngôn ngữ lập trình. Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu cách đọc và ghi file hiệu quả sử dụng ngôn ngữ lập trình Go.
Phương thức chúng ta sử dụng để đọc và ghi các file sẽ theo một định dạng không xác định. Nghĩa là chúng ta sẽ không sử dụng những kỹ thuật để xử lý đọc các file .csv, .txt, .xls…
Đọc file
Mục đích để đọc các file trong hệ thống của bạn, chúng ta phải sử dụng module io/ioutil
. Đầu tiên, chúng ta sẽ phải lấy nội dung của một file vào trong bộ nhớ bằng cách gọi ioutil.ReadFile("/path/to/my/file.ext")
mà sẽ nhận vào một đường dẫn đến file mà bạn muốn đọc (Nó chỉ là một tham số truyền vào). Hàm này sẽ trả về dữ liệu của file hoặc một err
mà bạn có thể xử lý giống như một lỗi ở trong Go.
Chúng ta tạo một file mới main.go
cũng với một file tên là localfile.data
. Thêm ngẫu nhiên các đoạn text vào trong file .data
. Hãy cùng xem ví dụ sau đây.
package main // thêm vào 2 module chúng ta cần import ( "fmt" "io/ioutil" ) func main() { // Đọc nội dung trong localfile.data data, err := ioutil.ReadFile("localfile.data") // Nếu chương trình không thể đọc file // in ra nguyên nhân tại sao if err != nil { fmt.Println(err) } // Nếu đọc file thành công thì // in ra nội dung như một string fmt.Print(string(data)) }
Kết quả
this has all my content%
Như bạn có thể thấy, chúng ta đã quản lý thành công việc đọc tất cả dữ liệu lưu trữ bên trong file localfile.data
Ghi và tạo mới file
Chúng ta đã hiểu được cách đọc một file, tiếp theo hãy xem thử cách tạo và ghi vào một file mới
Mục đích để ghi nội dung vào file trong Go, chúng ta sẽ vẫn phải sử dụng module io/ioutil
. Chúng ta đầu tiên sẽ phải xây dựng một mảng byte biểu thị nội dung mà chúng ta mong muốn lưu vào file.
mydata := []byte("all my data I want to write to a file")
Sau khi tạo mảng byte này, chúng ta sau đấy có thể gọi hàm ioutil.WriteFile()
để ghi mảng byte này vào file. Phương thức WriteFile()
nhận 3 tham số khác nhau, tham số đầu tiên là đường dẫn ghi file, tham số thứ 2 là object mydata
, và tham số cuối cùng là FileMode
, tượng trưng cho mode và quyền của file.
// Phương thức WriteFile trả về lỗi nêú không thành công err := ioutil.WriteFile("myfile.data", mydata, 0777) // xử lý lỗi này if err != nil { // in ra thông tin lỗi fmt.Println(err) }
Nào hãy tiếp tục cập nhập thêm vào file main.go
để không chỉ đọc mà còn thực hiện ghi thông tin và tạo file trước.
package main import ( "fmt" "io/ioutil" ) func main() { mydata := []byte("All the data I wish to write to a file") // Phương thức WriteFile trả về lỗi nêú không thành công err := ioutil.WriteFile("myfile.data", mydata, 0777) // xử lý lỗi này if err != nil { // in ra thông tin lỗi fmt.Println(err) } data, err := ioutil.ReadFile("myfile.data") if err != nil { fmt.Println(err) } fmt.Print(string(data)) }
Sau khi chạy đoạn code trên, chúng ta sẽ thấy kết quả là một file mới được sinh ra tự động trong thư mục hiện tại là myfile.data
, sau đó chương trình sẽ đọc file mới được tạo ra và in kết quả ra console.
All the data I wish to write to a file
Ghi vào file có sẵn
Tiếp theo hãy cùng xem ví dụ sau đây thực hiện ghi thông tin vào file có sẵn.
package main import ( "fmt" "io/ioutil" "os" ) func main() { mydata := []byte("All the data I wish to write to a file\n") // Phương thức WriteFile trả về lỗi nêú không thành công err := ioutil.WriteFile("myfile.data", mydata, 0777) // xử lý lỗi này if err != nil { // in ra thông tin lỗi fmt.Println(err) } data, err := ioutil.ReadFile("myfile.data") if err != nil { fmt.Println(err) } fmt.Print(string(data)) f, err := os.OpenFile("myfile.data", os.O_APPEND|os.O_WRONLY, 0600) if err != nil { panic(err) } defer f.Close() if _, err = f.WriteString("new data that wasn't there originally\n"); err != nil { panic(err) } data, err = ioutil.ReadFile("myfile.data") if err != nil { fmt.Println(err) } fmt.Print(string(data)) }
Kết quả
All the data I wish to write to a file new data that wasn't there originally
Chúng ta đã có thêm vào thành công đoạn text vào một file có sẵn sử dụng os.OpenFile
và f.WriteString()
File Permission
Nó cực kỳ là quan trọng để hiểu quyền truy cập đến các file khác nhau khi bạn thực hiện ghi một file mới.
Ghi đồng thời vào file
Khi có nhiều goroutine cùng đồng thời ghi vào một file, chúng ta sẽ lại gập vấn đề liên quan đến Data Race. Nên việc ghi đồng thời vào một file nên được phối hợp sử dụng channel.
Chúng ta sẽ thử viết một chương trình tạo 100 goroutine. Mỗi một goroutine này sẽ sinh ra ngẫu nhiên một số đồn thời, do đó kết quả sẽ sinh ra hàng trăm số ngẫu nhiên. Những số ngẫu nhiên này sẽ được ghi vào một file. Chúng ta sẽ giải quyết vấn đề này bằng cách sử dụng cách tiếp cận như sau
- Tạo một channel dùng để đọc và ghi các số ngẫu nhiên được sinh ra
- Tạo ra 100 nhà sản xuất (
produce
) goroutine. Mỗi goroutine sẽ sinh ra một số ngẫu nhiên và cùng ghi số ngẫu nhiên đó vào channel - Tạo một khách hàng (
consume
) goroutine sẽ thực hiện đọc từ channel và ghi các số ngẫu nhiện này vào file. Do chúng ta chỉ có duy nhất một goroutine thực hiện ghi vào một file do đó tránh được điều kiện Race. - Đóng file khi hoàn thành
Trước tiên hãy viết hàm produce để sinh ra số ngẫu nhiên
func produce(data chan int, wg *sync.WaitGroup) { n := rand.Intn(999) data <- n wg.Done() }
Hàm bên trên sẽ sinh ra một số ngẫu nhiên và ghi vào channel data
và sau đó gọi hàm Done
trong WaitGroup
để thông báo rằng nó đã hoàn thành công việc của mình.
Hãy chuyển sang hàm ghi dữ liệu vào file
func consume(data chan int, done chan bool) { f, err := os.Create("concurrent") if err != nil { fmt.Println(err) return } for d := range data { _, err = fmt.Fprintln(f, d) if err != nil { fmt.Println(err) f.Close() done <- false return } } err = f.Close() if err != nil { fmt.Println(err) done <- false return } done <- true }
Hàm consume
tạo một file tên là concurrent
, nó sau đó sẽ đọc các số ngẫu nhiên từ channel data
, và ghi vào file. Khi nó đã hoàn thành việc đọc và ghi toàn bộ các số ngẫu nhiên, nó sẽ ghi true
vào channel done
để thông báo rằng nó đã hoàn thành xong nhiệm vụ của mình.
Sau đây là toàn bộ chương trình
package main import ( "fmt" "math/rand" "os" "sync" ) func produce(data chan int, wg *sync.WaitGroup) { n := rand.Intn(999) data <- n wg.Done() } func consume(data chan int, done chan bool) { f, err := os.Create("concurrent") if err != nil { fmt.Println(err) return } for d := range data { _, err = fmt.Fprintln(f, d) if err != nil { fmt.Println(err) f.Close() done <- false return } } err = f.Close() if err != nil { fmt.Println(err) done <- false return } done <- true } func main() { data := make(chan int) done := make(chan bool) wg := sync.WaitGroup{} for i := 0; i < 100; i++ { wg.Add(1) go produce(data, &wg) } go consume(data, done) go func() { wg.Wait() close(data) }() d := <-done if d == true { fmt.Println("File written successfully") } else { fmt.Println("File writing failed") } }
Hàm main
sẽ tạo channel data
mà từ đó các số ngẫu nhiên được đọc và ghi vào. Channel done
được sử dụng bởi goroutine consume
để thông báo cho main
rằng nó đã hoàn thành nhiệm vụ của mình. Waitgroup wg
được sử dụng để chờ cho tới khi 100 goroutine hoàn thành việc tạo ra các số ngẫu nhiên.
Vòng lặp for
được dùng để tạo ra 100 goroutine. Một cuộc gọi goroutine gọi hàm wait()
trong waitgroup
thực hiện đợi cho tới khi tất cả các goroutine hoàn thành việc sinh ra các số ngẫu nhiên. Sau đó nó thực hiện đóng channel. Khi channel được đóng, và consume
goroutine thực hiện xong việc ghi tất cả các số ngẫu nhiên này vào file, nó thực hiện ghi true
vào channel done
, khi nó hàm main
sẽ không bị chặn nữa và in ra File written successfully
.