问题
golang 使用 ioutil.TempFile
或者 os.OpenFile
进行文件文件写入之后对 *os.File
进行直接读取操作的时候,读取到的往往是空的。看下面的例子
os.OpenFile 示例:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
wdata := []byte("Writer data to file.")
// 打开一个文件
file, err := os.OpenFile("/tmp/test.data", os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModeAppend|os.ModePerm)
defer file.Close()
if err != nil {
fmt.Println(err)
}
// 执行写入操作
file.Write(wdata)
// 对 *os.file 执行扫描
s := bufio.NewScanner(file)
for s.Scan() {
fmt.Println(s.Text())
}
}
对 *os.File
的类型 file 进行扫描和打印发现输出为空,我们明明进行了写入操作,为啥读取不到?
ioutil.TempFile 也是一样:
package main
import (
"fmt"
"io/ioutil"
"os"
)
func main() {
data := []byte(`
["aaaaa",
"22222",
3333,]`)
tmpfile, err := ioutil.TempFile("", "test")
if err != nil {
fmt.Println(err)
}
tmpfile.Write([]byte(data))
tout, terr := ioutil.ReadAll(tmpfile)
if err != nil {
fmt.Println(terr)
}
// 直接读取为空
fmt.Println("read temp", string(tout))
// 用os.Open 再从文件读取一次,有数据返回
of, oerr := os.Open(tmpfile.Name())
if oerr != nil {
fmt.Println(oerr)
}
all, _ := ioutil.ReadAll(of)
fmt.Println(string(all))
of.Close()
tmpfile.Close()
os.Remove(tmpfile.Name())
}
看到上面的列子,如果再对文件直接一次读取,不从 *os.File
返回就可以获取到数据。
io.Seeker
上面的例子让人感到困惑,为什么直接从 *os.File
或不去不到数据,而再读取一次文件返回的 *os.File
就读取不到内容? bufio.NewScanner
和 ioutil.ReadAll
传参都是 io.Reader
接口实现链式读取,从 *os.File
从读取内容,同样是 *os.File
为什么写入之后就读取不到内容?再读取一次就能获取到? 再来看看 *os.File
就明白了:
type File struct {
// contains filtered or unexported fields
}
File represents an open file descriptor. 返回的 *os.File
是一个文件描述符。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
嗯...,概念挺多。需要理解的是,把文件描述符理解为操作文件的门把手就行了,golang 的 *os.File
里面有很多方法(c/c++ 也差不多),而且各个操作系统的文件描述符还不同。先回到问题上,跟我们相关的是 seeker
这个方法。 seek() 方法用于移动文件读取指针到指定位置。
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Seek设置下一次读/写的位置。offset为相对偏移量,而whence决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。它返回新的偏移量(相对开头)和可能的错误。
了解了有这个东西之后,那下面的就好理解了。当创建一个文件并往里面写入内容的时候,指针会移动到该偏移量,即文件的末尾。当我们从 *os.File
读取的时候就会从末尾读取导致我们读取不到内容(io.EOF).
解决
了解了上面的流程之后,只需要对 *os.File
进行 ”归位“ 操作即可,即让偏移量回到文件的开头,再从文件的开头读取一遍:
tempfile.Seek(0,0)
ioutil.ReadAll(tempfile)
参考
- golang File.Seek : https://pkg.go.dev/os#File.Seek
- 文件描述符维基百科:https://zh.wikipedia.org/wiki/%E6%96%87%E4%BB%B6%E6%8F%8F%E8%BF%B0%E7%AC%A6