Как читать и записывать файлы в Go: полное руководство для начинающих
Основные подходы к чтению и записи файлов в Go
В Go есть несколько способов работы с файлами, и выбор зависит от конкретной задачи. Рассмотрим три основных подхода: для маленьких файлов, для больших файлов и для построчной обработки.
1. Чтение и запись целиком (для небольших файлов)
Самый простой способ — использовать пакет os для чтения и записи всего файла сразу. Подходит для файлов размером до нескольких мегабайт.
package main
import (
"fmt"
"os"
)
func main() {
// Чтение всего файла
data, err := os.ReadFile("input.txt")
if err != nil {
fmt.Printf("Ошибка чтения файла: %v\n", err)
return
}
// Обработка данных
result := "Processed: " + string(data)
// Запись в файл
err = os.WriteFile("output.txt", []byte(result), 0644)
if err != nil {
fmt.Printf("Ошибка записи файла: %v\n", err)
return
}
fmt.Println("Файл успешно обработан!")
}
Важно: os.ReadFile загружает весь файл в память. Для больших файлов это может привести к проблемам с памятью.
2. Построчное чтение и запись (для больших файлов)
Для обработки больших файлов используйте bufio.Scanner для чтения и bufio.Writer для записи. Это позволяет обрабатывать файл построчно, не загружая его целиком в память.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// Открытие файла для чтения
inputFile, err := os.Open("large_input.txt")
if err != nil {
fmt.Printf("Ошибка открытия файла: %v\n", err)
return
}
defer inputFile.Close() // Важно: закрываем файл при завершении функции
// Создание файла для записи
outputFile, err := os.Create("large_output.txt")
if err != nil {
fmt.Printf("Ошибка создания файла: %v\n", err)
return
}
defer outputFile.Close()
// Создаем сканер для чтения и writer для записи
scanner := bufio.NewScanner(inputFile)
writer := bufio.NewWriter(outputFile)
defer writer.Flush() // Важно: сохраняем все данные из буфера в файл
lineNumber := 0
for scanner.Scan() {
line := scanner.Text()
lineNumber++
// Обработка строки
processedLine := fmt.Sprintf("Строка %d: %s\n", lineNumber, line)
// Запись обработанной строки
_, err := writer.WriteString(processedLine)
if err != nil {
fmt.Printf("Ошибка записи строки: %v\n", err)
return
}
}
// Проверка ошибок сканера
if err := scanner.Err(); err != nil {
fmt.Printf("Ошибка чтения файла: %v\n", err)
return
}
fmt.Println("Большой файл успешно обработан!")
}
3. Использование io.Copy для копирования данных
Если нужно просто скопировать данные из одного файла в другой или из сети в файл, используйте io.Copy:
package main
import (
"io"
"os"
)
func main() {
srcFile, err := os.Open("source.txt")
if err != nil {
panic(err)
}
defer srcFile.Close()
dstFile, err := os.Create("destination.txt")
if err != nil {
panic(err)
}
defer dstFile.Close()
// Копируем данные из source в destination
bytesCopied, err := io.Copy(dstFile, srcFile)
if err != nil {
panic(err)
}
println("Скопировано байт:", bytesCopied)
}
Важные правила работы с файлами в Go
- Всегда проверяйте ошибки — каждая операция с файлом может завершиться ошибкой
- Используйте defer для закрытия файлов — это гарантирует, что файл закроется даже при возникновении ошибки
- Не забывайте про Flush() для bufio.Writer — данные сначала накапливаются в буфере и только потом записываются в файл
- Выбирайте правильный подход — для маленьких файлов используйте os.ReadFile/WriteFile, для больших — bufio.Scanner/Writer
- Права доступа — при создании файла указывайте права (обычно 0644 для обычных файлов)
Теперь вы можете вернуться к вашему коду и переписать его, используя один из описанных подходов. Для начала рекомендую первый способ с os.ReadFile и os.WriteFile, а когда освоитесь — переходите к работе с большими файлами через bufio.
Альтернативный подход: Использование ioutil (устаревший, но часто встречается в старых примерах)
В более старых версиях Go (до 1.16) для работы с файлами использовался пакет ioutil. Хотя сейчас он считается устаревшим, вы всё ещё можете встретить его в legacy-коде:
package main
import (
"fmt"
"io/ioutil"
"log"
)
func main() {
// Чтение файла (устаревший способ)
data, err := ioutil.ReadFile("input.txt")
if err != nil {
log.Fatal(err)
}
// Обработка
result := "Обработано: " + string(data)
// Запись (устаревший способ)
err = ioutil.WriteFile("output.txt", []byte(result), 0644)
if err != nil {
log.Fatal(err)
}
fmt.Println("Готово!")
}
Почему это устарело? Начиная с Go 1.16, функции ioutil.ReadFile и ioutil.WriteFile были перенесены напрямую в пакет os. Теперь рекомендуется использовать os.ReadFile и os.WriteFile — они делают то же самое, но не требуют дополнительного импорта.
Дополнительный совет: Обработка ошибок через log.Fatal
В простых скриптах или утилитах можно использовать log.Fatal(err) вместо ручной обработки ошибок — это автоматически выведет сообщение об ошибке и завершит программу с кодом 1. Для production-кода лучше использовать более детальную обработку ошибок.