如何从大日志文件中读取最后两行而不将其完全加载到内存中?
我需要每10秒阅读一次(在Win机器上)...而且我一直试图读取最后一行...
package main
import (
"fmt"
"time"
"os"
)
const MYFILE = "logfile.log"
func main() {
c := time.Tick(10 * time.Second)
for now := range c {
readFile(MYFILE)
}
}
func readFile(fname string){
file, err:=os.Open(fname)
if err!=nil{
panic(err)
}
buf:=make([]byte, 32)
c, err:=file.ReadAt(32, ????)
fmt.Printf("%s\n", c)
}
日志文件类似于:
07/25/2013 11:55:42.400, 0.559
07/25/2013 11:55:52.200, 0.477
07/25/2013 11:56:02.000, 0.463
07/25/2013 11:56:11.800, 0.454
07/25/2013 11:56:21.600, 0.424
07/25/2013 11:56:31.400, 0.382
07/25/2013 11:56:41.200, 0.353
07/25/2013 11:56:51.000, 0.384
07/25/2013 11:57:00.800, 0.393
07/25/2013 11:57:10.600, 0.456
谢谢!
答案 0 :(得分:11)
您几乎可以使用file.Seek()或file.ReadAt(),然后向前阅读。你只能估计从哪里开始寻找,除非你知道2行= x字节。
您可以使用os.Stat(name)
获取文件长度以下是基于ReadAt,Stat和您的示例日志文件的示例:
package main
import (
"fmt"
"os"
"time"
)
const MYFILE = "logfile.log"
func main() {
c := time.Tick(10 * time.Second)
for _ = range c {
readFile(MYFILE)
}
}
func readFile(fname string) {
file, err := os.Open(fname)
if err != nil {
panic(err)
}
defer file.Close()
buf := make([]byte, 62)
stat, err := os.Stat(fname)
start := stat.Size() - 62
_, err = file.ReadAt(buf, start)
if err == nil {
fmt.Printf("%s\n", buf)
}
}
答案 1 :(得分:3)
我认为File.Seek(0, 2)
和File.Read()
的组合应该有用。
Seek
调用会将您带到文件末尾。你可以在EOF之前Seek
到一个位置来获得最后几行。然后你Read
直到EOF,然后在你的goroutine中睡10秒钟;下一个Read
有机会为您提供更多数据。
您可以从GNU tail
's source抓取这个想法(以及最初显示最后几行的回扫逻辑)。
答案 2 :(得分:2)
嗯,这只是一个原创的想法,可能不是最好的方法,你应该检查并改进它,但似乎有效......
我希望经验丰富的Go用户也可以做出贡献。
使用Stat,您可以获取文件的大小,并从中获取用于ReadAt
的偏移量func readLastLine(fname string) {
file, err := os.Open(fname)
if err != nil {
panic(err)
}
defer file.Close()
fi, err := file.Stat()
if err != nil {
fmt.Println(err)
}
buf := make([]byte, 32)
n, err := file.ReadAt(buf, fi.Size()-int64(len(buf)))
if err != nil {
fmt.Println(err)
}
buf = buf[:n]
fmt.Printf("%s", buf)
}
答案 3 :(得分:1)
有些人会来到此页面,以寻求有效地读取日志文件的最后一行(如tail命令行工具)。
这是我的版本,可读取大文件的最后一行。它使用了之前的两个建议(使用Seek和文件Stat)。
它将逐个字节地向后读取文件(无需设置缓冲区大小),直到找到一行的开始或文件的开始。
func getLastLineWithSeek(filepath string) string {
fileHandle, err := os.Open(filepath)
if err != nil {
panic("Cannot open file")
os.Exit(1)
}
defer fileHandle.Close()
line := ""
var cursor int64 = 0
stat, _ := fileHandle.Stat()
filesize := stat.Size()
for {
cursor -= 1
fileHandle.Seek(cursor, io.SeekEnd)
char := make([]byte, 1)
fileHandle.Read(char)
if cursor != -1 && (char[0] == 10 || char[0] == 13) { // stop if we find a line
break
}
line = fmt.Sprintf("%s%s", string(char), line) // there is more efficient way
if cursor == -filesize { // stop if we are at the begining
break
}
}
return line
}