Go kullanarak / dosyadan / dosyaya nasıl okunur / yazılır?


284

Go'yu kendi başıma öğrenmeye çalışıyordum, ama sıradan dosyalardan okuma ve yazma konusunda zorlandım.

Kadarıyla alabilirsiniz inFile, _ := os.Open(INFILE, 0, 0), ama aslında okuma fonksiyonu []bytebir parametre olarak alır, çünkü aslında dosya içeriğini almak mantıklı değil .

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

Yanıtlar:


476

Go'daki dosyaları okumak ve yazmak için tüm yolların Go 1 uyumlu bir listesini yapalım.

Dosya API'si son zamanlarda değiştiğinden ve diğer yanıtların çoğu Go 1 ile çalışmadığından, bufioIMHO'nun da önemli olduğunu kaçırıyorlar .

Aşağıdaki örneklerde, dosyayı okuyarak ve hedef dosyaya yazarak kopyalarım.

Temel bilgilerle başlayın

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

Burada kullanılan os.Openve os.Createetrafında uygun sarmalayıcılar olan os.OpenFile. Genellikle OpenFiledoğrudan aramamız gerekmez .

EOF tedavisine dikkat edin. her çağrıyı Readdoldurmaya çalışır bufve io.EOFdosya sonuna ulaşırsa hata olarak döner . Bu durumda bufyine de veri tutulacaktır. Sonuç çağrıları, Readokunan bayt sayısı olarak sıfır ve io.EOFhata ile aynı olarak sıfır döndürür . Başka herhangi bir hata paniğe yol açacaktır.

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

bufioburada sadece tampon görevi görüyor, çünkü verilerle ilgimiz yok. Diğer çoğu durumda (özellikle metin dosyalarıyla)bufio , kolay ve esnek bir şekilde okuma ve yazma için güzel bir API vererek çok yararlıdır , sahne arkasındaki arabelleğe almayı işler.

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

Pasta kadar kolay! Ancak, yalnızca büyük dosyalarla uğraşmadığınızdan eminseniz kullanın.


55
Bu soruya rastlayan herkes için, bu kütüphaneler tanıtılmadan önce 2009'da sorulmuştur, bu yüzden lütfen bu cevabı rehberiniz olarak kullanın!
Seth Hoenig

1
Golang.org/pkg/os/#File.Write'a göre, Write tüm baytları yazmadığında bir hata döndürür. Bu nedenle, ilk örnekteki ( panic("error in writing")) ekstra kontrol gerekli değildir.
ayke

15
Bu örneklerin fo.Close () 'dan hata dönüşünü kontrol etmediğini unutmayın. Linux man sayfalarından close (2): close () 'nin dönüş değerini kontrol etmemek yaygın fakat yine de ciddi bir programlama hatasıdır. Önceki yazma (2) işlemindeki hataların ilk olarak son kapanışta () bildirilmesi oldukça olasıdır. Dosyayı kapatırken dönüş değerinin kontrol edilmemesi sessiz veri kaybına neden olabilir. Bu özellikle NFS ve disk kotası ile gözlemlenebilir.
Nick Craig-Wood

12
Peki, "büyük" dosya nedir? 1 KB? 1MB? 1GB? Yoksa "büyük" makine donanımına mı bağlı?
425nesp

3
@ 425nesp Dosyanın tamamını belleğe okur, bu nedenle çalışan makinedeki kullanılabilir bellek miktarına bağlıdır.
Mostafa

50

Bu iyi bir sürüm:

package main

import (
  "io/ioutil"; 
  )


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

8
Bu, tüm dosyayı bellekte saklar. Dosya büyük olabileceğinden, her zaman yapmak istediğiniz şey olmayabilir.
user7610

9
Ayrıca, 0x777sahte. Her durumda, daha fazla 0644veya 0755(sekizli, altıgen değil) olmalıdır.
cnst

@cnst 0x777
Trenton

31

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

Tekerleği yeniden icat gibi hissediyorum yoksa, io.Copyve io.CopyNiyi hizmet verebilir. İo.Copy işlevinin kaynağını kontrol ederseniz , bu Git kütüphanesinde paketlenmiş olan Mostafa'nın çözümlerinden (aslında 'temel' bir çözüm) bir şey değildir. Yine de ondan çok daha büyük bir tampon kullanıyorlar.


5
w.Sync()io.Copy(w, r)
kayda

Ayrıca, zaten var olan bir dosyaya io.Copy()yazarsanız, yalnızca onunla beslediğiniz verileri yazar, bu nedenle mevcut dosyada daha fazla içerik varsa, dosya silinmez, bu da bozuk dosyaya neden olabilir.
Invidian

