如何使用Go每10秒从一个大文件中读取最后一行

时间:2013-07-25 16:36:46

标签: go

如何从大日志文件中读取最后两行而不将其完全加载到内存中?

我需要每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

谢谢!

4 个答案:

答案 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
}