【问题标题】:How to read/write from/to a file using Go如何使用 Go 读取/写入文件
【发布时间】:2010-12-21 18:20:44
【问题描述】:

我一直在尝试自学 Go,但在尝试读取和写入普通文件时遇到了困难。

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

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

【问题讨论】:

    标签: file go


    【解决方案1】:

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

    The docs

    Read 从文件中读取最多 len(b) 个字节。它返回读取的字节数和错误(如果有)。 EOF 由 err 设置为 EOF 的零计数发出信号。

    这样不行吗?

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

    【讨论】:

    • 你有我的投票,因为你实际上承认真实的人在阅读文档时所看到的,而不是在那些习惯于 Go 的人阅读文档时重复他们被提醒(而不是阅读提醒)的内容他们已经熟悉的功能。
    【解决方案2】:

    试试这个:

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

    【讨论】:

    • 如果您想一次读取整个文件,这将起作用。如果文件非常大,或者您只想阅读其中的一部分,那么它可能不是您要查找的内容。
    • 你真的应该检查错误代码,而不是那样忽略它!
    • 这已经被移到了 ioutil 包中。所以应该是 ioutil.ReadFile()
    • 我修好了,所以它说 0644
    【解决方案3】:

    []byte 是全部或部分字节数组的切片(类似于子字符串)。将切片视为具有隐藏指针字段的值结构,供系统定位和访问全部或部分数组(切片),以及切片长度和容量字段,您可以使用@987654322 访问这些字段@ 和 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();
    }
    

    【讨论】:

    • Go 的约定是先检查错误,然后让正常代码驻留在if 块之外
    • @Jurily:如果出错时文件是打开的,怎么关闭?
    • 但是为什么 [256]byte 不被接受,而 inBuf:=make([]byte, 256) 明显愚蠢和冗长(但显然没有错)被接受?
    【解决方案4】:

    这是一个很好的版本:

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

    【讨论】:

    • 这会将整个文件存储在内存中。由于文件可能很大,这可能并不总是您想要做的。
    • 另外,0x777 是假的。无论如何,它应该更像06440755(八进制,而不是十六进制)。
    • @cnst 将其从 0x777 更改为 0644
    【解决方案5】:
    推荐的答案 Go Language

    让我们列出在 Go 中读写文件的所有方式的 Go 1 兼容列表。

    因为文件 API 最近发生了变化,并且大多数其他答案不适用于 Go 1。他们也错过了 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.Openos.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 非常有用,因为它提供了a nice API,让我们可以轻松灵活地进行读写,同时处理后台缓冲。


    注意:以下代码适用于较旧的 Go 版本(Go 1.15 及之前版本)。事情变了。新方式请看this answer

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

    简单易行!但只有在您确定自己不是在处理大文件时才使用它。

    【讨论】:

    • 对于任何偶然发现这个问题的人,最初是在 2009 年引入这些库之前提出的,所以请将此答案作为您的指南!
    • 根据golang.org/pkg/os/#File.Write,当Write还没有写入所有字节时,它会返回错误。因此,第一个示例 (panic("error in writing")) 中的额外检查是不必要的。
    • 请注意,这些示例不检查从 fo.Close() 返回的错误。来自 Linux 手册页 close(2):不检查 close() 的返回值是一个常见但严重的编程错误。上一次 write(2) 操作的错误很有可能在最后的 close() 中首先报告。关闭文件时不检查返回值可能会导致数据无声丢失。这在 NFS 和磁盘配额中尤其明显。
    • 那么,什么是“大”文件? 1KB? 1MB? 1GB?还是“大”取决于机器的硬件?
    • @425nesp 它将整个文件读入内存,所以它取决于运行机器的可用内存量。
    【解决方案6】:

    Read 方法接受一个字节参数,因为这是它将读取到的缓冲区。在某些圈子里,这是一个常见的习语,仔细想想还是有道理的。

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

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

    我将添加另一个提示,因为它真的很有用。从文件中读取一行最好不要使用 ReadLine 方法,而是使用 ReadBytes 或 ReadString 方法。

    【讨论】:

      【解决方案7】:

      使用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.Copyio.CopyN 可能会为您服务。如果您是 io.Copy 函数的 check the source,它只不过是 Go 库中打包的 Mostafa 解决方案之一(实际上是“基本”解决方案)。不过,他们使用的缓冲区比他大得多。

      【讨论】:

      • 有一点值得一提——要确保文件的内容写入磁盘,需要在io.Copy(w, r)之后使用w.Sync()
      • 另外,如果你写入已经存在的文件,io.Copy() 只会写入你提供给它的数据,所以如果现有文件有更多的内容,它不会被删除,这可能会导致损坏的文件。
      • @Invidian 这完全取决于您如何打开目标文件。如果您执行w, err := os.Create("output.txt"),则您所描述的不会发生,因为“创建创建或截断命名文件。如果文件已存在,则将其截断。” golang.org/pkg/os/#Create.
      • 这应该是正确的答案,因为它不会重新发明轮子,而不必在阅读之前一次读取整个文件。
      【解决方案8】:

      使用较新的 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")
      }
      

      这将覆盖文件的内容(如果不存在则创建一个新文件)。

      【讨论】:

        【解决方案9】:

        新方式

        从 Go 1.16 开始,使用 os.ReadFile 将文件加载到内存中,并使用 os.WriteFile 从内存中写入文件(ioutil.ReadFile 现在调用 os.ReadFile)。

        小心os.ReadFile,因为它将整个文件读入内存。

        package main
        
        import "os"
        
        func main() {
            b, err := os.ReadFile("input.txt")
            if err != nil {
                log.Fatal(err)
            }
        
            // `b` contains everything your file does
            // This writes it to the Standard Out
            os.Stdout.Write(b)
        
            // You can also write it to a file as a whole
            err = os.WriteFile("destination.txt", b, 0644)
            if err != nil {
                log.Fatal(err)
            }
        }
        

        【讨论】:

          【解决方案10】:

          您也可以使用fmt 包:

          package main
          
          import "fmt"
          
          func main(){
              file, err := os.Create("demo.txt")
              if err != nil {
                  panic(err)
              }
              defer file.Close()
              
              fmt.Fprint(file, name)
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-09-16
            • 1970-01-01
            • 2012-09-15
            • 2015-07-26
            • 1970-01-01
            • 2020-12-06
            • 2011-08-07
            相关资源
            最近更新 更多