Textdatei in String-Array lesen (und schreiben)

99

Die Fähigkeit, eine Textdatei in ein String-Array hinein und aus diesem heraus zu lesen (und zu schreiben), ist meines Erachtens eine recht häufige Anforderung. Dies ist auch sehr nützlich, wenn Sie mit einer Sprache beginnen, bei der zunächst kein Zugriff auf eine Datenbank erforderlich ist. Existiert man in Golang?
z.B

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

und

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

Ich würde es vorziehen, eine vorhandene zu verwenden, anstatt sie zu duplizieren.

brianoh
quelle
2
Verwenden Sie bufio.Scanner, um Zeilen aus einer Datei zu lesen, siehe stackoverflow.com/a/16615559/1136018 und golang.org/pkg/bufio
Jack Valmadre

Antworten:

122

Ab Go1.1 gibt es eine bufio.Scanner- API, mit der Zeilen aus einer Datei problemlos gelesen werden können. Betrachten Sie das folgende Beispiel von oben, das mit dem Scanner neu geschrieben wurde:

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 Zitronen
quelle
124

Wenn die Datei nicht zu groß ist, kann dies mit der getan werden , ioutil.ReadFileund strings.SplitFunktionen wie folgt:

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

Sie können die Dokumentation zu ioutil- und Strings- Paketen lesen .

yanatan16
quelle
5
Es liest die gesamte Datei in den Speicher ein, was ein Problem sein kann, wenn die Datei groß ist.
Jergason
22
@ Jergason, weshalb er seine Antwort mit "Wenn die Datei nicht zu groß ist ..." begann
Laurent
9
ioutil kann importiert werden als"io/ioutil"
Pramod
7
Beachten Sie Zeichenfolgen. Split fügt eine zusätzliche Zeile (eine leere Zeichenfolge) hinzu, wenn Sie reguläre POSIX-Textdateien analysieren. Beispiel
Bain
1
Zu Ihrer Information, unter Windows wird dies das nicht entfernen \r. Sie können also \ran jedes Element ein angehängt haben .
Matfax
32

Erste Antwort kann nicht aktualisiert werden.
Wie auch immer, nach der Veröffentlichung von Go1 gibt es einige wichtige Änderungen, daher habe ich wie folgt aktualisiert:

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
quelle
18

Sie können dafür os.File (das die io.Reader- Schnittstelle implementiert ) mit dem bufio- Paket verwenden. Diese Pakete werden jedoch unter Berücksichtigung der festen Speichernutzung erstellt (egal wie groß die Datei ist) und sind recht schnell.

Leider macht das Einlesen der gesamten Datei in den Speicher etwas komplizierter. Sie können einen Bytes.Buffer verwenden , um die Teile der Zeile zu verbinden, wenn sie das Zeilenlimit überschreiten. Auf jeden Fall empfehle ich Ihnen, den Zeilenleser direkt in Ihrem Projekt zu verwenden (insbesondere, wenn Sie nicht wissen, wie groß die Textdatei ist!). Wenn die Datei jedoch klein ist, kann das folgende Beispiel für Sie ausreichend sein:

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

Eine andere Alternative könnte darin bestehen, io.ioutil.ReadAll zu verwenden, um die vollständige Datei sofort einzulesen und anschließend das zeilenweise Schneiden durchzuführen . Ich gebe Ihnen kein explizites Beispiel dafür, wie Sie die Zeilen zurück in die Datei schreiben, aber im Grunde os.Create()folgt eine Schleife ähnlich der im Beispiel (siehe main()).

tux21b
quelle
Danke für diese Info. Ich war mehr daran interessiert, ein vorhandenes Paket für die gesamte Arbeit zu verwenden, da ich es für sehr nützlich halte. Zum Beispiel möchte ich Go mit Persistenz von Daten verwenden, ohne anfänglich eine Datenbank zu verwenden. Einige Sprachen haben dies, glaube ich. z.B. Ich denke, Ruby hat Readlines, die eine Reihe von Zeichenfolgen (aus dem Speicher) lesen - nicht, dass ich besonders ein Ruby-Fan bin. Es ist keine große Sache, denke ich, ich mag keine Vervielfältigung, aber vielleicht bin es nur ich, der es will. Wie auch immer, ich habe ein Paket dafür geschrieben und vielleicht werde ich es auf Github setzen. Diese Dateien sind normalerweise sehr klein.
Brianoh
Wenn Sie einfach jede Art von Go-Strukturen beibehalten möchten (z. B. ein Array von Zeichenfolgen, Ganzzahlen, Karten oder komplizierteren Strukturen), können Sie einfach die gob.Encode()dafür verwenden. Das Ergebnis ist eine Binärdatei anstelle einer durch Zeilenumbrüche getrennten Textdatei. Diese Datei kann alle Arten von Daten enthalten, kann effizient analysiert werden, die resultierende Datei wird kleiner und Sie müssen sich nicht mit diesen Zeilenumbrüchen und der dynamischen Zuordnung befassen. Daher ist es wahrscheinlich besser für Sie geeignet, wenn Sie nur etwas für die spätere Verwendung mit Go beibehalten möchten.
Tux21b
Was ich möchte, ist ein Array von Textzeilen, damit ich jede Zeile (jedes Feld) ändern kann. Diese Dateien sind sehr klein. Wenn Änderungen vorgenommen werden, werden die Zeichenfolgen variabler Länge schließlich zurückgeschrieben. Es ist sehr flexibel und schnell für das, was ich tun möchte. Ich brauche die Zeilenumbrüche, um die Zeilen (Felder) zu trennen. Vielleicht gibt es einen besseren Weg, aber dies scheint für meine Zwecke derzeit in Ordnung zu sein. Ich werde mir später ansehen, was Sie vorschlagen, und es dann vielleicht ändern.
Brianoh
2
Beachten Sie, dass ab r58 (Juli 2011) das Codierungs- / Zeilenpaket entfernt wurde. "Seine Funktionalität ist jetzt in 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')
}

oder

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
quelle
1
Je "moderner" jeder versucht zu sagen, dass Go ist, desto mehr sieht es aus wie ein 35 Jahre alter Bibliotheksbindungscode mit minimalem Minimum. : \ Die Tatsache, dass das einfache Lesen einer zeilenbasierten Textdatei ein solches Durcheinander darstellt, verstärkt nur, dass Go einen langen Weg vor sich hat, um ... allgemeiner zu sein. Es gibt eine Menge textbasierter, zeilenbasierter Daten, die auf anderen langs und Plattformen immer noch sehr effizient verarbeitet werden. $
.02