จะอ่าน / เขียนจาก / ถึงไฟล์โดยใช้ Go ได้อย่างไร


284

ฉันพยายามที่จะเรียนรู้ Go ด้วยตัวเอง แต่ฉันยังคงนิ่งงันในการพยายามอ่านและเขียนไฟล์ธรรมดา

ฉันสามารถเข้าใจได้inFile, _ := os.Open(INFILE, 0, 0)แต่จริงๆแล้วการรับเนื้อหาของไฟล์นั้นไม่สมเหตุสมผลเพราะฟังก์ชั่นการอ่านใช้[]byteเป็นพารามิเตอร์

func (file *File) Read(b []byte) (n int, err Error)

คำตอบ:


476

มาสร้างรายการที่เข้ากันได้กับ Go 1 ของวิธีการอ่านและเขียนไฟล์ใน Go

เนื่องจากไฟล์ API มีการเปลี่ยนแปลงเมื่อเร็ว ๆ นี้และคำตอบอื่น ๆ ส่วนใหญ่ไม่ทำงานกับ Go 1 พวกเขายังพลาด bufioซึ่ง IMHO สำคัญ

ในตัวอย่างต่อไปนี้ฉันคัดลอกไฟล์โดยอ่านจากมันและเขียนไปยังไฟล์ปลายทาง

เริ่มต้นด้วยพื้นฐาน

package main

import (
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := fo.Write(buf[:n]); err != nil {
            panic(err)
        }
    }
}

ที่นี่ฉันใช้os.Openและos.Createสิ่งที่ล้อมรอบos.OpenFileสะดวก โดยปกติเราไม่จำเป็นต้องโทรติดต่อOpenFileโดยตรง

สังเกตการรักษา EOF Readพยายามกรอกข้อมูลการbufโทรแต่ละครั้งและส่งกลับio.EOFข้อผิดพลาดหากถึงจุดสิ้นสุดไฟล์ในการดำเนินการดังกล่าว ในกรณีนี้bufจะยังคงเก็บข้อมูล การเรียกครั้งต่อ ๆ ไปจะReadส่งกลับค่าศูนย์ตามจำนวนไบต์ที่อ่านและเหมือนกับio.EOFข้อผิดพลาด ข้อผิดพลาดอื่น ๆ จะนำไปสู่ความตื่นตระหนก

การใช้ bufio

package main

import (
    "bufio"
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()
    // make a read buffer
    r := bufio.NewReader(fi)

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()
    // make a write buffer
    w := bufio.NewWriter(fo)

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := w.Write(buf[:n]); err != nil {
            panic(err)
        }
    }

    if err = w.Flush(); err != nil {
        panic(err)
    }
}

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

การใช้ ioutil

package main

import (
    "io/ioutil"
)

func main() {
    // read the whole file at once
    b, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }

    // write the whole body at once
    err = ioutil.WriteFile("output.txt", b, 0644)
    if err != nil {
        panic(err)
    }
}

ง่ายเหมือนพาย! แต่ใช้เฉพาะเมื่อคุณแน่ใจว่าคุณไม่ได้จัดการกับไฟล์ขนาดใหญ่


55
สำหรับใครก็ตามที่สะดุดกับคำถามนี้ตอนแรกมันถูกถามในปี 2009 ก่อนที่จะมีการแนะนำห้องสมุดเหล่านี้ดังนั้นโปรดใช้คำตอบนี้เป็นแนวทางของคุณ!
เซทเฮนิก

1
ตามgolang.org/pkg/os/#File.Writeเมื่อ Write ไม่ได้เขียนไบต์ทั้งหมดมันจะส่งคืนข้อผิดพลาด ดังนั้นการตรวจสอบเพิ่มเติมในตัวอย่างแรก ( panic("error in writing")) จึงไม่จำเป็น
ayke

15
โปรดทราบว่าตัวอย่างเหล่านี้ไม่ได้ตรวจสอบข้อผิดพลาดที่ส่งคืนจาก fo.Close () จากหน้า man Linux ปิด (2): ไม่ตรวจสอบค่าส่งคืนของ close () เป็นข้อผิดพลาดทั่วไป แต่อย่างไรก็ตามการเขียนโปรแกรมอย่างจริงจัง อาจเป็นไปได้ว่ามีการรายงานข้อผิดพลาดในการดำเนินการเขียน (2) ครั้งก่อนที่การปิดครั้งสุดท้าย () ไม่ตรวจสอบค่าส่งคืนเมื่อปิดไฟล์อาจทำให้ข้อมูลสูญหายได้ สิ่งนี้สามารถสังเกตได้โดยเฉพาะกับ NFS และกับโควต้าดิสก์
Nick Craig-Wood

