Работа с файловой системой в Go

В этом уроке мы изучим работу с файловой системой в Go. Это важный навык, который позволит вам:

  • Читать и записывать данные в файлы
  • Управлять директориями и путями
  • Обрабатывать ошибки при работе с файлами
  • Эффективно работать с большими файлами
  • Создавать надёжные системы хранения данных

💡 Интересный факт: Go предоставляет несколько способов работы с файлами, от простых однострочных операций до сложных буферизированных операций, что позволяет выбрать оптимальный подход для каждой задачи.

Основные пакеты для работы с файлами

В Go есть несколько важных пакетов для работы с файлами:

  • os - базовые операции с файлами и директориями
  • io - интерфейсы для ввода/вывода
  • bufio - буферизированные операции
  • path/filepath - работа с путями
  • ioutil - вспомогательные функции

Чтение файлов

1. Простое чтение файла

package main

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

func main() {
	// Открываем файл
	file, err := os.Open("example.txt")
	if err != nil {
		fmt.Println("Ошибка при открытии файла:", err)
		return
	}
	// Гарантированное закрытие файла
	defer file.Close()

	// Чтение содержимого файла
	content, err := ioutil.ReadAll(file)
	if err != nil {
		fmt.Println("Ошибка при чтении файла:", err)
		return
	}

	fmt.Println("Содержимое файла:")
	fmt.Println(string(content))
}

Объяснение:

  • os.Open открывает файл в режиме только для чтения
  • defer file.Close() гарантирует закрытие файла даже при ошибках
  • ioutil.ReadAll читает всё содержимое файла за один раз
  • Преобразование string(content) необходимо, так как ReadAll возвращает байтовый срез

Ожидаемый вывод:

Содержимое файла:
Это содержимое файла example.txt
Вторая строка
Третья строка

2. Построчное чтение файла

package main

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

func main() {
	file, err := os.Open("example.txt")
	if err != nil {
		fmt.Println("Ошибка при открытии файла:", err)
		return
	}
	defer file.Close()

	// Создаём сканер для построчного чтения
	scanner := bufio.NewScanner(file)
	lineNumber := 1

	// Читаем файл построчно
	for scanner.Scan() {
		fmt.Printf("Строка %d: %s\n", lineNumber, scanner.Text())
		lineNumber++
	}

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

Объяснение:

  • bufio.NewScanner создаёт сканер для построчного чтения
  • scanner.Scan() читает следующую строку
  • scanner.Text() возвращает текущую строку
  • scanner.Err() проверяет наличие ошибок

Ожидаемый вывод:

Строка 1: Это содержимое файла example.txt
Строка 2: Вторая строка
Строка 3: Третья строка

Запись в файлы

1. Простая запись в файл

package main

import (
	"fmt"
	"os"
)

func main() {
	// Создаём новый файл (или перезаписываем существующий)
	file, err := os.Create("output.txt")
	if err != nil {
		fmt.Println("Ошибка при создании файла:", err)
		return
	}
	defer file.Close()

	// Записываем несколько строк
	lines := []string{
		"Первая строка",
		"Вторая строка",
		"Третья строка",
	}

	for _, line := range lines {
		_, err := file.WriteString(line + "\n")
		if err != nil {
			fmt.Println("Ошибка при записи в файл:", err)
			return
		}
	}

	fmt.Println("Запись в файл успешно завершена!")
}

Объяснение:

  • os.Create создаёт новый файл или перезаписывает существующий
  • file.WriteString записывает строку в файл
  • Добавляем \n для перевода строки
  • defer file.Close() гарантирует закрытие файла

Ожидаемый вывод:

Запись в файл успешно завершена!

Содержимое файла output.txt:

Первая строка
Вторая строка
Третья строка

2. Буферизированная запись

package main

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

func main() {
	file, err := os.Create("buffered_output.txt")
	if err != nil {
		fmt.Println("Ошибка при создании файла:", err)
		return
	}
	defer file.Close()

	// Создаём буферизированный писатель
	writer := bufio.NewWriter(file)
	
	// Записываем данные в буфер
	for i := 1; i <= 1000; i++ {
		fmt.Fprintf(writer, "Строка %d\n", i)
	}

	// Записываем буфер в файл
	if err := writer.Flush(); err != nil {
		fmt.Println("Ошибка при записи буфера:", err)
		return
	}

	fmt.Println("Буферизированная запись завершена!")
}

Объяснение:

  • bufio.NewWriter создаёт буферизированный писатель
  • fmt.Fprintf форматирует и записывает строку в буфер
  • writer.Flush() записывает буфер в файл
  • Буферизация повышает производительность при большом количестве записей

Ожидаемый вывод:

Буферизированная запись завершена!

Содержимое файла buffered_output.txt:

Строка 1
Строка 2
...
Строка 1000

Работа с директориями

1. Создание и удаление директорий

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	// Создаём путь к новой директории
	dirPath := filepath.Join("test", "subdir", "data")
	
	// Создаём директорию со всеми родительскими директориями
	if err := os.MkdirAll(dirPath, 0755); err != nil {
		fmt.Println("Ошибка при создании директории:", err)
		return
	}

	fmt.Printf("Директория %s создана успешно\n", dirPath)

	// Удаляем директорию и все её содержимое
	if err := os.RemoveAll("test"); err != nil {
		fmt.Println("Ошибка при удалении директории:", err)
		return
	}

	fmt.Println("Директория и её содержимое удалены")
}

