ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 Go 读取/写入文件

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

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

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

I
Inanc Gumus

让我们列出在 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 它将整个文件读入内存,因此它取决于正在运行的机器中的可用内存量。
P
Peter Mortensen

这是一个很好的版本:

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
u
user7610

使用 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
这应该是正确的答案,因为它不会重新发明轮子,而不必在读取整个文件之前立即读取它。
I
Inanc Gumus

新的方法

从 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 has.
    // 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)
    }
}

P
Peter Mortensen

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

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


p
peterSO

[]byte 是全部或部分字节数组的切片(类似于子字符串)。将切片视为具有隐藏指针字段的值结构,供系统定位和访问数组(切片)的全部或部分,以及切片长度和容量的字段,您可以使用 {2 } 和 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) 显然愚蠢而冗长(但显然没有错)被接受?
J
Joakim

尝试这个:

package main

import (
  "io"; 
  )


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

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

您也可以使用 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)
}

H
Hannes Ovrén

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

The docs

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

那不行吗?

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


你有我的投票,因为你实际上承认真实的人在阅读文档时看到的内容,而不是在阅读他们已经熟悉的函数的文档时重复那些习惯于 Go 的人被提醒(而不是阅读提醒)的内容。
P
Peter Mortensen

Read 方法接受一个字节参数,因为这是它将读取到的缓冲区。在某些圈子中,这是一个常见的成语,当你考虑它时,它是有道理的。

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

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

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