Wczytaj plik tekstowy do tablicy ciągów (i zapisz)

100

Możliwość odczytu (i zapisu) pliku tekstowego do iz tablicy ciągów jest moim zdaniem dość powszechnym wymaganiem. Jest to również bardzo przydatne, gdy zaczynasz od języka, który eliminuje początkową potrzebę dostępu do bazy danych. Czy ktoś istnieje w Golangu?
na przykład

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

i

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

Wolałbym użyć istniejącego zamiast duplikować.

brianoh
źródło
2
Użyj bufio.Scanner do odczytywania wierszy z pliku, zobacz stackoverflow.com/a/16615559/1136018 i golang.org/pkg/bufio
Jack Valmadre

Odpowiedzi:

124

Od wydania Go1.1 istnieje interfejs API bufio.Scanner, który może z łatwością odczytywać wiersze z pliku. Rozważmy następujący przykład z góry, przepisany za pomocą programu 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 Lemons
źródło
124

Jeśli plik nie jest zbyt duży, można to zrobić za pomocą funkcji ioutil.ReadFilei strings.Split:

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

Możesz przeczytać dokumentację dotyczącą pakietów ioutil i strings .

yanatan16
źródło
5
Wczytuje cały plik do pamięci, co może być problemem, jeśli plik jest duży.
jergason
22
@Jergason, dlatego swoją odpowiedź rozpoczął od „Jeśli plik nie jest zbyt duży ...”
laurent
9
ioutil można zaimportować jako"io/ioutil"
Pramod,
7
Zwróć uwagę na ciągi: Split doda jedną dodatkową linię (pusty ciąg) podczas analizowania zwykłych plików tekstowych POSIX. Przykład
bain
1
FYI, w systemie Windows nie spowoduje to usunięcia \r. Więc może masz \rdołączone do każdego elementu.
matfax
32

Nie można zaktualizować pierwszej odpowiedzi.
W każdym razie, po wydaniu Go1 jest kilka istotnych zmian, więc zaktualizowałem, jak pokazano poniżej:

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
źródło
18

Możesz użyć os.File (który implementuje interfejs io.Reader ) z pakietem bufio . Jednak te pakiety są budowane z myślą o stałym zużyciu pamięci (bez względu na to, jak duży jest plik) i są dość szybkie.

Niestety sprawia to, że wczytywanie całego pliku do pamięci jest nieco bardziej skomplikowane. Możesz użyć bytes.Buffer, aby połączyć części linii, jeśli przekraczają limit linii. W każdym razie radzę spróbować użyć czytnika linii bezpośrednio w swoim projekcie (zwłaszcza jeśli nie wiesz, jak duży jest plik tekstowy!). Ale jeśli plik jest mały, poniższy przykład może Ci wystarczyć:

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

Inną alternatywą może być użycie io.ioutil.ReadAll, aby od razu wczytać cały plik, a następnie wykonać cięcie po linii. Nie daję ci wyraźnego przykładu zapisywania wierszy z powrotem do pliku, ale w zasadzie jest os.Create()to pętla, po której następuje pętla podobna do tej w przykładzie (patrz main()).

tux21b
źródło
Dzięki za te informacje. Byłem bardziej zainteresowany wykorzystaniem istniejącego pakietu do wykonania całej pracy, ponieważ myślę, że jest całkiem przydatny. Na przykład chcę używać Go z trwałością danych bez początkowego używania bazy danych. Sądzę, że niektóre języki mają to. na przykład. Myślę, że Ruby ma Readlines, które czyta tablicę ciągów (z pamięci) - nie znaczy to, że jestem szczególnie fanem Rubiego. Myślę, że to nic wielkiego, po prostu nie lubię powielania, ale może to tylko ja tego chcę. W każdym razie napisałem pakiet, aby to zrobić i być może umieszczę go na githubie. Te pliki są zazwyczaj bardzo małe.
brianoh
Jeśli chcesz po prostu utrwalić jakiekolwiek struktury go (np. Tablicę łańcuchów, liczb całkowitych, mapy lub bardziej skomplikowane struktury), możesz po prostu użyć gob.Encode()do tego celu. Rezultatem jest plik binarny zamiast pliku tekstowego rozdzielanego znakami nowej linii. Ten plik może zawierać wszelkiego rodzaju dane, może być wydajnie analizowany, plik wynikowy będzie mniejszy i nie musisz zajmować się nowymi wierszami i alokacją dynamiczną. Więc prawdopodobnie lepiej Ci pasuje, jeśli chcesz po prostu zachować coś do późniejszego użycia w Go.
tux21b
To, czego chcę, to tablica linii tekstu, aby móc zmienić dowolną linię (pole). Te pliki są bardzo małe. Po wprowadzeniu zmian ciągi o zmiennej długości są ostatecznie zapisywane z powrotem. Jest bardzo elastyczny i szybki jak na to, co chcę robić. Potrzebuję nowych linii, aby oddzielić linie (pola). Być może jest lepszy sposób, ale obecnie wydaje mi się to w porządku. Przyjrzę się temu, co zasugerujesz później i być może wtedy to zmienię.
brianoh
2
Zauważ, że od r58 (lipiec 2011) pakiet kodowania / linii został usunięty. „Jego funkcjonalność jest teraz w 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')
}

lub

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
źródło
1
im bardziej "nowoczesny" każdy próbuje powiedzieć Go jest, tym bardziej wygląda jak 35-letni kod wiążący bibliotekę z minimalnym minimum. : \ Fakt, że zwykłe czytanie pliku tekstowego opartego na wierszach jest takim bałaganem, tylko utwierdza w przekonaniu, że Go ma długą drogę do ... przejścia ... do bardziej ogólnego przeznaczenia. Istnieje DUŻO tekstowych, liniowych danych, które są nadal bardzo wydajnie przetwarzane w innych językach i na innych platformach. 0,02 $
ChrisH