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

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

WBOY
WBOY轉載
2024-02-13 13:57:191012瀏覽

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

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

問題內容

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

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

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

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)
    }
}

基準測試

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”產生

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

解決方法

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

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

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

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

程式

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

值得注意

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

基準

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

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 倍

程式碼

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

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
}

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

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中文網其他相關文章!

陳述:
本文轉載於:stackoverflow.com。如有侵權,請聯絡admin@php.cn刪除