21

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

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

5 回答 5

17

您可以使用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)
    }

}
于 2013-07-25T16:52:28.710 回答
11

有些人会来这个页面寻找有效地读取日志文件的最后一行(如 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
}
于 2018-07-13T15:16:17.587 回答
3

我认为两者的结合File.Seek(0, 2)应该File.Read()起作用。

Seek调用将您带到文件的末尾。您可以Seek在 EOF 之前的某个位置获取最后几行。然后你Read直到 EOF 并在你的 goroutine 中睡 10 秒;接下来Read有机会为您获取更多数据。

您可以从GNUtail的源代码中获取这个想法(以及最初显示最后几行的回扫逻辑)。

于 2013-07-25T16:53:32.497 回答
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)

}
于 2013-07-25T17:35:52.547 回答
0

这是我为以反向行顺序读取大字节而编写的代码。它不会在尾随空格处中断。

这段代码做的是反向循环字节,它计算它遇到的字节数。当它解除换行符时,它会按该数字循环返回以将行写入append()结果中[]byte,然后重置该数字。它这样做直到maxLine满足变量。

这过于复杂,如果您只想从特定行读取字节,可能有更好的方法。为了便于阅读,人们一直渴望变量名。

func ReverseByte(fileByte []byte, maxLine int) []byte {
    // This is a byte "code" for NewLine or "\n"
    nl := byte(10)

    var reverseFileByte []byte
    var lineLen, lineWritten int

    byteIndex := len(fileByte) - 1
    for lineWritten < maxLine {
        if fileByte[byteIndex] == nl {
            currentLine := make([]byte, lineLen)
            byteLineIndex := byteIndex
            var currentLineIndex int
            for currentLineIndex < lineLen {
                currentLine[currentLineIndex] = fileByte[byteLineIndex]
                byteLineIndex++
                currentLineIndex++
            }
            reverseFileByte = append(reverseFileByte, currentLine...)
            lineLen = 0
            lineWritten++
        }
        lineLen++
        byteIndex--
    }
    return reverseFileByte
}

https://go.dev/play/p/qKDFxiJQAfF

于 2022-01-06T18:48:46.117 回答