【发布时间】:2010-12-21 18:20:44
【问题描述】:
我一直在尝试自学 Go,但在尝试读取和写入普通文件时遇到了困难。
我可以到inFile, _ := os.Open(INFILE, 0, 0),但实际上获取文件的内容没有意义,因为读取函数将[]byte作为参数。
func (file *File) Read(b []byte) (n int, err Error)
【问题讨论】:
我一直在尝试自学 Go,但在尝试读取和写入普通文件时遇到了困难。
我可以到inFile, _ := os.Open(INFILE, 0, 0),但实际上获取文件的内容没有意义,因为读取函数将[]byte作为参数。
func (file *File) Read(b []byte) (n int, err Error)
【问题讨论】:
仅查看文档,您似乎应该只声明一个 []byte 类型的缓冲区并将其传递给 read,然后 read 将读取那么多字符并返回实际读取的字符数(以及一个错误)。
Read 从文件中读取最多 len(b) 个字节。它返回读取的字节数和错误(如果有)。 EOF 由 err 设置为 EOF 的零计数发出信号。
这样不行吗?
编辑:另外,我认为您也许应该使用 bufio 包中声明的 Reader/Writer 接口,而不是使用 os 包。
【讨论】:
试试这个:
package main
import (
"io";
)
func main() {
contents,_ := io.ReadFile("filename");
println(string(contents));
io.WriteFile("filename", contents, 0644);
}
【讨论】:
[]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();
}
【讨论】:
if 块之外
这是一个很好的版本:
package main
import (
"io/ioutil";
)
func main() {
contents,_ := ioutil.ReadFile("plikTekstowy.txt")
println(string(contents))
ioutil.WriteFile("filename", contents, 0644)
}
【讨论】:
0x777 是假的。无论如何,它应该更像0644 或0755(八进制,而不是十六进制)。
让我们列出在 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.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 非常有用,因为它提供了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)
}
}
简单易行!但只有在您确定自己不是在处理大文件时才使用它。
【讨论】:
panic("error in writing")) 中的额外检查是不必要的。
Read 方法接受一个字节参数,因为这是它将读取到的缓冲区。在某些圈子里,这是一个常见的习语,仔细想想还是有道理的。
通过这种方式,您可以确定读取器将读取多少字节并检查返回以查看实际读取了多少字节并适当地处理任何错误。
正如其他人在他们的答案中指出的那样,bufio 可能是您想要从大多数文件中读取的内容。
我将添加另一个提示,因为它真的很有用。从文件中读取一行最好不要使用 ReadLine 方法,而是使用 ReadBytes 或 ReadString 方法。
【讨论】:
使用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.Copy 和 io.CopyN 可能会为您服务。如果您是 io.Copy 函数的 check the source,它只不过是 Go 库中打包的 Mostafa 解决方案之一(实际上是“基本”解决方案)。不过,他们使用的缓冲区比他大得多。
【讨论】:
io.Copy(w, r)之后使用w.Sync()
io.Copy() 只会写入你提供给它的数据,所以如果现有文件有更多的内容,它不会被删除,这可能会导致损坏的文件。
w, err := os.Create("output.txt"),则您所描述的不会发生,因为“创建创建或截断命名文件。如果文件已存在,则将其截断。” golang.org/pkg/os/#Create.
使用较新的 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")
}
这将覆盖文件的内容(如果不存在则创建一个新文件)。
【讨论】:
从 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)
}
}
【讨论】:
您也可以使用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)
}
【讨论】: