Maison > Article > développement back-end > Ignorer les lignes contenant un motif dans un fichier texte long dans Go
php Editor Apple Dans le langage Go, nous avons souvent besoin de traiter des fichiers texte volumineux. Parfois, nous ne nous intéressons qu'aux lignes contenant un modèle spécifique et ignorons les autres lignes. Heureusement, dans Go, nous pouvons utiliser des expressions régulières et bufio.Scanner pour atteindre cet objectif. En utilisant des expressions régulières pour faire correspondre les lignes et en exécutant le fichier via un scanner ligne par ligne, nous pouvons facilement filtrer les lignes qui ne nous intéressent pas. Cette astuce améliore non seulement l'efficacité, mais rend également notre code plus concis et plus lisible. Voyons ensuite comment ignorer les lignes contenant des motifs dans les fichiers texte longs dans Go.
J'essaie d'implémenter une fonction pour ignorer les lignes contenant des motifs dans des fichiers texte longs (ascii garanti) en go
Je le suis withoutignore
和 withignore
下面的函数都接受文件名参数输入并返回 *byte.buffer
,随后可用于写入 io.writer
.
withignore
函数采用附加参数 pattern
从文件中排除包含模式的行。该函数可以工作,但通过基准测试,发现它比 慢 5 倍而不忽略
. Y a-t-il un moyen de l'améliorer ?
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) } } }
Et peut être utilisé depuis la ligne de commande“base64dump.log”生成
base64 /dev/urandom | head -c 10000000 > base64dump.log
Puisque ascii est garanti, il peut fonctionner directement au niveau byte
.
Cependant, si vous vérifiez chaque octet pour un caractère de nouvelle ligne lors de la lecture de l'entrée, puis recherchez à nouveau le modèle dans la ligne, l'opération sera appliquée à chaque octet.
D'un autre côté, si vous lisez un bloc d'entrée et effectuez une recherche optimisée de modèles dans le texte, sans même vérifier chaque octet d'entrée, vous pouvez minimiser les opérations par octet d'entrée.
Par exemple, l'algorithme de recherche de chaîne Boyer-Moore. La fonction bytes.index
intégrée de Go a également été optimisée. La vitesse atteinte dépendra bien entendu des données d'entrée et du mode réel. Pour l'entrée spécifiée dans la question, les performances de `bytes.index s'améliorent considérablement lorsqu'elles sont mesurées.
Programme
bytes.index
, vous pouvez trouver où dans le bloc le motif se produit Remarquable
Une opération de lecture peut renvoyer moins de données que la taille du bloc, il est donc logique de répéter l'opération de lecture jusqu'à ce que la taille du bloc de données soit lue.
Benchmark
Le code optimisé est généralement beaucoup plus complexe, mais il est également nettement plus performant, comme nous le verrons plus tard.
benchmarktestwithoutignore-8 270 4137267 ns/op benchmarktestwithignore-8 54 22403931 ns/op benchmarktestfilter-8 150 7947454 ns/op
Ici, la méthode de code optimisé benchmarktestfilter-8
只比没有过滤的操作慢1.9倍左右,而benchmarktestwithignore-8
est 5,4 fois plus lente que la valeur de comparaison non filtrée.
Regardez-le sous un autre angle : le code optimisé est 2,8 fois plus rapide que le code non optimisé.
code
Bien sûr, voici le code pour vos propres 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 }
La section benchmark pourrait ressembler à ceci :
func BenchmarkTestFilter(b *testing.B) { for i := 0; i < b.N; i++ { _, err := filterFile("base64dump.log", "AUDIT") if err != nil { b.Fatal(err) } } }
La fonction de filtre est divisée et le travail réel est effectué en func filter(reader io.reader,pattern []byte, chunksize int) (*bytes.buffer, error)
.
La création de tests unitaires a été préparée ou prise en compte en injectant le reader et le chunksize, qui manquent ici mais sont définitivement recommandés lorsqu'il s'agit d'index.
Cependant, le but ici est de trouver un moyen d’améliorer considérablement les performances.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!