小编典典

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

go

.eg 1. 日志文件

  • 开始
  • 1号线
  • 2号线
  • 3号线
  • 结束

从头开始读取文件时,我就能获得Line1的搜索位置。

func getSeekLocation() int64 {
    start := int64(0)
    input, err := os.Open(logFile)
    if err != nil {
        fmt.Println(err)
    }
    if _, err := input.Seek(start, io.SeekStart); err != nil {
        fmt.Println(err)
    }
    scanner := bufio.NewScanner(input)

    pos := start
    scanLines := func(data []byte, atEOF bool) (advance int, token []byte, 
    err error) {
        advance, token, err = bufio.ScanLines(data, atEOF)
        pos += int64(advance)
        return
    }
    scanner.Split(scanLines)
    for scanner.Scan() {
       if strings.Contains(scanner.Text(), "Line1") {
        break
       }
    }
    size, err := getFileSize()
    if err != nil {
        fmt.Println(err)
    }
    return size - pos
}

但这不是解决问题的有效方法,因为随着文件大小的增加,获取位置的时间也会增加。我想从EOF位置获取线的位置,我认为这样会更有效。


阅读 262

收藏
2020-07-02

共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: %s\n", pos, line)
    }
}

const src = `Start
Line1
Line2
Line3
End`

输出(在Go Playground上尝试):

Line start: 24, line: End
Line start: 18, line: Line3
Line start: 12, line: Line2
Line start:  6, line: Line1
Line start:  0, line: Start
Error: EOF

笔记:

  • 上面Scanner没有限制最大行长,它可以处理所有行。
  • 上面Scanner处理行尾\n\r\n行尾(由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: %s\n", pos, line)

    if i := strings.Index(line, what); i >= 0 {
        fmt.Printf("Found %q at line position: %d, global position: %d\n",
            what, i, pos+i)
        break
    }
}

输出(在Go Playground上尝试):

Line start: 24, line: End
Line start: 18, line: Line3
Line start: 12, line: Line2
Found "ine2" at line position: 1, global position: 13
2020-07-02