Đọc tệp văn bản thành mảng chuỗi (và ghi)


100

Tôi tin rằng khả năng đọc (và ghi) một tệp văn bản vào và ra khỏi một mảng chuỗi là một yêu cầu khá phổ biến. Nó cũng khá hữu ích khi bắt đầu với một ngôn ngữ loại bỏ nhu cầu ban đầu để truy cập cơ sở dữ liệu. Có ai tồn tại ở Golang không?
ví dụ

func ReadLines(sFileName string, iMinLines int) ([]string, bool) {

func WriteLines(saBuff[]string, sFilename string) (bool) { 

Tôi muốn sử dụng một cái hiện có hơn là bản sao.


2
Sử dụng bufio.Scanner để đọc các dòng từ một tập tin, xem stackoverflow.com/a/16615559/1136018golang.org/pkg/bufio
Jack Valmadre

Câu trả lời:


123

Kể từ phiên bản Go1.1, có một API bufio.Scanner có thể dễ dàng đọc các dòng từ một tệp. Hãy xem xét ví dụ sau từ trên, được viết lại bằng Máy quét:

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var lines []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }
    return lines, scanner.Err()
}

// writeLines writes the lines to the given file.
func writeLines(lines []string, path string) error {
    file, err := os.Create(path)
    if err != nil {
        return err
    }
    defer file.Close()

    w := bufio.NewWriter(file)
    for _, line := range lines {
        fmt.Fprintln(w, line)
    }
    return w.Flush()
}

func main() {
    lines, err := readLines("foo.in.txt")
    if err != nil {
        log.Fatalf("readLines: %s", err)
    }
    for i, line := range lines {
        fmt.Println(i, line)
    }

    if err := writeLines(lines, "foo.out.txt"); err != nil {
        log.Fatalf("writeLines: %s", err)
    }
}

124

Nếu tập tin là không quá lớn, điều này có thể được thực hiện với sự ioutil.ReadFilestrings.Splitchức năng giống như vậy:

content, err := ioutil.ReadFile(filename)
if err != nil {
    //Do something
}
lines := strings.Split(string(content), "\n")

Bạn có thể đọc tài liệu về gói ioutilchuỗi .


5
Nó đọc toàn bộ tệp trong bộ nhớ, có thể là một vấn đề nếu tệp lớn.
jergason

22
@Jergason, đó là lý do ông bắt đầu câu trả lời của mình với "Nếu tập tin không phải là quá lớn ..."
laurent

9
ioutil có thể được nhập dưới dạng"io/ioutil"
Pramod

7
Lưu ý các chuỗi.Split sẽ nối thêm một dòng (một chuỗi trống) khi phân tích cú pháp ví dụ
bain

1
FYI, trong Windows, điều này sẽ không loại bỏ \r. Vì vậy, bạn có thể có một \rnối vào mọi phần tử.
matfax

32

Không thể cập nhật câu trả lời đầu tiên.
Dù sao, sau khi phát hành Go1, có một số thay đổi đột phá, vì vậy tôi đã cập nhật như hình dưới đây:

package main

import (
    "os"
    "bufio"
    "bytes"
    "io"
    "fmt"
    "strings"
)

// Read a whole file into the memory and store it as array of lines
func readLines(path string) (lines []string, err error) {
    var (
        file *os.File
        part []byte
        prefix bool
    )
    if file, err = os.Open(path); err != nil {
        return
    }
    defer file.Close()

    reader := bufio.NewReader(file)
    buffer := bytes.NewBuffer(make([]byte, 0))
    for {
        if part, prefix, err = reader.ReadLine(); err != nil {
            break
        }
        buffer.Write(part)
        if !prefix {
            lines = append(lines, buffer.String())
            buffer.Reset()
        }
    }
    if err == io.EOF {
        err = nil
    }
    return
}

func writeLines(lines []string, path string) (err error) {
    var (
        file *os.File
    )

    if file, err = os.Create(path); err != nil {
        return
    }
    defer file.Close()

    //writer := bufio.NewWriter(file)
    for _,item := range lines {
        //fmt.Println(item)
        _, err := file.WriteString(strings.TrimSpace(item) + "\n"); 
        //file.Write([]byte(item)); 
        if err != nil {
            //fmt.Println("debug")
            fmt.Println(err)
            break
        }
    }
    /*content := strings.Join(lines, "\n")
    _, err = writer.WriteString(content)*/
    return
}

func main() {
    lines, err := readLines("foo.txt")
    if err != nil {
        fmt.Println("Error: %s\n", err)
        return
    }
    for _, line := range lines {
        fmt.Println(line)
    }
    //array := []string{"7.0", "8.5", "9.1"}
    err = writeLines(lines, "foo2.txt")
    fmt.Println(err)
}

18

Bạn có thể sử dụng os.File (triển khai giao diện io.Reader ) với gói bufio cho việc đó. Tuy nhiên, các gói đó được xây dựng với lưu ý đến việc sử dụng bộ nhớ cố định (bất kể tệp có dung lượng lớn như thế nào) và khá nhanh.

Thật không may, điều này làm cho việc đọc toàn bộ tệp vào bộ nhớ phức tạp hơn một chút. Bạn có thể sử dụng bộ đệm byte để nối các phần của dòng nếu chúng vượt quá giới hạn dòng. Dù sao, tôi khuyên bạn nên thử sử dụng trình đọc dòng trực tiếp trong dự án của bạn (đặc biệt nếu bạn không biết tệp văn bản lớn như thế nào!). Nhưng nếu tệp nhỏ, ví dụ sau có thể đủ cho bạn:

package main

import (
    "os"
    "bufio"
    "bytes"
    "fmt"
)

// Read a whole file into the memory and store it as array of lines
func readLines(path string) (lines []string, err os.Error) {
    var (
        file *os.File
        part []byte
        prefix bool
    )
    if file, err = os.Open(path); err != nil {
        return
    }
    reader := bufio.NewReader(file)
    buffer := bytes.NewBuffer(make([]byte, 1024))
    for {
        if part, prefix, err = reader.ReadLine(); err != nil {
            break
        }
        buffer.Write(part)
        if !prefix {
            lines = append(lines, buffer.String())
            buffer.Reset()
        }
    }
    if err == os.EOF {
        err = nil
    }
    return
}

func main() {
    lines, err := readLines("foo.txt")
    if err != nil {
        fmt.Println("Error: %s\n", err)
        return
    }
    for _, line := range lines {
        fmt.Println(line)
    }
}

Một giải pháp thay thế khác có thể là sử dụng io.ioutil.ReadAll để đọc toàn bộ tệp cùng một lúc và thực hiện cắt từng dòng sau đó. Tôi không cung cấp cho bạn một ví dụ rõ ràng về cách ghi các dòng trở lại tệp, nhưng về cơ bản os.Create()đó là một vòng lặp được theo sau bởi một vòng lặp tương tự như trong ví dụ (xem main()).


Cảm ơn vì thông tin đó. Tôi quan tâm hơn đến việc sử dụng một gói hiện có để thực hiện toàn bộ công việc, vì tôi nghĩ rằng nó khá hữu ích. Ví dụ: tôi muốn sử dụng Go với tính liên tục của dữ liệu mà không cần sử dụng cơ sở dữ liệu ban đầu. Tôi tin một số ngôn ngữ có điều này. ví dụ. Tôi nghĩ Ruby có Readlines đọc một mảng chuỗi (từ bộ nhớ) - không phải tôi đặc biệt là một người hâm mộ Ruby. Tôi đoán không có vấn đề gì to tát, tôi chỉ không thích sự trùng lặp, nhưng có lẽ chỉ có tôi là muốn. Dù sao thì, tôi đã viết một gói để làm điều đó và có lẽ tôi sẽ đưa nó lên github. Các tệp này thường rất nhỏ.
brianoh

Nếu bạn chỉ muốn duy trì bất kỳ loại cấu trúc go nào (ví dụ như một mảng chuỗi, số nguyên, bản đồ hoặc các cấu trúc phức tạp hơn), bạn có thể chỉ cần sử dụng gob.Encode()cho điều đó. Kết quả là một tệp nhị phân thay vì một tệp văn bản được phân tách bằng dòng mới. Tệp này có thể chứa tất cả các loại dữ liệu, có thể được phân tích cú pháp hiệu quả, tệp kết quả sẽ nhỏ hơn và bạn không phải xử lý các dòng mới và phân bổ động. Vì vậy, nó có lẽ phù hợp hơn cho bạn nếu bạn chỉ muốn duy trì một cái gì đó để sử dụng sau này với Go.
tux21b

Những gì tôi muốn là một mảng các dòng văn bản để tôi có thể thay đổi bất kỳ dòng (trường) nào. Các tệp này rất nhỏ. Khi các thay đổi được thực hiện, các chuỗi có độ dài thay đổi cuối cùng sẽ được ghi lại. Nó rất linh hoạt và nhanh chóng cho những gì tôi muốn làm. Tôi cần các dòng mới để phân tách các dòng (trường). Có lẽ có một cách tốt hơn, nhưng điều này có vẻ ổn đối với mục đích của tôi hiện tại. Tôi sẽ xem xét những gì bạn đề xuất sau và có thể thay đổi nó sau đó.
brianoh

2
Lưu ý rằng kể từ r58 (tháng 7 năm 2011), gói mã hóa / dòng đã bị xóa. "Chức năng của nó hiện đã có trong bufio."
kristianp

4
func readToDisplayUsingFile1(f *os.File){
    defer f.Close()
    reader := bufio.NewReader(f)
    contents, _ := ioutil.ReadAll(reader)
    lines := strings.Split(string(contents), '\n')
}

hoặc là

func readToDisplayUsingFile1(f *os.File){
    defer f.Close()
    slice := make([]string,0)

    reader := bufio.NewReader(f)

    for{

    str, err := reader.ReadString('\n')
    if err == io.EOF{
        break
    }

        slice = append(slice, str)
    }

1
Mọi người càng cố nói cờ vây càng "hiện đại" thì nó càng giống mã ràng buộc thư viện tối thiểu 35 tuổi. : \ Thực tế là việc chỉ đọc một tệp văn bản dựa trên dòng là một mớ hỗn độn như vậy chỉ củng cố rằng Go còn một chặng đường dài để .... đi ... đạt được mục đích chung hơn. Có rất nhiều văn bản, dữ liệu dựa trên dòng vẫn đang được xử lý rất hiệu quả trong các ngôn ngữ và nền tảng khác. $ 0,02
ChrisH
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.