os, которые отвечают за работу с файлами. Вы научитесь создавать и открывать файлы, читать их и записывать в них данные. Узнаете, как работать с временными файлами и директориями.Producer и Consumer. Поставщик сохраняет в файл историю покупок автомобилей в виде JSON-строк, а потребитель — читает историю событий из этого файла. Событие представлено в виде структуры:type Event struct {
ID uint `json:"id"`
CarModel string `json:"car_model"`
Price float64 `json:"price"`
} OpenFile(), которая позволяет открыть файл в разных режимах:func OpenFile(name string, flag int, perm FileMode) (*File, error) flag — это битовая маска нужных констант. Список констант:const (
// можно выбрать только один режим среди O_RDONLY, O_WRONLY и O_RDWR
O_RDONLY int = syscall.O_RDONLY // открыть файл в режиме чтения
O_WRONLY int = syscall.O_WRONLY // открыть файл в режиме записи
O_RDWR int = syscall.O_RDWR // открыть файл в режиме чтения и записи
// значения для управления поведением файла
O_APPEND int = syscall.O_APPEND // добавлять новые данные в файл при записи
O_CREATE int = syscall.O_CREAT // создать новый файл, если файла не существует
O_EXCL int = syscall.O_EXCL // используется вместе с O_CREATE и возвращает
// ошибку, если файл уже существует
O_SYNC int = syscall.O_SYNC // открыть в режиме синхронного ввода/вывода
O_TRUNC int = syscall.O_TRUNC // очистить файл при открытии
) perm функции OpenFile() — это набор модификаторов доступа, с которыми будет создан новый файл, если его не существует. Набор доступов задаётся в виде числа, как в Unix-подобных системах. Если вы не знакомы с ними, почитать о них можно на linux.com.OpenFile() возвращает указатель на структуру File, у которой есть много методов для работы с содержимым файла, так что она реализует основные интерфейсы пакета io: io.Reader, io.ReaderAt, io.ReaderFrom, io.Writer, io.WriterAt, io.Seeker, io.Closer и другие.*File:// Read читает из файла и записывает в буфер p.
func (f *File) Read(p []byte) (n int, err error)
// ReadAt читает из файла со смещением off и записывает в буфер p.
func (f *File) ReadAt(p []byte, off int64) (n int, err error)
// ReadFrom читает из r и записывает в файл.
func (f *File) ReadFrom(r Reader) (n int64, err error)
// Write записывает из буфера p в файл.
func (f *File) Write(p []byte) (n int, err error)
// WriteAt записывает из буфера p в файл со смещением off.
func (f *File) WriteAt(p []byte, off int64) (n int, err error)
// Seek устанавливает смещение offset для следующего чтения
// или записи в зависимости от whence:
// 0 — относительно начала файла,
// 1 — относительно текущего смещения,
// 2 — относительно конца файла.
func (f *File) Seek(offset int64, whence int) (int64, error)
// Close закрывает файл.
func (f *File) Close() error File. После этого можно будет пользоваться её методами для чтения и изменения его содержимого. Пока файл открыт, можно читать и записывать данные. По умолчанию чтение и запись продолжается с того места, где закончилась предыдущая операция. После выполнения всех необходимых действий с файлом его следует закрыть.defer f.Close().O_WRONLY;O_CREATE;O_APPEND.O_RDONLY;O_CREATE;flag1 := os.O_WRONLY | os.O_CREATE | os.O_APPEND
flag2 := os.O_RDONLY | os.O_CREATE type Producer struct {
file *os.File // файл для записи
}
func NewProducer(filename string) (*Producer, error) {
// открываем файл для записи в конец
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
return &Producer{file: file}, nil
}
func (p *Producer) Close() error {
// закрываем файл
return p.file.Close()
}
type Consumer struct {
file *os.File // файл для чтения
}
func NewConsumer(filename string) (*Consumer, error) {
// открываем файл для чтения
file, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
return &Consumer{file: file}, nil
}
func (c *Consumer) Close() error {
// закрываем файл
return c.file.Close()
} os есть две простые функции, которые можно использовать, если файл нужно создать или открыть только для чтения:func Open(name string) (*File, error) Open() попытается открыть файл в режиме чтения. Если файла с указанным именем не существует, Open вернёт ошибку.Create():func Create(name string) (*File, error) Producer должен записывать строки в JSON-формате в файл, а Consumer - последовательно читать эту информацию из того же самого файла.Write структуры File:func (f *File) Write(p []byte) (n int, err error) Write сохраняет данные в файл и возвращает количество записанных байт и ошибку.Event в слайс байт функцией json.Marshal. События разделим символом переноса строки:func (p *Producer) WriteEvent(event *Event) error {
data, err := json.Marshal(&event)
if err != nil {
return err
}
// добавляем перенос строки
data = append(data, '\n')
_, err = p.file.Write(data)
return err
} Read данные, записав их в буфер.Reader, Writer, ReadWriter.Reader и Writer из пакета bufio:type Producer struct {
file *os.File
// добавляем Writer в Producer
writer *bufio.Writer
}
func NewProducer(filename string) (*Producer, error) {
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
return &Producer{
file: file,
// создаём новый Writer
writer: bufio.NewWriter(file),
}, nil
}
func (p *Producer) WriteEvent(event *Event) error {
data, err := json.Marshal(&event)
if err != nil {
return err
}
// записываем событие в буфер
if _, err := p.writer.Write(data); err != nil {
return err
}
// добавляем перенос строки
if err := p.writer.WriteByte('\n'); err != nil {
return err
}
// записываем буфер в файл
return p.writer.Flush()
}
type Consumer struct {
file *os.File
// добавляем reader в Consumer
reader *bufio.Reader
}
func NewConsumer(filename string) (*Consumer, error) {
file, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
return &Consumer{
file: file,
// создаём новый Reader
reader: bufio.NewReader(file),
}, nil
}
func (c *Consumer) ReadEvent() (*Event, error) {
// читаем данные до символа переноса строки
data, err := c.reader.ReadBytes('\n')
if err != nil {
return nil, err
}
// преобразуем данные из JSON-представления в структуру
event := Event{}
err = json.Unmarshal(data, &event)
if err != nil {
return nil, err
}
return &event, nil
} Writer.Flush для сброса буфера, иначе данные так и останутся в нём.bufio есть структура Scanner. Она предоставляет удобный интерфейс для чтения данных, например, файла строк. Структура Scanner реализует методы:// Buffer устанавливает свой буфер buf с максимальным размером max.
func (s *Scanner) Buffer(buf []byte, max int)
// Bytes возвращает данные последнего сканирования.
func (s *Scanner) Bytes() []byte
// Err возвращает ошибку сканирования.
// Если scanner достиг конца файла, в качестве ошибки вернётся nil.
func (s *Scanner) Err() error
// Scan производит сканирование до следующего токена (разделителя).
func (s *Scanner) Scan() bool
// Split задаёт свою функцию сканирования.
// По умолчанию используется построчное сканирование.
func (s *Scanner) Split(split SplitFunc)
// Text возвращает данные последнего сканирования в виде строки.
func (s *Scanner) Text() string Scanner. Код стал проще. Теперь он выглядит так: type Consumer struct {
file *os.File
// заменяем Reader на Scanner
scanner *bufio.Scanner
}
func NewConsumer(filename string) (*Consumer, error) {
file, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
return &Consumer{
file: file,
// создаём новый scanner
scanner: bufio.NewScanner(file),
}, nil
}
func (c *Consumer) ReadEvent() (*Event, error) {
// одиночное сканирование до следующей строки
if !c.scanner.Scan() {
return nil, c.scanner.Err()
}
// читаем данные из scanner
data := c.scanner.Bytes()
event := Event{}
err := json.Unmarshal(data, &event)
if err != nil {
return nil, err
}
return &event, nil
}
func (c *Consumer) Close() error {
return c.file.Close()
} go run компилятор создаёт временный исполняемый файл и выполняет его. Пакет os содержит несколько функций для создания временных файлов и директорий:// TempDir возвращает путь до временного каталога (может быть пустым).
func TempDir() string
// MkdirTemp создаёт временный каталог по пути dir.
func MkdirTemp(dir, pattern string) (string, error)
// CreateTemp создаёт временный файл по пути dir.
func CreateTemp(dir, pattern string) (*File, error) MkdirTemp и CreateTemp принимают pattern, который будет использоваться для генерации имени временного файла или каталога. Если pattern содержит символ *, вместо него подставится случайная последовательность символов. Если же символа * нет, случайная последовательность символов добавится к концу pattern. Например, по pattern my*test сгенерируется имя my998518050test, а по pattern mytest — mytest3745872346.Remove или RemoveAll:// Remove удаляет файл или пустой каталог.
func Remove(name string) error
// RemoveAll удаляет каталог со всеми вложенными файлами и каталогами.
func RemoveAll(path string) error os используются для сохранения настроек и конфигураций. Как правило, настройки в файлах не занимают большой объём и могут быть записаны или прочитаны без дополнительной буферизации. Для записи данных в файл можно использовать функцию WriteFile(name string, data []byte, perm FileMode) error, а для чтения — ReadFile(name string) ([]byte, error). Эти функции не требуют дополнительного открытия и закрытия файла, WriteFile записывает в файл указанный слайс байт, а ReadFile читает всё содержимое файла и возвращает тоже слайс байт..json-файл.package settings
import (
"encoding/json"
"os"
"testing"
)
// Settings содержит настройки приложения.
type Settings struct {
Port int `json:"port"`
Host string `json:"host"`
}
// Save сохраняет настройки в файле fname.
func (settings Settings) Save(fname string) error {
// сериализуем структуру в JSON формат
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
// сохраняем данные в файл
return os.WriteFile(fname, data, 0666)
}
func TestSettings(t *testing.T) {
fname := `settings.json`
settings := Settings{
Port: 3000,
Host: `localhost`,
}
if err := settings.Save(fname); err != nil {
t.Error(err)
}
} os.bufio.