12
ดังนั้นไฟล์ "ใหญ่" คืออะไร 1KB? 1MB? 1GB? หรือ "ใหญ่" ขึ้นอยู่กับฮาร์ดแวร์ของเครื่อง?
425nesp

3
@ 425nesp มันจะอ่านไฟล์ทั้งหมดในหน่วยความจำดังนั้นมันขึ้นอยู่กับปริมาณของหน่วยความจำที่มีอยู่ในเครื่องที่ใช้งาน
Mostafa

49

นี่เป็นรุ่นที่ดี:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}

8
วิธีนี้จะเก็บไฟล์ทั้งหมดในหน่วยความจำ เนื่องจากไฟล์อาจมีขนาดใหญ่ซึ่งอาจไม่ใช่สิ่งที่คุณต้องการทำเสมอไป
user7610

9
นอกจากนี้ยัง0x777เป็นการหลอกลวง ไม่ว่าในกรณีใดมันควรจะเหมือน0644หรือมากกว่า0755(ฐานแปดไม่ใช่ฐานสิบหก)
cnst

@cnst เปลี่ยนเป็น 0644 จาก 0x777
Trenton

31

การใช้ io.Copy

package main

import (
    "io"
    "log"
    "os"
)

func main () {
    // open files r and w
    r, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    defer r.Close()

    w, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    defer w.Close()

    // do the actual work
    n, err := io.Copy(w, r)
    if err != nil {
        panic(err)
    }
    log.Printf("Copied %v bytes\n", n)
}

หากคุณไม่รู้สึกอยากพลิกโฉมพวงมาลัยio.Copyและio.CopyNอาจช่วยคุณได้ หากคุณตรวจสอบแหล่งที่มาของฟังก์ชั่น io.Copy มันไม่มีอะไรเลยนอกจากหนึ่งในโซลูชั่น Mostafa (อันที่จริงแล้วเป็นหนึ่งเดียว) ที่บรรจุในไลบรารี Go พวกเขาใช้บัฟเฟอร์ขนาดใหญ่กว่าที่เป็นอยู่


5
สิ่งหนึ่งที่ควรพูดถึง - เพื่อให้แน่ใจว่าเนื้อหาของไฟล์นั้นถูกเขียนลงดิสก์คุณต้องใช้w.Sync()หลังจากio.Copy(w, r)
Shay Tsadok

นอกจากนี้หากคุณเขียนไปยังไฟล์ที่มีอยู่แล้วio.Copy()จะเขียนเฉพาะข้อมูลที่คุณป้อนด้วยดังนั้นหากไฟล์ที่มีอยู่มีเนื้อหาเพิ่มเติมจะไม่ถูกลบซึ่งอาจส่งผลให้ไฟล์เสียหาย
Invidian

1
@Invidian ทุกอย่างขึ้นอยู่กับวิธีที่คุณเปิดไฟล์ปลายทาง หากคุณทำw, err := os.Create("output.txt")สิ่งที่คุณอธิบายจะไม่เกิดขึ้นเพราะ "สร้างสร้างหรือตัดทอนไฟล์ที่ระบุชื่อหากไฟล์มีอยู่แล้วไฟล์จะถูกตัดทอน" golang.org/pkg/os/#Create
user7610

นี่ควรเป็นคำตอบที่ถูกต้องเนื่องจากมันไม่ได้คิดค้นวงล้อใหม่ในขณะที่ไม่ต้องอ่านไฟล์ทั้งหมดในครั้งเดียวก่อนที่จะอ่าน
Eli Davis

11

ด้วยรุ่น Go ที่ใหม่กว่าการอ่าน / เขียนไปยัง / จากไฟล์จึงเป็นเรื่องง่าย หากต้องการอ่านจากไฟล์:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    data, err := ioutil.ReadFile("text.txt")
    if err != nil {
        return
    }
    fmt.Println(string(data))
}

ในการเขียนไฟล์:

package main

import "os"

func main() {
    file, err := os.Create("text.txt")
    if err != nil {
        return
    }
    defer file.Close()

    file.WriteString("test\nhello")
}

สิ่งนี้จะเขียนทับเนื้อหาของไฟล์ (สร้างไฟล์ใหม่หากไม่มี)


