如何使用Go读写文件

huangapple go评论117阅读模式
英文:

How to read/write from/to a file using Go

问题

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

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

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

I've been trying to learn Go on my own, but I've been stumped on trying read from and write to ordinary files.

I can get as far as inFile, _ := os.Open(INFILE, 0, 0), but actually getting the content of the file doesn't make sense, because the read function takes a []byte as a parameter.

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

答案1

得分: 560

让我们制作一个与Go 1兼容的文件读写方式的列表。

由于文件API最近发生了变化,大多数其他答案在Go 1中不起作用。它们还缺少我认为很重要的bufio

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

从基础开始

package main

import (
	"io"
	"os"
)

func main() {
	// 打开输入文件
	fi, err := os.Open("input.txt")
	if err != nil {
		panic(err)
	}
	// 在退出时关闭fi并检查其返回的错误
	defer func() {
		if err := fi.Close(); err != nil {
			panic(err)
		}
	}()

	// 打开输出文件
	fo, err := os.Create("output.txt")
	if err != nil {
		panic(err)
	}
	// 在退出时关闭fo并检查其返回的错误
	defer func() {
		if err := fo.Close(); err != nil {
			panic(err)
		}
	}()

	// 创建一个缓冲区以保存读取的块
	buf := make([]byte, 1024)
	for {
		// 读取一个块
		n, err := fi.Read(buf)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if n == 0 {
			break
		}

		// 写入一个块
		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() {
	// 打开输入文件
	fi, err := os.Open("input.txt")
	if err != nil {
		panic(err)
	}
	// 在退出时关闭fi并检查其返回的错误
	defer func() {
		if err := fi.Close(); err != nil {
			panic(err)
		}
	}()
	// 创建一个读取缓冲区
	r := bufio.NewReader(fi)

	// 打开输出文件
	fo, err := os.Create("output.txt")
	if err != nil {
		panic(err)
	}
	// 在退出时关闭fo并检查其返回的错误
	defer func() {
		if err := fo.Close(); err != nil {
			panic(err)
		}
	}()
	// 创建一个写入缓冲区
	w := bufio.NewWriter(fo)

	// 创建一个缓冲区以保存读取的块
	buf := make([]byte, 1024)
	for {
		// 读取一个块
		n, err := r.Read(buf)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if n == 0 {
			break
		}

		// 写入一个块
		if _, err := w.Write(buf[:n]); err != nil {
			panic(err)
		}
	}

	if err = w.Flush(); err != nil {
		panic(err)
	}
}

bufio在这里只是充当缓冲区,因为我们对数据没有太多要做的事情。在大多数其他情况下(特别是对于文本文件),bufio非常有用,它为我们提供了一个方便灵活的API,同时在幕后处理缓冲。


**注意:**以下代码适用于旧版本的Go(Go 1.15及之前)。事情已经发生了变化(ioutil自Go 1.16起已被弃用)。有关新方法,请参阅此答案

使用ioutil

package main

import (
	"io/ioutil"
)

func main() {
	// 一次性读取整个文件
	b, err := ioutil.ReadFile("input.txt")
	if err != nil {
		panic(err)
	}

	// 一次性写入整个内容
	err = ioutil.WriteFile("output.txt", b, 0644)
	if err != nil {
		panic(err)
	}
}

简单易行!但只在确定不处理大文件时使用。

英文:

Let's make a Go 1-compatible list of all the ways to read and write files in Go.

Because file API has changed recently and most other answers don't work with Go 1. They also miss bufio which is important IMHO.

In the following examples I copy a file by reading from it and writing to the destination file.

Start with the basics

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

Here I used os.Open and os.Create which are convenient wrappers around os.OpenFile. We usually don't need to call OpenFile directly.

Notice treating EOF. Read tries to fill buf on each call, and returns io.EOF as error if it reaches end of file in doing so. In this case buf will still hold data. Consequent calls to Read returns zero as the number of bytes read and same io.EOF as error. Any other error will lead to a panic.

Using 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 is just acting as a buffer here, because we don't have much to do with data. In most other situations (specially with text files) bufio is very useful by giving us a nice API for reading and writing easily and flexibly, while it handles buffering behind the scenes.


Note: The following code is for older Go versions (Go 1.15 and before). Things have changed (ioutil is deprecated since Go 1.16). For the new way, take a look at this answer.

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

Easy as pie! But use it only if you're sure you're not dealing with big files.

答案2

得分: 57

这是一个好的版本(但请注意,自Go 1.16起,ioutil已被弃用):

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

This is a good version (but do note that ioutil is deprecated since Go 1.16):

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

答案3

得分: 43

新的方法

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

请注意,os.ReadFile会将整个文件读入内存。

package main

import "os"
import "log"

func main() {
    b, err := os.ReadFile("input.txt")
    if err != nil {
        log.Fatal(err)
    }

    // `b` 包含了文件的全部内容。
    // 这将其写入标准输出。
    os.Stdout.Write(b)

    // 你也可以将其作为一个整体写入文件。
    err = os.WriteFile("destination.txt", b, 0644)
    if err != nil {
        log.Fatal(err)
    }
}
英文:

New Way

Starting with Go 1.16, use os.ReadFile to load the file into memory, and use os.WriteFile to write to a file from memory (ioutil.ReadFile now calls os.ReadFile and is deprecated).

Be careful with the os.ReadFile because it reads the whole file into memory.

package main
import "os"
import "log"
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)
}
}

