Heim  >  Artikel  >  Backend-Entwicklung  >  Ignorieren Sie Zeilen mit Mustern in der Langtextdatei in Go

Ignorieren Sie Zeilen mit Mustern in der Langtextdatei in Go

WBOY
WBOYnach vorne
2024-02-13 13:57:19986Durchsuche

在 Go 中忽略长文本文件中包含模式的行

php-Editor Apple In der Go-Sprache müssen wir oft große Textdateien verarbeiten. Manchmal sind wir nur an Zeilen interessiert, die ein bestimmtes Muster enthalten, und ignorieren andere Zeilen. Glücklicherweise können wir in Go reguläre Ausdrücke und bufio.Scanner verwenden, um dieses Ziel zu erreichen. Indem wir reguläre Ausdrücke zum Abgleichen von Zeilen verwenden und die Datei Zeile für Zeile durch einen Scanner laufen lassen, können wir Zeilen, die uns nicht interessieren, ganz einfach herausfiltern. Dieser Tipp verbessert nicht nur die Effizienz, sondern macht unseren Code auch prägnanter und lesbarer. Schauen wir uns als Nächstes an, wie man Zeilen mit Mustern in langen Textdateien in Go ignoriert.

Frageninhalt

Ich versuche, eine Funktion zu implementieren, um Zeilen mit Mustern in langen Textdateien (garantiert ASCII) in go zu ignorieren

Ich bin withoutignorewithignore 下面的函数都接受文件名参数输入并返回 *byte.buffer,随后可用于写入 io.writer.

withignore 函数采用附加参数 pattern 从文件中排除包含模式的行。该函数可以工作,但通过基准测试,发现它比 慢 5 倍而不忽略 . Gibt es eine Möglichkeit, es zu verbessern?

package main

import (
    "bufio"
    "bytes"
    "io"
    "log"
    "os"
)

func withoutignore(f string) (*bytes.buffer, error) {
    rfd, err := os.open(f)
    if err != nil {
        log.fatal(err)
    }

    defer func() {
        if err := rfd.close(); err != nil {
            log.fatal(err)
        }
    }()

    inputbuffer := make([]byte, 1048576)
    var bytesread int

    var bs []byte
    opbuffer := bytes.newbuffer(bs)

    for {
        bytesread, err = rfd.read(inputbuffer)

        if err == io.eof {
            return opbuffer, nil
        }

        if err != nil {
            return nil, nil
        }

        _, err = opbuffer.write(inputbuffer[:bytesread])
        if err != nil {
            return nil, err
        }
    }
    return opbuffer, nil
}

func withignore(f, pattern string) (*bytes.buffer, error) {
    rfd, err := os.open(f)
    if err != nil {
        log.fatal(err)
    }

    defer func() {
        if err := rfd.close(); err != nil {
            log.fatal(err)
        }
    }()

    scanner := bufio.newscanner(rfd)
    var bs []byte
    buffer := bytes.newbuffer(bs)
    for scanner.scan() {
        if !bytes.contains(scanner.bytes(), []byte(pattern)) {
            _, err := buffer.writestring(scanner.text() + "\n")
            if err != nil {
                return nil, nil
            }
        }
    }

    return buffer, nil
}

func main() {
    // buff, err := withoutignore("base64dump.log")
    buff, err := withignore("base64dump.log", "audit")
    if err != nil {
        log.fatal(err)
    }

    _, err = buff.writeto(os.stdout)
    if err != nil {
        log.fatal(err)
    }
}

Benchmarks

package main

import "testing"

func benchmarktestwithoutignore(b *testing.b) {
    for i := 0; i < b.n; i++ {
        _, err := withoutignore("base64dump.log")
        if err != nil {
            b.fatal(err)
        }
    }
}

func benchmarktestwithignore(b *testing.b) {
    for i := 0; i < b.n; i++ {
        _, err := withignore("base64dump.log", "audit")
        if err != nil {
            b.fatal(err)
        }
    }
}

Und kann über die Befehlszeile verwendet werden“base64dump.log”生成

base64 /dev/urandom | head -c 10000000 > base64dump.log

Lösung

Da ASCII garantiert ist, kann es direkt auf der byte-Ebene funktionieren.

Wenn Sie jedoch beim Lesen der Eingabe jedes Byte auf ein Zeilenumbruchzeichen überprüfen und dann innerhalb der Zeile erneut nach dem Muster suchen, wird die Operation auf jedes Byte angewendet.

Wenn Sie andererseits einen Eingabeblock lesen und eine optimierte Suche nach Mustern im Text durchführen, ohne jedes Eingabebyte zu überprüfen, können Sie die Operationen pro Eingabebyte minimieren.

Zum Beispiel der Boyer-Moore-String-Suchalgorithmus. Auch die integrierte bytes.index-Funktion von Go wurde optimiert. Die erreichte Geschwindigkeit hängt natürlich von den Eingabedaten und dem tatsächlichen Modus ab. Für die in der Frage angegebene Eingabe verbessert sich die Leistung von „bytes.index“ bei Messung erheblich.

