Golang *os.file 读取为空的问题

原创
2021/11/09 17:24
阅读数 84

问题

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.NewScannerioutil.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 是一个文件描述符。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIXLinux这样的操作系统。

嗯...,概念挺多。需要理解的是,把文件描述符理解为操作文件的门把手就行了,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)

参考

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部