Bagaimana cara membaca / menulis dari / ke file menggunakan Go?

284

Saya sudah mencoba belajar Go sendiri, tetapi saya bingung mencoba membaca dari dan menulis ke file biasa.

Saya bisa mendapatkan sejauh ini inFile, _ := os.Open(INFILE, 0, 0), tetapi sebenarnya mendapatkan konten file tidak masuk akal, karena fungsi baca mengambil []bytesebagai parameter.

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

Jawaban:

476

Mari kita membuat daftar Go-kompatibel semua cara untuk membaca dan menulis file di Go.

Karena file API telah berubah baru-baru ini dan sebagian besar jawaban lainnya tidak berfungsi dengan Go 1. Mereka juga ketinggalan bufio IMHO yang penting.

Dalam contoh berikut ini saya menyalin file dengan membaca darinya dan menulis ke file tujuan.

Mulailah dengan dasar-dasarnya

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

Di sini saya menggunakan os.Opendan os.Createpembungkus yang nyaman di sekitar os.OpenFile. Kami biasanya tidak perlu menelepon OpenFilelangsung.

Perhatikan merawat EOF. Readmencoba untuk mengisi bufsetiap panggilan, dan kembali io.EOFsebagai kesalahan jika mencapai akhir file dalam melakukannya. Dalam hal ini bufmasih akan menyimpan data. Panggilan konsekuen untuk Readmengembalikan nol ketika jumlah byte dibaca dan sama io.EOFdengan kesalahan. Kesalahan lainnya akan menyebabkan kepanikan.

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

bufiohanya bertindak sebagai penyangga di sini, karena kami tidak memiliki banyak hubungannya dengan data. Dalam kebanyakan situasi lain (khususnya dengan file teks) bufiosangat berguna dengan memberi kami API yang bagus untuk membaca dan menulis dengan mudah dan fleksibel, sementara itu menangani buffering di belakang layar.

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

Mudah seperti pai! Tetapi gunakan hanya jika Anda yakin tidak berurusan dengan file besar.

Mostafa
sumber
55
Bagi siapa saja yang menemukan pertanyaan ini, pertanyaan ini awalnya ditanyakan pada tahun 2009 sebelum perpustakaan ini diperkenalkan, jadi tolong, gunakan jawaban ini sebagai panduan Anda!
Seth Hoenig
1
Menurut golang.org/pkg/os/#File.Write , ketika Write belum menulis semua byte, ia mengembalikan kesalahan. Jadi pemeriksaan tambahan pada contoh pertama ( panic("error in writing")) tidak perlu.
ayke
15
Perhatikan bahwa contoh-contoh ini tidak memeriksa kesalahan pengembalian dari fo.Close (). Dari halaman manual Linux close (2): Tidak memeriksa nilai balik dari close () adalah kesalahan pemrograman yang umum tetapi tetap serius. Sangat mungkin bahwa kesalahan pada operasi penulisan (2) sebelumnya dilaporkan pertama kali pada penutupan akhir (). Tidak memeriksa nilai kembali saat menutup file dapat menyebabkan hilangnya data secara diam-diam. Ini terutama dapat diamati dengan NFS dan dengan kuota disk.
Nick Craig-Wood
12
Jadi, apa itu file "besar"? 1 KB? 1MB? 1GB? Atau apakah "besar" tergantung pada perangkat keras mesin?
425nesp
3
@ 425nesp Ini membaca seluruh file ke dalam memori, jadi itu tergantung pada jumlah memori yang tersedia di mesin yang berjalan.
Mostafa
49

Ini versi bagus:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}
Piotr
sumber
8
Ini menyimpan seluruh file dalam memori. Karena file bisa besar, itu mungkin tidak selalu apa yang ingin Anda lakukan.
user7610
9
Juga, 0x777palsu. Bagaimanapun, itu harus lebih seperti 0644atau 0755(oktal, bukan hex).
cnst
@cnst mengubahnya menjadi 0644 dari 0x777
Trenton
31

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

Jika Anda merasa tidak ingin menciptakan kembali roda, itu io.Copydan io.CopyNmungkin bisa membantu Anda dengan baik. Jika Anda memeriksa sumber fungsi io.Copy, itu tidak lain adalah salah satu solusi Mostafa (yang 'dasar', sebenarnya) yang dikemas dalam Go library. Mereka menggunakan buffer yang jauh lebih besar daripada dia.