答案4

得分: 41

使用io.Copy

package main

import (
    "io"
    "log"
    "os"
)

func main() {
    // 打开文件 r 和 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()

    // 进行实际的操作
    n, err := io.Copy(w, r)
    if err != nil {
        panic(err)
    }
    log.Printf("已复制 %v 字节\n", n)
}

如果你不想重复造轮子,io.Copyio.CopyN 可能会很有用。如果你查看io.Copy函数的源代码,你会发现它实际上就是 Mostafa 的解决方案之一(实际上是“基本”解决方案),只是在 Go 库中进行了封装。不过他们使用的缓冲区比他使用的要大得多。

英文:

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

If you don't feel like reinventing the wheel, the io.Copy and io.CopyN may serve you well. If you check the source of the io.Copy function, it is nothing but one of the Mostafa's solutions (the 'basic' one, actually) packaged in the Go library. They are using a significantly larger buffer than he is, though.

答案5

得分: 24

注意。自Go 1.16起,ioutil已被弃用

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

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

英文:

Note. ioutil is deprecated since Go 1.16.

With newer Go versions, reading/writing to/from a file is easy. To read from a file:

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

To write to a file:

package main
import "os"
func main() {
file, err := os.Create("text.txt")
if err != nil {
return
}
defer file.Close()
file.WriteString("test\nhello")
}

This will overwrite the content of a file (create a new file if it was not there).

答案6

得分: 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();
}
英文:

[]byte is a slice (similar to a substring) of all or part of a byte array. Think of the slice as a value structure with a hidden pointer field for the system to locate and access all or part of an array (the slice), plus fields for the length and capacity of the slice, which you can access using the len() and cap() functions.

Here's a working starter kit for you, which reads and prints a binary file; you will need to change the inName literal value to refer to a small file on your system.

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

答案7

得分: 7

注意。自Go 1.16起,ReadFileos中。

尝试这样做:

package main

import (
  "io"
)


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

[a]: https://pkg.go.dev/os#ReadFile
英文:

Note. Since Go 1.16 ReadFile is in the os package.

Try this:

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

答案8

得分: 4

你也可以使用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)
}
英文:

you can use the fmt package also:

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

答案9

得分: 1

只看文档,似乎你只需要声明一个类型为[]byte的缓冲区,并将其传递给read函数,该函数将读取多达指定数量的字符,并返回实际读取的字符数(以及错误)。

文档中说

> Read从文件中读取多达len(b)字节。它返回读取的字节数和错误(如果有)。当err设置为EOF时,通过计数为零来表示EOF。

这个方法不起作用吗?

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

英文:

Just looking at the documentation it seems you should just declare a buffer of type []byte and pass it to read which will then read up to that many characters and return the number of characters actually read (and an error).

The docs say

> Read reads up to len(b) bytes from the File. It returns the number of bytes read and an Error, if any. EOF is signaled by a zero count with err set to EOF.

Does that not work?

EDIT: Also, I think you should perhaps use the Reader/Writer interfaces declared in the bufio package instead of using os package.

答案10

得分: 1

Read方法接受一个字节参数,因为它将读取到该缓冲区中。这在某些圈子中是一种常见的习惯用法,当你思考一下的时候,它也是有道理的。

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

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

我还要补充一个提示,因为它非常有用。从文件中读取一行最好不要使用ReadLine方法,而是使用ReadBytes或ReadString方法。

英文:

The Read method takes a byte parameter because that is the buffer it will read into. It's a common idiom in some circles and makes some sense when you think about it.

This way you can determine how many bytes will be read by the reader and inspect the return to see how many bytes actually were read and handle any errors appropriately.

As others have pointed in their answers, bufio is probably what you want for reading from most files.

I'll add one other hint since it's really useful. Reading a line from a file is best accomplished not by the ReadLine method, but the ReadBytes or ReadString method instead.

huangapple
  • 本文由 发表于 2009年12月1日 03:17:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/1821811.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定