Как читать файл построчно в Go: пошаговое руководство
Правильный способ: использование bufio.Scanner
В Go для эффективного построчного чтения файлов используется пакет bufio и его тип Scanner. Это самый идиоматичный и производительный способ, который не загружает весь файл в память, а читает его порциями по мере необходимости.
Базовый пример построчного чтения:
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
Как это работает
- Открытие файла —
os.Open()возвращает дескриптор файла. Важно сразу закрыть его черезdefer file.Close(). - Создание сканера —
bufio.NewScanner(file)создаёт сканер, который по умолчанию разбивает данные на строки (разделитель — символ новой строки\n). - Цикл чтения —
scanner.Scan()возвращаетtrue, пока есть следующая строка. Когда строки заканчиваются или происходит ошибка, возвращаетсяfalse. - Получение строки —
scanner.Text()возвращает текущую строку без символа новой строки. - Проверка ошибок — после цикла обязательно проверьте
scanner.Err(), чтобы убедиться, что чтение завершилось без ошибок (например, из-за повреждения файла).
Расширенные примеры
Фильтрация строк (пропуск пустых строк и комментариев):
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
)
func main() {
file, err := os.Open("config.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNumber := 0
for scanner.Scan() {
lineNumber++
line := strings.TrimSpace(scanner.Text())
// Пропускаем пустые строки и комментарии (начинающиеся с # или //)
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
continue
}
// Обрабатываем значимую строку
fmt.Printf("Строка %d: %s\n", lineNumber, line)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
Чтение больших файлов с кастомным буфером:
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("large_file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
// Увеличиваем размер буфера до 1 МБ (по умолчанию 64 КБ)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for scanner.Scan() {
line := scanner.Text()
// Обработка строки
_ = line
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
Обработка ошибок и специальные случаи:
- Если строка длиннее буфера,
Scannerвернёт ошибкуErrTooLong. Увеличьте буфер черезscanner.Buffer(). - Для Windows-файлов с разделителями
\r\nсканер автоматически удаляет\r. - Если вам нужно читать строки с кастомным разделителем, используйте
scanner.Split(bufio.ScanLines)или напишите свою функцию разделения.
Альтернативы (когда Scanner не подходит)
- bufio.Reader — если вам нужен более низкоуровневый контроль, используйте
bufio.NewReader(file).ReadString('\n')илиReadBytes('\n'). - ioutil.ReadFile — только для маленьких файлов, которые гарантированно помещаются в память.
В 99% случаев bufio.Scanner — это правильный выбор. Он прост, эффективен и обрабатывает все краевые случаи.
Альтернативный подход: использование bufio.Reader
Если вам нужен более гибкий контроль над процессом чтения (например, чтение большими блоками или работа с произвольными разделителями), можно использовать bufio.Reader.
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
// Если достигнут конец файла и есть прочитанные данные
if err.Error() == "EOF" && len(line) > 0 {
fmt.Print(line)
break
}
if err.Error() == "EOF" {
break
}
log.Fatal(err)
}
// Удаляем символ новой строки
line = line[:len(line)-1]
fmt.Println(line)
}
}
Когда использовать bufio.Reader вместо Scanner:
- Когда нужно читать строки с произвольным разделителем (не только
\n). - Когда требуется обрабатывать очень длинные строки (более 64 КБ по умолчанию).
- Когда нужно комбинировать построчное чтение с другими операциями ввода-вывода (например, чтением бинарных данных).
Однако для большинства задач bufio.Scanner удобнее — он автоматически обрабатывает конец файла и ошибки, а код получается чище.