Programm

  • Beim Lesen eines Blocks, bei dem die Blockgröße deutlich größer als die maximale Zeilenlänge sein sollte, sollte ein Wert >= 64 KB wahrscheinlich gut sein. Beim Testen habe ich die 1 MB in der Frage verwendet.
  • Ein Block endet normalerweise nicht mit einer neuen Zeile. Suchen Sie also vom Ende des Blocks bis zur nächsten neuen Zeile, beschränken Sie die Suche auf diesen Abschnitt und merken Sie sich die verbleibenden Daten für den nächsten Durchgang.
  • Der letzte Block endet nicht unbedingt mit einem Zeilenumbruchzeichen
  • Mit Hilfe leistungsstarker Go-Funktionen bytes.index können Sie herausfinden, wo im Block das Muster auftritt
  • Suchen Sie ab der gefundenen Position nach vorangestellten und folgenden Zeilenumbrüchen
  • Der Block wird dann am Anfang der entsprechenden Zeile ausgegeben
  • Und setzen Sie die Suche ab dem Ende der Zeile fort, in der das Muster erscheint
  • Wenn die Suche keine anderen Standorte findet, geben Sie die verbleibenden Standorte aus
  • Lesen Sie den nächsten Abschnitt und wenden Sie die beschriebenen Schritte erneut an, bis Sie das Ende der Datei erreicht haben

Bemerkenswert

Ein Lesevorgang kann weniger Daten als die Blockgröße zurückgeben, daher ist es sinnvoll, den Lesevorgang zu wiederholen, bis die Blockgröße der Daten gelesen ist.

Benchmark

Optimierter Code ist in der Regel deutlich komplexer, performt aber auch deutlich besser, wie wir später sehen werden.

benchmarktestwithoutignore-8         270       4137267 ns/op
benchmarktestwithignore-8             54      22403931 ns/op
benchmarktestfilter-8                150       7947454 ns/op

Hier ist die optimierte Code-benchmarktestfilter-8只比没有过滤的操作慢1.9倍左右,而benchmarktestwithignore-8Methode 5,4-mal langsamer als der ungefilterte Vergleichswert.

Betrachten Sie es aus einer anderen Perspektive: Der optimierte Code ist 2,8-mal schneller als der nicht optimierte Code.

Code

Natürlich ist hier der Code für Ihre eigenen Tests:

func filterfile(f, pattern string) (*bytes.buffer, error) {
    rfd, err := os.open(f)
    if err != nil {
        log.fatal(err)
    }
    defer func() {
        if err := rfd.close(); err != nil {
            log.fatal(err)
        }
    }()

    reader := bufio.newreader(rfd)
    return filter(reader, []byte(pattern), 1024*1024)
}

// chunksize must be larger than the longest line
// a reasonable size is probably >= 64k
func filter(reader io.reader, pattern []byte, chunksize int) (*bytes.buffer, error) {
    var bs []byte
    buffer := bytes.newbuffer(bs)

    chunk := make([]byte, chunksize)

    var remaining []byte
    for lastchunk := false; !lastchunk; {
        n, err := readchunk(reader, chunk, remaining, chunksize)
        if err != nil {
            if err == io.eof {
                lastchunk = true
            } else {
                return nil, err
            }
        }

        remaining = remaining[:0]
        if !lastchunk {
            for i := n - 1; i > 0; i-- {
                if chunk[i] == '\n' {
                    remaining = append(remaining, chunk[i+1:n]...)
                    n = i + 1
                    break
                }
            }
        }

        s := 0
        for s < n {
            hit := bytes.index(chunk[s:n], pattern)
            if hit < 0 {
                break
            }
            hit += s
            startofline := hit
            for ; startofline > 0; startofline-- {
                if chunk[startofline] == '\n' {
                    startofline++
                    break
                }
            }
            endofline := hit + len(pattern)
            for ; endofline < n; endofline++ {
                if chunk[endofline] == '\n' {
                    break
                }
            }
            endofline++

            _, err = buffer.write(chunk[s:startofline])
            if err != nil {
                return nil, err
            }
            s = endofline
        }

        if s < n {
            _, err = buffer.write(chunk[s:n])
            if err != nil {
                return nil, err
            }
        }
    }

    return buffer, nil
}

func readchunk(reader io.reader, chunk, remaining []byte, chunksize int) (int, error) {
    copy(chunk, remaining)
    r := len(remaining)
    for r < chunksize {
        n, err := reader.read(chunk[r:])
        r += n
        if err != nil {
            return r, err
        }
    }
    return r, nil
}

Der Benchmark-Bereich könnte so aussehen:

func BenchmarkTestFilter(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _, err := filterFile("base64dump.log", "AUDIT")
        if err != nil {
            b.Fatal(err)
        }
    }
}

Die Filterfunktion ist aufgeteilt und die eigentliche Arbeit wird in func filter(reader io.reader,pattern []byte, chunksize int) (*bytes.buffer, error) erledigt.

Die Erstellung von Unit-Tests wurde durch das Einfügen von Readern und Chunksize vorbereitet oder in Betracht gezogen. Dies fehlt hier, ist aber beim Umgang mit Indizes unbedingt zu empfehlen.

Allerdings geht es hier darum, einen Weg zu finden, die Leistung deutlich zu verbessern.

Das obige ist der detaillierte Inhalt vonIgnorieren Sie Zeilen mit Mustern in der Langtextdatei in Go. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen