如何使用Go从文件读/写文件?


284

我一直在尝试自己学习Go,但是在尝试读取和写入普通文件时遇到了麻烦。

我可以说到最远inFile, _ := os.Open(INFILE, 0, 0),但是实际上获取文件的内容没有任何意义,因为read函数将a []byte作为参数。

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

Answers:


476

让我们列出与Go 1兼容的清单,其中列出了在Go中读取和写入文件的所有方式。

由于文件API最近已更改,并且大多数其他答案不适用于Go1。他们也错过了bufio重要的恕我直言。

在以下示例中,我通过读取文件并将其写入目标文件来复制文件。

从基础开始

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()返回的错误。从Linux手册页close(2):不检查close()的返回值是一个常见的但仍然很严重的编程错误。很有可能首先在最后的close()中报告了先前write(2)操作的错误。关闭文件时不检查返回值可能会导致数据静默丢失。使用NFS和磁盘配额尤其可以观察到这一点。
Nick Craig-Wood

12
那么,什么是“大”文件?1KB?1MB?1GB?还是“大”取决于机器的硬件?
425nesp 2014年

3
@ 425nesp它将整个文件读入内存,因此它取决于正在运行的计算机中的可用内存量。
Mostafa 2015年

49

这是好版本:

package main

import (
  "io/ioutil"; 
  )


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

8
这会将整个文件存储在内存中。由于文件可能很大,因此可能并不总是您想要的。
user7610 2014年

9
另外,0x777是假的。无论如何,它都应该更像06440755(八进制,而不是十六进制)。
cnst 2014年

@cnst将其从0x777更改为0644
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)
}

如果您不想重新发明轮子,the io.Copyio.CopyN可能会为您服务。如果您检查 io.Copy函数的源代码,那么它只是Go库中打包的Mostafa解决方案之一(实际上是“基本”解决方案)。但是,他们使用的缓冲区比他大得多。


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

这应该是正确的答案,因为它不会重新发明轮子,而不必在读取文件之前立即读取整个文件。
伊莱·戴维斯

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
Go约定是首先检查错误,然后让正常代码驻留在if块外部
黑森

@Jurily:如果在发生错误时打开文件,如何关闭它?
peterSO

10
@peterSO:使用延迟
詹姆斯·

但是,为什么不接受[256] byte,却接受inBuf:= make([] byte,256)中明显的愚蠢和冗长(但显然没有错)呢?
卡迪夫太空人

7

尝试这个:

package main

import (
  "io"; 
  )


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

1
如果您想一次读取整个文件,这将起作用。如果文件很大,或者您只想阅读其中的一部分,则可能不是您要查找的文件。
埃文·肖

3
您应该真正检查错误代码,而不是那样忽略它!!
哈森

7
现在已将其移至ioutil软件包中。因此应该是ioutil.ReadFile()
Christopher 2010年

我固定好了,然后说“ 0644
Joakim

1

只是看一下文档,似乎您应该只声​​明[] byte类型的缓冲区并将其传递给read,然后它将读取多达这么多的字符并返回实际读取的字符数(以及错误)。

医生

读取从文件中读取多达len(b)个字节。它返回读取的字节数和一个错误(如果有)。通过将err设置为EOF的零计数来发信号通知EOF。

那行不通吗?

编辑:另外,我认为您也许应该使用bufio包中声明的Reader / Writer接口,而不要使用os包。


我之所以投票,是因为您实际上承认真正的人在阅读文档时看到的内容,而不是在阅读他们熟悉的功能的文档时,会模仿那些习惯于Go的人而不是他们(而不是REMINDED OF)。
卡迪夫太空人2012年

1

Read方法采用一个byte参数,因为这是它将读取到的缓冲区。在某些圈子里这是一个常见的成语,并且当您考虑它时会说得通。

这样,您可以确定读取器将读取多少字节,并检查返回内容以查看实际读取了多少字节并适当地处理任何错误。

正如其他人在他们的答案中指出的那样,bufio可能是您想要从大多数文件中读取的内容。

我将添加另一个提示,因为它确实很有用。从文件读取行最好不是通过ReadLine方法,而是通过ReadBytes或ReadString方法来完成。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.