首頁 > 後端開發 > Golang > 在 Go 中忽略長文字檔案中包含模式的行

在 Go 中忽略長文字檔案中包含模式的行

WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB
發布: 2024-02-13 13:57:19
轉載
1114 人瀏覽過

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

php小編蘋果在Go語言中,我們經常需要處理大型文字檔。有時,我們只對包含特定模式的行感興趣,而忽略其他行。幸運的是,在Go中,我們可以使用正規表示式和bufio.Scanner來實現這個目標。透過使用正規表示式來匹配行,並透過Scanner來逐行處理文件,我們可以輕鬆地過濾掉我們不感興趣的行。這個技巧不僅可以提高效率,還可以讓我們的程式碼更加簡潔和可讀。接下來,讓我們一起來看看如何在Go中實作忽略長文字檔案中包含模式的行。

問題內容

我正在嘗試實作一個函數來忽略 go 中長文字檔案(保證 ascii)中包含模式的行

我在withoutignorewithignore 下面的函數都接受檔名參數輸入並傳回*byte.buffer,隨後可用於寫入 io.writer

withignore 函數採用附加參數 pattern 從檔案中排除包含模式的行。該函數可以工作,但通過基準測試,發現它比 慢 5 倍而不忽略 。有什麼辦法可以改進嗎?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

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)

    }

}

登入後複製

基準測試

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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)

        }

    }

}

登入後複製

並且可以在命令列中使用“base64dump.log”產生

1

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

登入後複製

解決方法

由於ascii是有保證的,所以可以直接在byte層級工作。

不過,如果在讀取輸入時檢查每個位元組是否有換行符,然後再次在行內搜尋模式,則操作將應用於每個位元組。

另一方面,如果讀取輸入區塊並對文字中的模式執行最佳化搜索,甚至不檢查每個輸入字節,則可以最大限度地減少每個輸入位元組的操作。

例如,boyer-moore 字串搜尋演算法。 go內建的bytes.index函數也進行了最佳化。所達到的速度當然取決於輸入資料和實際模式。對於問題中指定的輸入,測量時`bytes.index 的性能顯著提高。

程式

  • 讀取一個區塊,其中區塊大小應明顯長於最大行長度,值 >= 64kb 可能應該不錯,在測試中使用了問題中的 1mb。
  • 一個區塊通常不會以換行符結束,因此從區塊的末尾搜尋到下一個換行符,將搜尋限制在這個切片並記住下一次傳遞的剩餘資料
  • 最後一個區塊不一定以換行符號結尾
  • 在高效能 go 函數 bytes.index 的幫助下,您可以找到該模式在區塊中出現的位置
  • 從找到的位置搜尋前面和後面的換行符號
  • 然後該區塊被輸出到對應的行開頭
  • 並且從出現模式的行尾繼續搜尋
  • 如果搜尋沒有找到其他位置,則輸出其餘位置
  • 讀取下一個區塊並再次應用所描述的步驟,直到到達檔案末端

值得注意

讀取操作傳回的資料可能少於區塊大小,因此重複讀取操作直到讀取區塊大小的資料是有意義的。

基準

優化後的程式碼通常要複雜得多,但效能也明顯更好,我們稍後會看到。

1

2

3

benchmarktestwithoutignore-8         270       4137267 ns/op

benchmarktestwithignore-8             54      22403931 ns/op

benchmarktestfilter-8                150       7947454 ns/op

登入後複製

這裡,優化後的程式碼benchmarktestfilter-8只比沒有過濾的操作慢1.9倍左右,而benchmarktestwithignore-8方法比沒有過濾的比較值慢5.4倍。

從另一個角度來看:優化後的程式碼比未優化的程式碼快2.8 倍

程式碼

當然,這是您自己測試的程式碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

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

}

登入後複製

基準部分可能看起來像這樣:

1

2

3

4

5

6

7

8

func BenchmarkTestFilter(b *testing.B) {

    for i := 0; i < b.N; i++ {

        _, err := filterFile("base64dump.log", "AUDIT")

        if err != nil {

            b.Fatal(err)

        }

    }

}

登入後複製

濾波器函數被拆分,實際工作在 func filter(reader io.reader,pattern []byte, chunksize int) (*bytes.buffer, error) 中完成。

透過注入讀取器和 chunksize,已經準備或考慮了單元測試的創建,這裡缺少這一點,但在處理索引時絕對建議這樣做。

但是,這裡的要點是找到一種方法來顯著提高效能。

以上是在 Go 中忽略長文字檔案中包含模式的行的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板