อ่านไฟล์ข้อความลงในสตริงอาร์เรย์ (และเขียน)


103

ความสามารถในการอ่าน (และเขียน) ไฟล์ข้อความเข้าและออกจากสตริงอาร์เรย์คือฉันเชื่อว่าเป็นข้อกำหนดที่ค่อนข้างธรรมดา นอกจากนี้ยังมีประโยชน์มากเมื่อเริ่มต้นด้วยภาษาโดยไม่จำเป็นต้องเข้าถึงฐานข้อมูลในตอนแรก มีอยู่ใน Golang หรือไม่?
เช่น

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

และ

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

ฉันต้องการใช้อันที่มีอยู่มากกว่าที่จะทำซ้ำ


2
ใช้ bufio.Scanner เพื่ออ่านบรรทัดจากไฟล์ดูที่stackoverflow.com/a/16615559/1136018และgolang.org/pkg/bufio
Jack Valmadre

คำตอบ:


129

ตั้งแต่รุ่น Go1.1 มีbufio.Scanner API ที่สามารถอ่านบรรทัดจากไฟล์ได้อย่างง่ายดาย ลองพิจารณาตัวอย่างต่อไปนี้จากด้านบนเขียนใหม่ด้วย 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)
    }
}

126

หากไฟล์ไม่ใหญ่เกินไปสามารถทำได้โดยใช้ฟังก์ชันioutil.ReadFileและstrings.Splitดังนี้:

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

คุณสามารถอ่านเอกสารเกี่ยวกับแพ็กเกจioutilและสตริง


5
มันอ่านไฟล์ทั้งหมดในหน่วยความจำซึ่งอาจเป็นปัญหาได้หากไฟล์มีขนาดใหญ่
jergason

22
@Jergason ซึ่งเป็นสาเหตุที่เขาเริ่มตอบว่า "ถ้าไฟล์ไม่ใหญ่เกินไป ... "
laurent

9
สามารถนำเข้า ioutil ได้ที่"io/ioutil"
Pramod

7
หมายเหตุstrings.Splitจะผนวกบรรทัดเสริม (สตริงที่ว่างเปล่า) เมื่อแยกไฟล์ข้อความปกติ POSIX ตัวอย่าง
Bain

1
FYI ใน Windows สิ่งนี้จะไม่ลบไฟล์\r. ดังนั้นคุณอาจ\rต่อท้ายทุกองค์ประกอบ
matfax

32

ไม่สามารถอัปเดตคำตอบแรกได้
อย่างไรก็ตามหลังจากปล่อย Go1 มีการเปลี่ยนแปลงบางอย่างที่ผิดปกติดังนั้นฉันจึงอัปเดตดังที่แสดงด้านล่าง:

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

คุณสามารถใช้os.File (ซึ่งใช้อินเทอร์เฟซio.Reader ) กับไฟล์ bufioแพ็คเกจสำหรับสิ่งนั้น อย่างไรก็ตามแพ็กเกจเหล่านั้นสร้างขึ้นโดยคำนึงถึงการใช้หน่วยความจำคงที่ (ไม่ว่าไฟล์จะมีขนาดใหญ่แค่ไหน) และค่อนข้างเร็ว

น่าเสียดายที่ทำให้การอ่านไฟล์ทั้งหมดลงในหน่วยความจำซับซ้อนขึ้นเล็กน้อย คุณสามารถใช้ไบต์บัฟเฟอร์เพื่อรวมส่วนต่างๆของบรรทัดหากเกินขีด จำกัด ของบรรทัด อย่างไรก็ตามฉันขอแนะนำให้คุณลองใช้โปรแกรมอ่านบรรทัดโดยตรงในโครงการของคุณ (โดยเฉพาะอย่างยิ่งหากไม่ทราบว่าไฟล์ข้อความมีขนาดใหญ่แค่ไหน!) แต่ถ้าไฟล์มีขนาดเล็กตัวอย่างต่อไปนี้อาจเพียงพอสำหรับคุณ:

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

อีกทางเลือกหนึ่งอาจใช้io.ioutil.ReadAllเพื่ออ่านในไฟล์ที่สมบูรณ์พร้อมกันและทำการแบ่งส่วนทีละบรรทัดในภายหลัง ฉันไม่ได้ให้ตัวอย่างที่ชัดเจนเกี่ยวกับวิธีการเขียนเส้นกลับไปที่ไฟล์ แต่โดยพื้นฐานแล้วจะos.Create()ตามด้วยลูปที่คล้ายกับในตัวอย่าง (ดูmain())


