在Go语言中查找行数的最快方法是什么?

7

我现在正在使用的:

numlines := strings.Count(editor.Text(), "\n")
fmt.Print(strconv.Itoa(numlines))
message.SetText(strconv.Itoa(numlines))

每当文本框被更新时,此代码将运行。如何以最符合Go语言规范的方式实现此功能?

2个回答

6

没问题。但不要忘记,如果最后一个字符不是换行符,则必须将出现次数加1,因为这将是行数的数量(最后一行可能没有换行符)。

我们可能认为,由于您正在计算的子字符串只是单个字符(单个rune),因此我们可以创建一个自定义解决方案,仅计算此单个字符的出现次数(而不是计算子字符串)。它可能看起来像这样:

func countRune(s string, r rune) int {
    count := 0
    for _, c := range s {
        if c == r {
            count++
        }
    }
    return count
}

对于字符串值的for range循环遍历其rune

并且可以通过测试来验证它(在Go Playground中尝试):

fmt.Println(countRune("asdf\nasdf\nasdf\n", '\n')) // Prints 3

实际上,这并不比计算换行符更快,因为在UTF-8编码中,一个换行符只占用一个字节。而strings.Count()已经针对长度为1的子字符串进行了优化,因此计算子字符串的数量并不会更慢:

// Count counts the number of non-overlapping instances of substr in s.
// If substr is an empty string, Count returns 1 + the number of Unicode code points in s.
func Count(s, substr string) int {
    if len(substr) == 1 && cpu.X86.HasPOPCNT {
        return countByte(s, byte(substr[0]))
    }
    return countGeneric(s, substr)
}

func countByte(s string, c byte) int // ../runtime/asm_amd64.s

如果您可以访问编辑器的“内部”字节或符文数组,而无需调用其方法创建并返回内容的副本,则可以提高此操作(计算行数)的性能。

4

当你询问最快的方法时,你应该使用 Go testing 包的基准测试设施来进行测量。

例如,使用 strings.Count 计算 lorem ipsum 中的行数,使用 for range 循环遍历 string,并测量从 byte 切片转换为 string 的任何额外成本。你可以通过计算 byte 切片中的行数来避免任何 byte 切片到 string 的开销。

$ gotest lines_test.go -bench=.
data: /home/peter/shakespeare.pg100.txt 5589889
BenchmarkStringCount-4     30000000    57.3 ns/op     0 B/op   0 allocs/op
BenchmarkStringByRune-4     3000000   563 ns/op       0 B/op   0 allocs/op
BenchmarkBytesToString-4   10000000   173 ns/op     480 B/op   1 allocs/op
BenchmarkBytesCount-4      20000000    61.2 ns/op     0 B/op   0 allocs/op

lines_test.go:

package main

import (
    "bytes"
    "strconv"
    "strings"
    "testing"
)

func linesStringCount(s string) string {
    n := strings.Count(s, "\n")
    if len(s) > 0 && !strings.HasSuffix(s, "\n") {
        n++
    }
    return strconv.Itoa(n)
}

func linesStringByRune(s string) string {
    n := 0
    for _, r := range s {
        if r == '\n' {
            n++
        }
    }
    if len(s) > 0 && !strings.HasSuffix(s, "\n") {
        n++
    }
    return strconv.Itoa(n)
}

func linesBytesCount(s []byte) string {
    nl := []byte{'\n'}
    n := bytes.Count(s, nl)
    if len(s) > 0 && !bytes.HasSuffix(s, nl) {
        n++
    }
    return strconv.Itoa(n)
}

var data = []byte(`Lorem ipsum dolor sit amet, consectetur adipiscing elit, 
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 
Ut enim ad minim veniam, 
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 
Excepteur sint occaecat cupidatat non proident, 
sunt in culpa qui officia deserunt mollit anim id est laborum.`)

func BenchmarkStringCount(b *testing.B) {
    text := string(data)
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = linesStringCount(text)
    }
}

func BenchmarkStringByRune(b *testing.B) {
    text := string(data)
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = linesStringByRune(text)
    }
}

func BenchmarkBytesToText(b *testing.B) {
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = string(data)
    }
}

func BenchmarkBytesCount(b *testing.B) {
    text := data
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = linesBytesCount(text)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接