Объяснение:

  • filepath.Join создаёт корректный путь для текущей ОС
  • os.MkdirAll создаёт все необходимые директории
  • 0755 устанавливает права доступа (rwxr-xr-x)
  • os.RemoveAll удаляет директорию и всё её содержимое

Ожидаемый вывод:

Директория test/subdir/data создана успешно
Директория и её содержимое удалены

2. Получение информации о файлах

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	// Получаем информацию о файле
	info, err := os.Stat("example.txt")
	if err != nil {
		fmt.Println("Ошибка при получении информации:", err)
		return
	}

	// Выводим информацию о файле
	fmt.Println("Имя файла:", info.Name())
	fmt.Println("Размер:", info.Size(), "байт")
	fmt.Println("Права доступа:", info.Mode())
	fmt.Println("Время изменения:", info.ModTime())
	fmt.Println("Это директория:", info.IsDir())

	// Получаем абсолютный путь
	absPath, err := filepath.Abs("example.txt")
	if err != nil {
		fmt.Println("Ошибка при получении пути:", err)
		return
	}
	fmt.Println("Абсолютный путь:", absPath)
}

Объяснение:

  • os.Stat возвращает информацию о файле
  • info.Name() возвращает имя файла
  • info.Size() возвращает размер в байтах
  • info.Mode() показывает права доступа
  • info.ModTime() возвращает время последнего изменения
  • filepath.Abs получает абсолютный путь к файлу

Ожидаемый вывод:

Имя файла: example.txt
Размер: 1024 байт
Права доступа: -rw-r--r--
Время изменения: 2023-01-01 12:00:00 +0000 UTC
Это директория: false
Абсолютный путь: /home/user/projects/example.txt

Практические задания

Задание 1: Анализатор логов

Создайте программу, которая:

  1. Читает лог-файл построчно
  2. Находит строки, содержащие ошибки
  3. Сохраняет найденные ошибки в отдельный файл
  4. Выводит статистику по ошибкам

Ожидаемый результат:

  • Отфильтрованные ошибки в отдельном файле
  • Статистика по типам ошибок
  • Временные метки ошибок

Задание 2: Менеджер резервных копий

Разработайте программу для:

  1. Создания резервных копий указанных файлов
  2. Хранения нескольких версий файлов
  3. Восстановления файлов из резервной копии
  4. Ведения лога операций

Ожидаемый результат:

  • Резервные копии файлов
  • История изменений
  • Возможность восстановления
  • Лог операций

Задание 3: Система мониторинга

Создайте систему для:

  1. Мониторинга изменений в директории
  2. Отслеживания новых файлов
  3. Проверки размера файлов
  4. Генерации отчётов

Ожидаемый результат:

  • Отслеживание изменений
  • Предупреждения о больших файлах
  • Ежедневные отчёты
  • История изменений

Что дальше?

В следующем уроке мы:

  • Изучим работу с сетью
  • Познакомимся с HTTP-клиентами и серверами
  • Узнаем о протоколах передачи данных
  • Начнём создавать сетевые приложения

🎯 Цель урока: К концу этого урока вы должны уметь:

  • Читать и записывать файлы
  • Работать с директориями
  • Использовать буферизацию
  • Обрабатывать ошибки при работе с файлами