10

[]byteเป็นชิ้น (คล้ายกับสตริงย่อย) ของทั้งหมดหรือบางส่วนของอาร์เรย์ไบต์ คิดว่าส่วนย่อยเป็นโครงสร้างค่าที่มีเขตข้อมูลตัวชี้ที่ซ่อนอยู่เพื่อให้ระบบค้นหาและเข้าถึงอาร์เรย์ทั้งหมดหรือบางส่วน (ชิ้น) รวมทั้งเขตข้อมูลสำหรับความยาวและความจุของชิ้นซึ่งคุณสามารถเข้าถึงโดยใช้len()และcap()ฟังก์ชั่น .

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

package main
import (
    "fmt";
    "os";
)
func main()
{
    inName := "file-rw.bin";
    inPerm :=  0666;
    inFile, inErr := os.Open(inName, os.O_RDONLY, inPerm);
    if inErr == nil {
        inBufLen := 16;
        inBuf := make([]byte, inBufLen);
        n, inErr := inFile.Read(inBuf);
        for inErr == nil {
            fmt.Println(n, inBuf[0:n]);
            n, inErr = inFile.Read(inBuf);
        }
    }
    inErr = inFile.Close();
}

9
การประชุมไปคือการตรวจสอบความผิดพลาดครั้งแรกและปล่อยให้ปกติรหัสอาศัยอยู่นอกifบล็อก
Hasen

@Jurily: หากไฟล์ถูกเปิดเมื่อเกิดข้อผิดพลาดคุณจะปิดมันได้อย่างไร?
peterSO

10
@ peterSO: use defer
James Antill

แต่ทำไม [256] ไบต์ไม่ได้รับการยอมรับและชัดเจนและโง่เง่า verbose (แต่เห็นได้ชัดว่าไม่ผิด) inBuf: = make ([] byte, 256) ยอมรับ?
ชายในพื้นที่ cardiff

7

ลองสิ่งนี้:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}

1
สิ่งนี้จะทำงานหากคุณต้องการอ่านไฟล์ทั้งหมดในครั้งเดียว หากไฟล์มีขนาดใหญ่มากหรือคุณต้องการอ่านเพียงบางส่วนมันอาจไม่ใช่สิ่งที่คุณต้องการ
Evan Shaw

3
คุณควรตรวจสอบรหัสข้อผิดพลาดและอย่าเพิกเฉยเช่นนั้น !!
hasen

7
นี่ถูกย้ายไปยังแพ็คเกจ ioutil แล้ว ดังนั้นจะเป็น ioutilReadFile ()
Christopher

ฉันแก้ไขแล้วมันบอกว่า 0644
Joakim

1

เพียงแค่ดูเอกสารดูเหมือนว่าคุณควรประกาศบัฟเฟอร์ประเภท [] ไบต์และส่งไปอ่านซึ่งจะอ่านถึงอักขระจำนวนมากและส่งคืนจำนวนอักขระที่อ่านจริง (และข้อผิดพลาด)

เอกสารบอกว่า

อ่านอ่านได้สูงสุด len (b) ไบต์จากไฟล์ ส่งคืนจำนวนไบต์ที่อ่านและข้อผิดพลาดถ้ามี EOF ถูกส่งสัญญาณโดยการนับศูนย์โดยตั้งค่า err เป็น EOF

มันไม่ทำงานเหรอ?

แก้ไข: นอกจากนี้ฉันคิดว่าคุณควรใช้ Reader / Writer อินเตอร์เฟสที่ประกาศในแพ็คเกจbufioแทนที่จะใช้แพ็คเกจos


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

1

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

วิธีนี้คุณสามารถกำหนดจำนวนผู้อ่านที่จะอ่านจำนวนไบต์และตรวจสอบการส่งคืนเพื่อดูจำนวนไบต์ที่อ่านจริงและจัดการข้อผิดพลาดได้อย่างเหมาะสม

ในขณะที่คนอื่น ๆ ชี้ไปที่คำตอบของพวกเขา bufio อาจเป็นสิ่งที่คุณต้องการอ่านจากไฟล์ส่วนใหญ่

ฉันจะเพิ่มคำใบ้อีกอันหนึ่งเพราะมันมีประโยชน์จริงๆ การอ่านบรรทัดจากไฟล์ทำได้ดีที่สุดไม่ใช่โดยวิธี ReadLine แต่เป็นวิธี ReadBytes หรือ ReadString แทน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.