Membaca file teks ke dalam larik string (dan menulis)

100

Kemampuan untuk membaca (dan menulis) file teks masuk dan keluar dari array string adalah persyaratan yang cukup umum. Ini juga sangat berguna ketika memulai dengan bahasa yang pada awalnya menghilangkan kebutuhan untuk mengakses database. Apa ada di Golang?
misalnya

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

dan

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

Saya lebih suka menggunakan yang sudah ada daripada duplikat.

brianoh
sumber
2
Gunakan bufio.Scanner untuk membaca baris dari file, lihat stackoverflow.com/a/16615559/1136018 dan golang.org/pkg/bufio
Jack Valmadre

Jawaban:

124

Pada rilis Go1.1, ada bufio.Scanner API yang dapat dengan mudah membaca baris dari sebuah file. Pertimbangkan contoh berikut dari atas, yang ditulis ulang dengan Scanner:

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)
    }
}
Kyle Lemon
sumber
124

Jika file tidak terlalu besar, ini bisa dilakukan dengan fungsi ioutil.ReadFiledan strings.Splitseperti ini:

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

Anda dapat membaca dokumentasi di ioutil dan paket string .

yanatan16
sumber
5
Itu membaca seluruh file ke memori, yang bisa menjadi masalah jika file besar.
jergason
22
@Jergason, itulah sebabnya dia memulai jawabannya dengan "Jika file tidak terlalu besar ..."
laurent
9
ioutil dapat diimpor sebagai"io/ioutil"
Pramod
7
Note strings.Split akan menambahkan satu baris ekstra (string kosong) saat mengurai contoh
bain
1
FYI, di Windows, ini tidak akan menghapus file \r. Jadi, Anda mungkin memiliki tambahan \runtuk setiap elemen.
matfax
32

Tidak dapat memperbarui jawaban pertama.
Bagaimanapun, setelah Go1 rilis, ada beberapa perubahan yang melanggar, jadi saya perbarui seperti yang ditunjukkan di bawah ini:

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)
}
Bill.Zhuang
sumber
18

Anda dapat menggunakan os.File (yang mengimplementasikan antarmuka io.Reader ) dengan paket bufio untuk itu. Namun, paket-paket tersebut dibangun dengan mempertimbangkan penggunaan memori tetap (tidak peduli seberapa besar file tersebut) dan cukup cepat.

Sayangnya hal ini membuat pembacaan seluruh file ke dalam memori menjadi sedikit lebih rumit. Anda dapat menggunakan bytes.Buffer untuk bergabung dengan bagian-bagian baris jika melebihi batas baris. Bagaimanapun, saya sarankan Anda untuk mencoba menggunakan pembaca baris secara langsung dalam proyek Anda (terutama jika tidak tahu seberapa besar file teksnya!). Tetapi jika filenya kecil, contoh berikut mungkin cukup untuk Anda:

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)
    }
}

Alternatif lain mungkin menggunakan io.ioutil.ReadAll untuk membaca file lengkap sekaligus dan melakukan pemotongan per baris sesudahnya. Saya tidak memberi Anda contoh eksplisit tentang cara menulis baris kembali ke file, tetapi pada dasarnya itu os.Create()diikuti oleh loop yang mirip dengan yang ada di contoh (lihat main()).

tux21b
sumber
Terima kasih atas infonya. Saya lebih tertarik menggunakan paket yang sudah ada untuk melakukan keseluruhan pekerjaan, karena menurut saya ini cukup berguna. Misalnya, saya ingin menggunakan Go dengan ketekunan data tanpa menggunakan database pada awalnya. Saya percaya beberapa bahasa memiliki ini. misalnya. Saya pikir Ruby memiliki Readlines yang membaca serangkaian string (dari memori) - bukan berarti saya terutama penggemar Ruby. Kurasa bukan masalah besar, aku hanya tidak suka duplikasi, tapi mungkin hanya aku yang menginginkannya. Bagaimanapun, saya telah menulis paket untuk melakukannya dan mungkin saya akan menaruhnya di github. File ini biasanya sangat kecil.
brianoh
Jika Anda hanya ingin mempertahankan segala jenis struktur go (misalnya larik string, bilangan bulat, peta, atau struktur yang lebih rumit), Anda dapat menggunakan gob.Encode()for itu. Hasilnya adalah file biner, bukan file teks yang dipisahkan baris baru. File ini dapat berisi semua jenis data, dapat diurai secara efisien, file yang dihasilkan akan lebih kecil dan Anda tidak perlu berurusan dengan baris baru dan alokasi dinamis tersebut. Jadi mungkin lebih cocok untuk Anda jika Anda hanya ingin mempertahankan sesuatu untuk digunakan nanti dengan Go.
tux21b
Yang saya inginkan adalah larik baris teks sehingga saya dapat mengubah baris (bidang) apa pun. File-file ini sangat kecil. Ketika perubahan dibuat, string dengan panjang variabel akhirnya ditulis kembali. Sangat fleksibel dan cepat untuk apa yang ingin saya lakukan. Saya membutuhkan baris baru untuk memisahkan baris (bidang). Mungkin ada cara yang lebih baik, tetapi ini tampaknya OK untuk tujuan saya saat ini. Saya akan melihat apa yang Anda sarankan nanti dan mungkin mengubahnya nanti.
brianoh
2
Perhatikan bahwa mulai r58 (Juli 2011), paket encoding / line telah dihapus. "Fungsinya sekarang ada di 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')
}

atau

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)
    }
Muhammad Soliman
sumber
1
semakin "modern" setiap orang terus mencoba untuk mengatakan Go, semakin terlihat seperti kode pengikat perpustakaan minimal 35 tahun. : \ Fakta bahwa hanya membaca file teks berbasis baris adalah kekacauan hanya memperkuat bahwa Go memiliki jalan panjang untuk .... go ... menjadi tujuan yang lebih umum. Ada BANYAK teks, data berbasis baris di luar sana yang masih diproses dengan sangat efisien di bahasa dan platform lain. $ 0,02
ChrisH