Go – Đọc và ghi file

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

  1. Tạo một channel dùng để đọc và ghi các số ngẫu nhiên được sinh ra
  2. 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
  3. 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.
  4. Đó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.

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.