从末尾读取日志文件并获取特定字符串的偏移量

从末尾读取日志文件并获取特定字符串的偏移量,第1张

从末尾读取日志文件并获取特定字符串的偏移量

注意:
我优化并改进了以下解决方案,并将其作为库发布在这里:

github.com/icza/backscanner


bufio.Scanner
使用a
io.Reader
作为其源,它不支持从任意位置进行查找和/或读取,因此它无法从头开始扫描线。
bufio.Scanner
只有在读取了输入之前的所有数据之后,它才能读取输入的任何部分(也就是说,如果先读取所有文件内容,则只能读取文件的结尾)。

因此,我们需要定制的解决方案来实现这种功能。幸运的

os.File
是,它在实现
io.Seeker
和时都支持从任意位置读取
io.ReaderAt
(它们中的任何一个都足以满足我们的需要)。

扫描仪返回从末尾开始向后的行

让我们构造一个从最后一行开始
向后

Scanner
扫描行的。为此,我们将使用。下面的实现使用一个内部缓冲区,从输入的末尾开始,按块将数据读取到该缓冲区中。输入的大小也必须传递(这基本上是我们要从其开始读取的位置,不一定必须是结束位置)。
__
io.ReaderAt

type Scanner struct {    r   io.ReaderAt    pos int    err error    buf []byte}func NewScanner(r io.ReaderAt, pos int) *Scanner {    return &Scanner{r: r, pos: pos}}func (s *Scanner) readMore() {    if s.pos == 0 {        s.err = io.EOF        return    }    size := 1024    if size > s.pos {        size = s.pos    }    s.pos -= size    buf2 := make([]byte, size, size+len(s.buf))    // ReadAt attempts to read full buff!    _, s.err = s.r.ReadAt(buf2, int64(s.pos))    if s.err == nil {        s.buf = append(buf2, s.buf...)    }}func (s *Scanner) Line() (line string, start int, err error) {    if s.err != nil {        return "", 0, s.err    }    for {        lineStart := bytes.LastIndexByte(s.buf, 'n')        if lineStart >= 0 { // We have a complete line: var line string line, s.buf = string(dropCR(s.buf[lineStart+1:])), s.buf[:lineStart] return line, s.pos + lineStart + 1, nil        }        // Need more data:        s.readMore()        if s.err != nil { if s.err == io.EOF {     if len(s.buf) > 0 {         return string(dropCR(s.buf)), 0, nil     } } return "", 0, s.err        }    }}// dropCR drops a terminal r from the data.func dropCR(data []byte) []byte {    if len(data) > 0 && data[len(data)-1] == 'r' {        return data[0 : len(data)-1]    }    return data}

使用它的示例:

func main() {    scanner := NewScanner(strings.NewReader(src), len(src))    for {        line, pos, err := scanner.Line()        if err != nil { fmt.Println("Error:", err) break        }        fmt.Printf("Line start: %2d, line: %sn", pos, line)    }}const src = `StartLine1Line2Line3End`

输出(在Go Playground上尝试):

Line start: 24, line: EndLine start: 18, line: Line3Line start: 12, line: Line2Line start:  6, line: Line1Line start:  0, line: StartError: EOF

笔记:

  • 上面
    Scanner
    没有限制最大行长,它可以处理所有行。
  • 上面
    Scanner
    处理行尾
    n
    rn
    行尾(由
    dropCR()
    函数确保)。
  • 您可以传递任何起始位置,而不仅仅是大小/长度,并且清单行将从此处开始(继续)。
  • 上面
    Scanner
    没有重用缓冲区,总是在需要时创建新的缓冲区。(预)分配2个缓冲区并明智地使用它们就足够了。实现将变得更加复杂,并且将引入最大行长度限制。
与文件一起使用

要将其

Scanner
与文件一起使用,您可以使用
os.Open()
打开文件。注意
*File
实现
io.ReaderAt()
。然后,您可以
File.Stat()
用来获取有关文件(
os.FileInfo
)的信息,包括文件的大小(长度):

f, err := os.Open("a.txt")if err != nil {    panic(err)}fi, err := f.Stat()if err != nil {    panic(err)}defer f.Close()scanner := NewScanner(f, int(fi.Size()))
在一行中寻找子字符串

如果要在一行中查找子字符串,则只需使用上面的代码即可

Scanner
返回每行的起始位置,并从末尾读取行。

您可以使用来检查每行中的子字符串

strings.Index()
,它返回该行内的子字符串位置,如果找到,则将行起始位置添加到该位置。

假设我们正在寻找

"ine2"
子字符串(这是该
"Line2"
行的一部分)。这是您可以执行的 *** 作:

scanner := NewScanner(strings.NewReader(src), len(src))what := "ine2"for {    line, pos, err := scanner.Line()    if err != nil {        fmt.Println("Error:", err)        break    }    fmt.Printf("Line start: %2d, line: %sn", pos, line)    if i := strings.Index(line, what); i >= 0 {        fmt.Printf("Found %q at line position: %d, global position: %dn", what, i, pos+i)        break    }}

输出(在Go Playground上尝试):

Line start: 24, line: EndLine start: 18, line: Line3Line start: 12, line: Line2Found "ine2" at line position: 1, global position: 13


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5125985.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-17
下一篇 2022-11-17

发表评论

登录后才能评论

评论列表(0条)

保存