1
@Invidian Her şey hedef dosyayı nasıl açtığınıza bağlıdır. Bunu yaparsanız w, err := os.Create("output.txt"), açıkladığınız şey gerçekleşmez, çünkü "Oluştur adlı dosyayı oluşturur veya keser. Dosya zaten varsa, kesilir." golang.org/pkg/os/#Create .
user7610

Bu doğru yanıt olmalıdır, çünkü okumadan önce tüm dosyayı bir kerede okumak zorunda kalmazken tekerleği yeniden icat etmez.
Eli Davis

11

Daha yeni Go sürümleriyle dosyaya / dosyadan yazma / yazma kolaydır. Bir dosyadan okumak için:

package main

import (
    "fmt"
    "io/ioutil"
)

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

Bir dosyaya yazmak için:

package main

import "os"

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

    file.WriteString("test\nhello")
}

Bu, bir dosyanın içeriğinin üzerine yazacaktır (orada değilse yeni bir dosya oluşturun).


10

[]bytebir bayt dizisinin tamamının veya bir kısmının dilimidir (bir alt dizeye benzer). Dilimin, sistemin bir dizinin tamamını veya bir bölümünü (dilim) bulması ve bunlara erişmesi için gizli işaretçi alanı olan bir değer yapısı, artı len()ve cap()işlevlerini kullanarak erişebileceğiniz dilim uzunluğu ve kapasitesi için alanlar olarak düşünün .

İşte sizin için, ikili dosyayı okuyan ve yazdıran çalışan bir başlangıç ​​kiti; inNamesisteminizdeki küçük bir dosyaya başvurmak için değişmez değeri değiştirmeniz gerekir .

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
Git kuralı, önce hatayı kontrol etmek ve normal kodun ifblok dışında
kalmasına

@Jurily: Hata oluştuğunda dosya açıksa, dosyayı nasıl kapatırsınız?
peterSO

10
@peterSO: erteleme kullan
James

Peki neden [256] bayt kabul edilmiyor ve açık saçma ve ayrıntılı (ama görünüşte yanlış değil) inBuf: = make ([] byte, 256) kabul ediliyor?
cardiff space man

7

Bunu dene:

package main

import (
  "io"; 
  )


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

1
Bu, tüm dosyayı bir kerede okumak istiyorsanız çalışır. Dosya gerçekten büyükse veya yalnızca bir kısmını okumak istiyorsanız, aradığınız şey olmayabilir.
Evan Shaw

3
Gerçekten hata kodunu kontrol etmeli ve böyle görmezden gelmemelisiniz !!
hasen

7
Bu şimdi ioutil paketine taşındı. Öyleyse ioutil.ReadFile ()
Christopher

Sabitledim yani 0644 diyor
Joakim

1

Sadece belgelere baktığınızda, [] bayt tipinde bir tampon bildirmeniz ve daha sonra bu kadar karaktere kadar okuyacak ve gerçekten okunan karakterlerin sayısını (ve bir hatayı) döndürecek şekilde okumalısınız.

Dokümanlar diyor ki

Dosyadan len (b) bayta kadar okuma yapar. Okunan bayt sayısını ve varsa bir Hata değerini döndürür. EOF, hata EOF olarak ayarlanmış bir sıfır sayımı ile bildirilir.

Bu çalışmıyor mu?

EDIT: Ayrıca, sanırım os paketi kullanmak yerine bufio paketinde bildirilen Reader / Writer arabirimlerini kullanmanız gerektiğini düşünüyorum .


Oyumu bana verdiniz, çünkü gerçekte, alışkın oldukları işlevin belgelerini okuduklarında, gitmeye alışkın olanların ne anlama geldiğini (REMINDED OF okumuyor) papağan yapmak yerine, belgeleri okuduklarında ne gördüklerini kabul ediyorsunuz.
cardiff space man

1

Read yöntemi bir bayt parametresi alır, çünkü okuyacağı tampon budur. Bazı çevrelerde ortak bir deyimdir ve bunu düşündüğünüzde bir anlam ifade eder.

Bu şekilde, okuyucu tarafından kaç bayt okunacağını belirleyebilir ve gerçekte kaç baytın okunduğunu görmek ve herhangi bir hatayı uygun şekilde işlemek için dönüşü denetleyebilirsiniz.

Diğerleri yanıtlarında işaret ettiği gibi, bufio muhtemelen çoğu dosyadan okumak istediğiniz şeydir.

Gerçekten yararlı olduğu için bir ipucu daha ekleyeceğim. Dosyadan bir satır okumak en iyi şekilde ReadLine yöntemiyle değil ReadBytes veya ReadString yöntemiyle yapılır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.