Go 仅读取文件最后一行踩坑

1,251 阅读4分钟

起因

只想读取文件的最后一行,但是只找到一些通过循环到最后一行的方法,但是需求是只需要看最后一行,这样大可不必,我的文本内容是多行json的字符串,目前测试只有两行 image.png

尝试方法

一开始想倒着读取,从文件末尾开始,然后找到上一行换行符也就是'\n' 于是就有了如下代码

func getLastline() {
   file, err := os.OpenFile("checkLog.txt", os.O_RDONLY, 0600)
   if err != nil {
      panic(err)
   }
   defer file.Close()
   info, _ := file.Stat()
   if info.Size() > 0 {
      index := int64(-1)
      r := bufio.NewReader(file)
      for {
         index--
         file.Seek(index, io.SeekEnd)
         readByte, err := r.ReadByte()
         if readByte == '\n' {
            break
         }
         if err != nil {
            if err == io.EOF {
               break
            }
            fmt.Println(err) //{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}:"tarot3.jpg"}
         }
      }
      line, _, _ := r.ReadLine()
      fmt.Println(string(line))
   }

}

问题出现了最后打印出的结果是:

{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}:"tarot3.jpg"}

但是期望值应该是:

{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}

这多出来一节不知道是怎么回事,误打误撞又调用了一次file.Seek()后又正确了

func getLastERR() {
   file, err := os.OpenFile("checkLog.txt", os.O_RDONLY, 0600)
   if err != nil {
      panic(err)
   }
   defer file.Close()
   info, _ := file.Stat()
   if info.Size() > 0 {
      index := int64(-1)
      r := bufio.NewReader(file)
      for {
         index--
         file.Seek(index, io.SeekEnd)
         readByte, err := r.ReadByte()
         if readByte == '\n' {
            break
         }
         if err != nil {
            if err == io.EOF {
               break
            }
            fmt.Println(err)
         }
      }
      file.Seek(0, io.SeekEnd)
      line, _, _ := r.ReadLine()
      fmt.Println(string(line))//{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}
   }

}

打印出如下:

{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}

当时直接匪夷所思了,猜测是因为bufio.reader是有缓存的,所以读取到最后一个字节是'\n'后依旧有一部分缓存在buf里,重新定位了一下seek就好了,那么在if readByte=='\n'后重定位seek应该也是可以的,于是代码又改成这样,结果也是正确的。

func getLastERR() {
   file, err := os.OpenFile("checkLog.txt", os.O_RDONLY, 0600)
   if err != nil {
      panic(err)
   }
   defer file.Close()
   info, _ := file.Stat()
   if info.Size() > 0 {
      index := int64(-1)
      r := bufio.NewReader(file)
      for {
         index--
         file.Seek(index, io.SeekEnd)
         readByte, err := r.ReadByte()
         if readByte == '\n' {
            file.Seek(0, io.SeekEnd)
            break
         }
         if err != nil {
            if err == io.EOF {
               break
            }
            fmt.Println(err)
         }
      }
      line, _, _ := r.ReadLine()
      fmt.Println(string(line))//{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}
   }

}

尝试peek看一下构造reader的时候缓冲区的内容于是r.peek了一下:

 bs,_:=r.Peek(4096)
      fmt.Println(string(bs))//{"cardTime":"2023-07-08 21:46:51","cardId":"tarot3.jpg"}{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}
{"cardTime":"2023-07-08 21:46:51","cardId":"tarot3.jpg"}
{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}

当场又匪夷所思了,peek之后即使没有重新file.Seek(),最后一行的读取又正确了。按理说peek只是看一下缓冲区里后面的数据,不应该会有操作才对,不知道为什么peek一下读取的内容就正确了。

func getLastERR() {
   file, err := os.OpenFile("checkLog.txt", os.O_RDONLY, 0600)
   if err != nil {
      panic(err)
   }
   defer file.Close()
   info, _ := file.Stat()
   if info.Size() > 0 {
      index := int64(-1)
      r := bufio.NewReader(file)
      bs,_:=r.Peek(4096)
      fmt.Println(string(bs))//{"cardTime":"2023-07-08 21:46:51","cardId":"tarot3.jpg"}{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}
      for {
         index--
         file.Seek(index, io.SeekEnd)
         readByte, err := r.ReadByte()
         if readByte == '\n' {
            //file.Seek(0, io.SeekEnd)
            break
         }
         if err != nil {
            if err == io.EOF {
               break
            }
            fmt.Println(err)
         }
      }
      line, _, _ := r.ReadLine()
      fmt.Println(string(line))//{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}
   }

}

这下直接不敢确定原因了,就直接一个一个的读取文件,之后再去readline,代码如下:

func getLastOk() {
   file, err := os.OpenFile("checkLog.txt", os.O_RDONLY, 0600)
   if err != nil {
      panic(err)
   }
   defer file.Close()
   info, _ := file.Stat()
   b := []byte{0}
   if info.Size() > 0 {
      index := int64(-1)
      for {
         index--
         file.Seek(index, io.SeekEnd)
         _, err := file.Read(b)
         if b[0] == '\n' {
            break
         }
         if err != nil {
            if err == io.EOF {
               break
            }
            fmt.Println(err)
         }
      }
      rd := bufio.NewReader(file)
      line, _, _ := rd.ReadLine()
      fmt.Println(string(line))
   }

}

这样也是可以正确读取文本的最后一行的,但是上面错误的原因还没完全搞清楚,而暂时用的这种方法是会频繁直接读取文件,理论上是不推荐的

更新

昨天翻到一篇讲述go的bufio包的文章,如下图,可能是bufio的缓存区非空,然后从最后一行开始往前移动seek指针不会读取到最后一行的\n,而读取的长度也比缓冲区小,导致缓冲区只更新了一部分也就读出来的长度的内容,readline又是读到\n,也就把残留的也一起读出来了。

硬核,图解bufio包系列之读取原理 image.png

打印一下最后的缓冲区内容:

func getLastERR() {
   file, err := os.OpenFile("checkLog.txt", os.O_RDONLY, 0600)
   if err != nil {
      panic(err)
   }
   defer file.Close()
   info, _ := file.Stat()
   if info.Size() > 0 {
      index := int64(-1)
      r := bufio.NewReader(file)
      for {
         index--
         file.Seek(index, io.SeekEnd)
         readByte, err := r.ReadByte()
         if readByte == '\n' {
            //file.Seek(0, io.SeekEnd)
            break
         }
         if err != nil {
            if err == io.EOF {
               break
            }
            fmt.Println(err)
         }
      }
      bytes, _ := rd.Peek(1000)
      fmt.Println(string(bytes))
      line, _, _ := r.ReadLine()
      fmt.Println(string(line))//{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}
   }

}

果然,第一行是残留了,并且会读取到 :"tarot3.jpg"}\n 的\n截止

{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}:"tarot3.jpg"}
{"cardTime":"2023-07-08 21:46:58","cardId":"tarot5.jpg"}

但是为什么在构造bufio.reader的时候就不会残留还是不清楚,不知道是不是bug

      r := bufio.NewReader(file)
      bs,_:=r.Peek(4096)
      fmt.Println(string(bs))//{"cardTime":"2023-07-08 21:46:51","cardId":"tarot3.jpg"}