Avatar ·

Как читать и записывать файлы в Go: полное руководство для начинающих

Всем привет! Я только начал изучать Go и столкнулся с проблемой при работе с файлами. Мне нужно написать простую программу, которая читает данные из одного текстового файла, обрабатывает их (например, переводит в верхний регистр) и записывает результат в другой файл. Я перепробовал несколько подходов, но постоянно получаю ошибки. Вот мой текущий код: ```go package main import ( "fmt" "os" ) func main() { // Пытаюсь прочитать файл data, err := os.Read("input.txt") if err != nil { fmt.Println("Ошибка чтения:", err) return } // Преобразую в строку и пытаюсь записать err = os.Write("output.txt", data) if err != nil { fmt.Println("Ошибка записи:", err) return } fmt.Println("Готово!") } ``` Компилятор выдаёт ошибки: `undefined: os.Read` и `undefined: os.Write`. Я посмотрел документацию, но там слишком много разных функций: `os.Open`, `os.Create`, `ioutil.ReadFile`, `bufio.NewReader`... Я совсем запутался. Подскажите, пожалуйста, как правильно читать и записывать файлы в Go? Какие функции лучше использовать для простых операций? Нужно ли закрывать файлы вручную? И как правильно обрабатывать ошибки? Буду благодарен за понятные примеры с пояснениями. Среда: Go 1.21, Windows 10. Файлы текстовые, небольшие (до 100 КБ). Хочется понять самый простой и надёжный способ. <<>>

Основные подходы к чтению и записи файлов в Go

В Go нет функций os.Read и os.Write — это типичная ошибка новичков. Вместо этого используются разные подходы в зависимости от задачи. Рассмотрим самые популярные и правильные способы.

Способ 1: Быстрое чтение/запись всего файла (для небольших файлов)

Самый простой способ — использовать функции из пакета os вместе с ioutil (устарел в Go 1.16, используйте os напрямую):

package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
    // Чтение всего файла в память
    data, err := os.ReadFile("input.txt")
    if err != nil {
        fmt.Printf("Ошибка чтения файла: %v\n", err)
        return
    }

    // Обработка данных (перевод в верхний регистр)
    processedData := strings.ToUpper(string(data))

    // Запись результата в новый файл
    err = os.WriteFile("output.txt", []byte(processedData), 0644)
    if err != nil {
        fmt.Printf("Ошибка записи файла: %v\n", err)
        return
    }

    fmt.Println("Файл успешно обработан!")
    fmt.Printf("Прочитано %d байт, записано %d байт\n", len(data), len(processedData))
}

Преимущества:

  • Минимум кода
  • Автоматическое закрытие файла
  • Простая обработка ошибок

Недостатки: подходит только для файлов, которые помещаются в оперативную память (до 100-200 МБ).

Способ 2: Построчное чтение с буферизацией (для больших файлов)

Если файл большой или нужно обрабатывать данные построчно, используйте bufio:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    // Открываем файл для чтения
    inputFile, err := os.Open("input.txt")
    if err != nil {
        fmt.Printf("Ошибка открытия файла: %v\n", err)
        return
    }
    defer inputFile.Close() // Важно! Закрываем файл при выходе из функции

    // Создаём файл для записи
    outputFile, err := os.Create("output.txt")
    if err != nil {
        fmt.Printf("Ошибка создания файла: %v\n", err)
        return
    }
    defer outputFile.Close()

    // Создаём буферизированные ридер и райтер
    scanner := bufio.NewScanner(inputFile)
    writer := bufio.NewWriter(outputFile)
    defer writer.Flush() // Важно! Сбрасываем буфер перед закрытием

    // Читаем и обрабатываем строки
    lineNumber := 0
    for scanner.Scan() {
        lineNumber++
        line := scanner.Text()
        
        // Обработка строки
        processedLine := strings.ToUpper(line)
        
        // Записываем результат
        _, err := writer.WriteString(processedLine + "\n")
        if err != nil {
            fmt.Printf("Ошибка записи строки %d: %v\n", lineNumber, err)
            return
        }
    }

    // Проверяем ошибки сканера
    if err := scanner.Err(); err != nil {
        fmt.Printf("Ошибка при чтении файла: %v\n", err)
        return
    }

    fmt.Printf("Обработано %d строк\n", lineNumber)
}

Способ 3: Чтение/запись с использованием io.Copy (для копирования данных)

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // Открываем исходный файл
    srcFile, err := os.Open("input.txt")
    if err != nil {
        fmt.Printf("Ошибка: %v\n", err)
        return
    }
    defer srcFile.Close()

    // Создаём целевой файл
    dstFile, err := os.Create("output.txt")
    if err != nil {
        fmt.Printf("Ошибка: %v\n", err)
        return
    }
    defer dstFile.Close()

    // Копируем данные
    bytesCopied, err := io.Copy(dstFile, srcFile)
    if err != nil {
        fmt.Printf("Ошибка копирования: %v\n", err)
        return
    }

    fmt.Printf("Скопировано %d байт\n", bytesCopied)
}

Важные моменты:

  • Всегда закрывайте файлы через defer file.Close()
  • Проверяйте ошибки после каждого вызова ввода-вывода
  • Используйте os.ReadFile и os.WriteFile для простых операций с небольшими файлами
  • Для больших файлов используйте буферизацию через bufio
  • Права доступа при создании файла: 0644 (владелец может читать/писать, остальные — только читать)

Выберите способ, который лучше всего подходит для вашей задачи. Для учебных примеров и небольших файлов рекомендую первый способ — он самый простой и понятный.

<<>>

Альтернативный подход: использование пакета ioutil (для старых версий Go)

Если вы используете Go версии ниже 1.16, то функции os.ReadFile и os.WriteFile недоступны. Вместо них применялся пакет ioutil:

package main

import (
    "fmt"
    "io/ioutil"
    "strings"
)

func main() {
    // Чтение файла
    data, err := ioutil.ReadFile("input.txt")
    if err != nil {
        fmt.Printf("Ошибка: %v\n", err)
        return
    }

    // Обработка
    processed := strings.ToUpper(string(data))

    // Запись файла
    err = ioutil.WriteFile("output.txt", []byte(processed), 0644)
    if err != nil {
        fmt.Printf("Ошибка: %v\n", err)
        return
    }
}

Важно: Начиная с Go 1.16, пакет ioutil объявлен устаревшим (deprecated). Все его функции были перенесены в пакеты os и io. Рекомендуется обновить код на использование os.ReadFile и os.WriteFile.

Дополнительная рекомендация: обработка ошибок с помощью log.Fatal

Для быстрых скриптов и утилит можно упростить обработку ошибок:

package main

import (
    "log"
    "os"
    "strings"
)

func main() {
    data, err := os.ReadFile("input.txt")
    if err != nil {
        log.Fatalf("Не удалось прочитать файл: %v", err)
    }

    processed := strings.ToUpper(string(data))

    err = os.WriteFile("output.txt", []byte(processed), 0644)
    if err != nil {
        log.Fatalf("Не удалось записать файл: %v", err)
    }

    log.Println("Файл успешно обработан!")
}

Функция log.Fatal выведет сообщение об ошибке и завершит программу с кодом 1. Это удобно для простых утилит, но не подходит для production-кода, где требуется более гибкая обработка ошибок.