Как читать файл построчно в Go: пошаговое руководство
Лучший способ: bufio.Scanner
Для построчного чтения файлов в Go рекомендуется использовать пакет bufio и его тип Scanner. Это эффективный способ, который читает файл небольшими кусками (буферами) и не загружает весь файл в память. Scanner также корректно обрабатывает разные окончания строк (\n, \r\n).
Вот правильное решение вашей задачи:
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
// Открываем файл
file, err := os.Open("large_log.txt")
if err != nil {
log.Fatal(err)
}
// Важно: закрываем файл после завершения работы
defer file.Close()
// Создаём новый Scanner для файла
scanner := bufio.NewScanner(file)
// Опционально: увеличиваем размер буфера, если строки очень длинные (по умолчанию 64КБ)
// scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
// Счётчик строк
lineNum := 0
// Читаем строку за строкой
for scanner.Scan() {
lineNum++
line := scanner.Text()
fmt.Printf("Строка %d: %s\n", lineNum, line)
}
// Проверяем, не возникла ли ошибка при сканировании
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
Ключевые моменты:
- os.Open() — открывает файл для чтения. В отличие от
ioutil.ReadFile(), он не загружает содержимое в память. - defer file.Close() — гарантирует закрытие файла даже при возникновении ошибки.
- bufio.NewScanner() — создаёт сканер, который читает файл буферизованно.
- scanner.Scan() — возвращает
true, пока есть строки для чтения. - scanner.Text() — возвращает текущую строку без символа перевода строки.
- scanner.Err() — проверяет, не возникла ли ошибка во время чтения (например, если строка превышает размер буфера).
Обработка ошибок
Важно обрабатывать ошибки на каждом этапе. log.Fatal() выводит сообщение и завершает программу. В production-коде можно использовать log.Printf() или возвращать ошибку из функции.
Что делать с очень длинными строками?
По умолчанию Scanner имеет буфер 64 КБ. Если ваши строки длиннее, вы получите ошибку bufio.Scanner: token too long. В этом случае увеличьте буфер:
scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024) // 1 МБ
Альтернатива: bufio.Reader
Если вам нужен более низкоуровневый контроль, можно использовать bufio.Reader:
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break // Конец файла
}
log.Fatal(err)
}
fmt.Print(line) // line включает символ '\n'
}
Но Scanner проще и чище для большинства задач.
Заключение
Используйте bufio.Scanner для построчного чтения файлов в Go. Это эффективно, безопасно по памяти и легко в использовании. Не забудьте обрабатывать ошибки и закрывать файлы!
Дополнительный подход: чтение с помощью ioutil.ReadFile и strings.Split (для маленьких файлов)
Если вы уверены, что файл маленький (до нескольких мегабайт), и производительность не критична, можно использовать ваш первоначальный подход, но с небольшой доработкой для совместимости с разными окончаниями строк:
package main
import (
"fmt"
"io/ioutil"
"log"
"strings"
)
func main() {
data, err := ioutil.ReadFile("small_config.txt")
if err != nil {
log.Fatal(err)
}
// Нормализуем окончания строк: заменяем \r\n на \n
content := strings.ReplaceAll(string(data), "\r\n", "\n")
// Удаляем возможный завершающий \r
content = strings.ReplaceAll(content, "\r", "")
lines := strings.Split(content, "\n")
for i, line := range lines {
fmt.Printf("Строка %d: %s\n", i+1, line)
}
}
Когда использовать этот метод?
- Файл гарантированно маленький (конфиги, списки, настройки).
- Вам нужно быстро прочитать файл целиком для дальнейшей обработки.
- Вы не работаете с потоками данных (например, сетевыми).
Но помните: для больших файлов или production-кода всегда используйте bufio.Scanner — это стандартный идиоматичный способ в Go.