pengguna7610
sumber
5
satu hal yang layak disebutkan - untuk memastikan bahwa isi file ditulis ke disk, Anda harus menggunakan w.Sync()setelahio.Copy(w, r)
Shay Tsadok
Juga, jika Anda menulis ke file yang sudah ada, io.Copy()hanya akan menulis data yang Anda masukkan, jadi jika file yang ada memiliki lebih banyak konten, itu tidak akan dihapus, yang dapat mengakibatkan file rusak.
Invidian
1
@Invidian Semuanya tergantung pada bagaimana Anda membuka file tujuan. Jika Anda melakukannya w, err := os.Create("output.txt"), apa yang Anda jelaskan tidak terjadi, karena "Buat membuat atau memotong file bernama. Jika file sudah ada, itu terpotong." golang.org/pkg/os/#Create .
user7610
Ini harus menjadi jawaban yang benar karena tidak menemukan kembali roda sementara tidak harus membaca seluruh file sekaligus sebelum membacanya.
Eli Davis
11

Dengan versi Go yang lebih baru, membaca / menulis ke / dari file itu mudah. Untuk membaca dari file:

package main

import (
    "fmt"
    "io/ioutil"
)

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

Untuk menulis ke file:

package main

import "os"

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

    file.WriteString("test\nhello")
}

Ini akan menimpa konten file (buat file baru jika tidak ada).

Salvador Dali
sumber
10

[]byteadalah slice (mirip dengan substring) dari semua atau bagian dari array byte. Pikirkan slice sebagai struktur nilai dengan bidang penunjuk tersembunyi untuk sistem untuk menemukan dan mengakses semua atau bagian dari array (slice), ditambah bidang untuk panjang dan kapasitas slice, yang dapat Anda akses menggunakan len()dan cap()fungsi .

Ini kit starter yang berfungsi untuk Anda, yang membaca dan mencetak file biner; Anda perlu mengubah nilai inNameliteral untuk merujuk ke file kecil di sistem Anda.

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();
}
peterSO
sumber
9
Go konvensi adalah untuk memeriksa kesalahan pertama, dan membiarkan normal kode berada di luar ifblok
Hasen
@Jurily: Jika file terbuka ketika kesalahan terjadi, bagaimana Anda menutupnya?
peterSO
10
@ PeterSO: use defer
James Antill
Tetapi mengapa byte [256] tidak diterima dan yang jelas konyol dan bertele-tele (tetapi tampaknya tidak salah) diBuf: = make ([] byte, 256) diterima?
Pria luar angkasa cardiff
7

Coba ini:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}
pemasar
sumber
1
Ini akan berfungsi jika Anda ingin membaca seluruh file sekaligus. Jika file tersebut sangat besar atau Anda hanya ingin membaca sebagian saja, mungkin itu bukan yang Anda cari.
Evan Shaw
3
Anda harus benar-benar memeriksa kode kesalahan, dan tidak mengabaikannya seperti itu !!
hasen
7
Ini telah dipindahkan ke paket ioutil sekarang. Jadi itu akan menjadi ioutil.ReadFile ()
Christopher
Saya memperbaikinya, katanya 0644
Joakim
1

Hanya dengan melihat dokumentasi sepertinya Anda harus mendeklarasikan buffer bertipe [] byte dan meneruskannya untuk membaca yang kemudian akan membaca hingga banyak karakter dan mengembalikan jumlah karakter yang benar-benar dibaca (dan kesalahan).

Dokumen mengatakan

Baca dibaca hingga len (b) byte dari File. Ini mengembalikan jumlah byte yang dibaca dan Kesalahan, jika ada. EOF ditandai oleh hitungan nol dengan err diatur ke EOF.

Apakah itu tidak berhasil?

EDIT: Juga, saya pikir Anda mungkin harus menggunakan antarmuka Reader / Writer yang dinyatakan dalam paket bufio daripada menggunakan paket os .

Hannes Ovrén
sumber
Anda memiliki suara saya karena Anda benar-benar mengakui apa yang dilihat orang nyata ketika mereka membaca dokumentasi, alih-alih menyebutkan apa yang biasa dilakukan Go adalah DIINGATKAN (tidak membaca DIPERINGATKAN) ketika mereka membaca dokumentasi dari fungsi yang sudah mereka kenal.
Pria luar angkasa cardiff
1

Metode Read mengambil parameter byte karena itu buffer yang akan dibaca. Ini adalah Idiom yang umum di beberapa kalangan dan masuk akal ketika Anda memikirkannya.

Dengan cara ini Anda dapat menentukan berapa banyak byte yang akan dibaca oleh pembaca dan memeriksa kembali untuk melihat berapa banyak byte yang benar-benar dibaca dan menangani kesalahan dengan tepat.

Seperti yang orang lain tunjukkan dalam jawaban mereka, bufio mungkin adalah apa yang Anda inginkan untuk membaca dari sebagian besar file.

Saya akan menambahkan satu petunjuk lain karena ini sangat berguna. Membaca satu baris dari suatu file paling baik dilakukan bukan dengan metode ReadLine tetapi metode ReadBytes atau ReadString.

Jeremy Wall
sumber