Голанг: Как мне определить количество строк в файле?
В Golang я ищу эффективный способ определения количества строк, которые имеет файл.
Конечно, я всегда могу пропустить весь файл, но не очень эффективен.
file, _ := os.Open("/path/to/filename")
fileScanner := bufio.NewScanner(file)
lineCount := 0
for fileScanner.Scan() {
lineCount++
}
fmt.Println("number of lines:", lineCount)
Есть ли лучший (более быстрый, дешевый) способ узнать, сколько строк имеет файл?
Ответы
Ответ 1
Здесь более быстрый счетчик строк, используя bytes.Count
, чтобы найти символы новой строки.
Это быстрее, потому что он убирает всю дополнительную логику и буферизацию, необходимые для возврата целых строк, и использует некоторые оптимизированные по сборке функции, предлагаемые пакетом байтов, для поиска символов в байтовом фрагменте.
Более крупные буферы также помогают здесь, особенно с большими файлами. В моей системе, с файлом, который я использовал для тестирования, 32k-буфер был самым быстрым.
func lineCounter(r io.Reader) (int, error) {
buf := make([]byte, 32*1024)
count := 0
lineSep := []byte{'\n'}
for {
c, err := r.Read(buf)
count += bytes.Count(buf[:c], lineSep)
switch {
case err == io.EOF:
return count, nil
case err != nil:
return count, err
}
}
}
и результат теста:
BenchmarkBuffioScan 500 6408963 ns/op 4208 B/op 2 allocs/op
BenchmarkBytesCount 500 4323397 ns/op 8200 B/op 1 allocs/op
BenchmarkBytes32k 500 3650818 ns/op 65545 B/op 1 allocs/op
Ответ 2
Самый эффективный способ, который я нашел, - это использование IndexByte байтового пакета, он как минимум в четыре раза быстрее, чем при использовании bytes.Count
и в зависимости от размера буфера он использует гораздо меньше памяти.
func lineCounter(r io.Reader) (int, error) {
var readSize int
var err error
var count int
buf := make([]byte, 1024)
for {
readSize, err = r.Read(buf)
if err != nil {
break
}
var buffPosition int
for {
i := bytes.IndexByte(buf[buffPosition:], '\n')
if i == -1 || readSize == buffPosition {
break
}
buffPosition += i + 1
count++
}
}
if readSize > 0 && count == 0 || count > 0 {
count++
}
if err == io.EOF {
return count, nil
}
return count, err
}
эталонный тест
BenchmarkIndexByteWithBuffer 2000000 653 ns/op 1024 B/op 1 allocs/op
BenchmarkBytes32k 500000 3189 ns/op 32768 B/op 1 allocs/op
Ответ 3
Нет никакого подхода, который значительно быстрее вашего, поскольку нет метаданных о том, сколько строк имеет файл. Вы можете немного ускорить поиск вручную символов новой строки:
func lineCount(r io.Reader) (int n, error err) {
buf := make([]byte, 8192)
for {
c, err := r.Read(buf)
if err != nil {
if err == io.EOF && c == 0 {
break
} else {
return
}
}
for _, b := range buf[:c] {
if b == '\n' {
n++
}
}
}
if err == io.EOF {
err = nil
}
}