ขอบคุณสำหรับข้อมูลนั้น ฉันสนใจที่จะใช้แพ็คเกจที่มีอยู่ในการทำงานทั้งหมดมากกว่าเพราะฉันคิดว่ามันมีประโยชน์มากทีเดียว ตัวอย่างเช่นฉันต้องการใช้ Go กับการคงอยู่ของข้อมูลโดยไม่ต้องใช้ฐานข้อมูลในตอนแรก บางภาษามีสิ่งนี้ฉันเชื่อ เช่น. ฉันคิดว่า Ruby มี Readlines ที่อ่านอาร์เรย์ของสตริง (จากหน่วยความจำ) ไม่ใช่ว่าฉันเป็นแฟน Ruby โดยเฉพาะ ไม่ใช่เรื่องใหญ่ที่ฉันคิดว่าฉันไม่ชอบการทำซ้ำ แต่อาจเป็นเพียงฉันที่ต้องการมัน อย่างไรก็ตามฉันได้เขียนแพ็คเกจเพื่อทำและบางทีฉันอาจจะวางไว้ใน github ไฟล์เหล่านี้มักมีขนาดเล็กมาก
brianoh

หากคุณต้องการเพียงแค่คงโครงสร้าง go แบบใดก็ได้ (เช่นอาร์เรย์ของสตริงจำนวนเต็มแผนที่หรือโครงสร้างที่ซับซ้อนมากขึ้น) คุณสามารถใช้สิ่งgob.Encode()นั้นได้ ผลลัพธ์คือไฟล์ไบนารีแทนที่จะเป็นไฟล์ข้อความที่คั่นขึ้นบรรทัดใหม่ ไฟล์นี้สามารถมีข้อมูลทุกประเภทสามารถแยกวิเคราะห์ได้อย่างมีประสิทธิภาพไฟล์ที่ได้จะมีขนาดเล็กลงและคุณไม่ต้องจัดการกับบรรทัดใหม่และการจัดสรรแบบไดนามิกเหล่านั้น ดังนั้นจึงน่าจะดีกว่าสำหรับคุณหากคุณต้องการคงบางอย่างไว้เพื่อใช้งานกับ Go ในภายหลัง
tux21b

สิ่งที่ฉันต้องการคืออาร์เรย์ของบรรทัดข้อความเพื่อให้ฉันสามารถเปลี่ยนบรรทัดใดก็ได้ (ฟิลด์) ไฟล์เหล่านี้มีขนาดเล็กมาก เมื่อมีการเปลี่ยนแปลงสตริงความยาวตัวแปรจะถูกเขียนกลับในที่สุด มีความยืดหยุ่นและรวดเร็วมากสำหรับสิ่งที่ฉันต้องการทำ ฉันต้องการขึ้นบรรทัดใหม่เพื่อแยกบรรทัด (ฟิลด์) บางทีอาจมีวิธีที่ดีกว่านี้ แต่ดูเหมือนว่าจะใช้ได้ตามวัตถุประสงค์ของฉันในปัจจุบัน ฉันจะดูสิ่งที่คุณแนะนำในภายหลังและอาจเปลี่ยนแปลงได้ในภายหลัง
brianoh

2
โปรดทราบว่า ณ วันที่ r58 (กรกฎาคม 2554) แพ็คเกจการเข้ารหัส / บรรทัดถูกลบออก "ตอนนี้ฟังก์ชันการทำงานอยู่ใน 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')
}

หรือ

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
ยิ่งทุกคน "ทันสมัย" พยายามพูดว่า Go is ก็ยิ่งดูเหมือนรหัสผูกห้องสมุดที่มีอายุน้อยที่สุด 35 ปีเท่านั้น : \ ความจริงที่ว่าการอ่านไฟล์ข้อความแบบบรรทัดเป็นเรื่องยุ่งยากเพียงอย่างเดียวเป็นการตอกย้ำว่า Go มีหนทางอีกยาวไกลในการ .... go ... เพื่อวัตถุประสงค์ทั่วไปมากขึ้น มีข้อความจำนวนมากข้อมูลตามบรรทัดที่ยังคงได้รับการประมวลผลอย่างมีประสิทธิภาพในภาษาและแพลตฟอร์มอื่น ๆ $ 0.02
ChrisH
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.