Чтение и запись различных файлов в Go

Чтение и запись файлов — важная функция вашей программы. Не все данные хранятся в том же пространстве памяти, что и ваша программа, и иногда вам нужно поделиться данными с другими программами или просмотреть их позже с помощью другой программы. Хранение данных в файле — хороший способ достижения этих целей. Сегодня мы рассмотрим, как можно читать и записывать данные в широко используемые типы файлов.

io.Reader, io.Writer

Вы когда-нибудь задумывались, как Go может читать и записывать так много разных вещей? Это все благодаря этим мощным интерфейсам. io.Reader описывает все, что Read, а io.Writer описывает все, что Write. Поскольку такое поведение может быть легко воспроизведено, все, что реализует интерфейсы io.Reader и io.Writer, может быть использовано для операций ввода-вывода. Это означает, что вы можете подключать и использовать различные входы и выходы. Вы можете читать из файла CSV и выводить его в JSON, или вы можете читать из stdin и записывать в CSV.

CSV

Файл CSV состоит из строк данных, где каждое значение разделено запятой (отсюда и название «значения, разделенные запятыми»). CSV — не самый быстрый формат для чтения и записи, но он очень универсален и может быть понят многими другими инструментами.

Чтение

package main

import (
    "encoding/csv"
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("see-es-vee.csv")
    if err != nil {
        fmt.Println(err)
    }
    defer f.Close()

    r := csv.NewReader(f)

    records, err := r.ReadAll()
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(records)
}
Вход в полноэкранный режим Выход из полноэкранного режима
[[id fruit color taste] [0 apple red sweet] [1 banana yellow sweet] [2 lemon yellow sour] [3 grapefruit red sour]]
Войти в полноэкранный режим Выход из полноэкранного режима

Вот очень простой способ чтения данных из файла CSV.

  • Сначала мы открываем наш файл see-es-vee.csv и сохраняем его как f.

  • После завершения работы мы хотим закрыть f. Это хороший способ экономии памяти. Хотя это короткая программа и закрытие не так уж необходимо, полезно выработать привычку делать это.

  • f имеет тип *os.File, который реализует интерфейс io.Reader. Поэтому мы можем передать f в csv.NewReader.

  • Это возвращает объект csv.Reader r. csv.Reader — это тип io.Reader, который специализируется на чтении файлов CSV.

  • Каждая строка CSV-файла называется записью. Поэтому мы можем рассматривать CSV-файл как фрагмент записей. r.ReadAll возвращает этот фрагмент записей.

  • Если мы выведем records, то увидим двухмерный срез строк.

Это замечательно, но что если мы захотим применить некоторые операции к каждой записи по мере чтения? К счастью, мы можем использовать более детальный подход.

func main() {
    f, err := os.Open("see-es-vee.csv")
    if err != nil {
        fmt.Println(err)
    }
    defer f.Close()

    r := csv.NewReader(f)

    for {
        record, err := r.Read()
        if err != io.EOF {
            break
        }
        if err != nil {
            fmt.Println(err)
        }

        fmt.Println(record)
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Выглядит примерно так же, как описано выше, верно? Часть до создания r такая же. Давайте посмотрим, что происходит дальше.

  • Мы входим в бесконечный цикл for, потому что мы хотим читать файл построчно, а Go не имеет представления о длине файла.

  • Мы читаем каждую запись, используя r.Read.

  • Как узнать, достигли ли мы конца файла? r.Read возвращает специальную ошибку io.EOF. EOF означает конец файла. Мы ловим эту ошибку в первую очередь и говорим нашей программе выйти из цикла for, как только мы достигнем конца.

  • Для любых других ошибок вы можете просто обработать их обычным способом.

  • После обработки ошибок мы можем делать с извлеченной записью все, что захотим. Некоторые идеи, которые приходят мне в голову, — это капитализация, округление, сравнение с произвольным значением и т.д.

Теперь давайте рассмотрим, как записывать данные в CSV-файл.

package main

import (
    "encoding/csv"
    "os"
)

func main() {
    f, err := os.Create("output.csv")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    w := csv.NewWriter(f)

    records := [][]string{
        {"id", "fruit", "color", "taste"},
        {"0", "apple", "red", "sweet"},
        {"1", "banana", "yellow", "sweet"},
        {"2", "lemon", "yellow", "sour"},
        {"3", "grapefruit", "red", "sour"},
    }

    w.WriteAll(records)
}
Вход в полноэкранный режим Выход из полноэкранного режима
id,fruit,color,taste
0,apple,red,sweet
1,banana,yellow,sweet
2,lemon,yellow,sour
3,grapefruit,red,sour
Войти в полноэкранный режим Выход из полноэкранного режима

Очень просто и понятно.

  • Сначала нам нужно создать файл, в который мы будем сбрасывать наши данные. Мы назовем его output.csv. Мы просто вызываем os.Create и сохраняем его экземпляр как f.

  • Помните, как мы уже создавали csv.Reader? Здесь мы создаем объект csv.Writer w. csv.NewWriter принимает интерфейс io.Writer, который реализует f.

  • Мы подготовим наши данные в виде двумерного среза строк. Мы назовем это records.

  • Наконец, мы просто используем w.WriteAll для записи records в output.csv.

JSON

Поскольку Go широко используется в веб-сервисах, он имеет надежную поддержку JSON. Я написал целый пост о чтении и записи файлов JSON, так что ознакомьтесь с ним для более подробного руководства!

Чтение

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type fruit struct {
    Id    int    `json:"id"`
    Fruit string `json:"fruit"`
    Color string `json:"color"`
    Taste string `json:"taste"`
}

func main() {
    f, err := os.Open("jay-son.json")
    if err != nil {
        fmt.Println(err)
    }
    defer f.Close()

    dec := json.NewDecoder(f)

    // read opening bracket
    _, err = dec.Token()
    if err != nil {
        fmt.Println(err)
    }

    for dec.More() {
        var fr fruit
        err := dec.Decode(&fr)
        if err != nil {
            fmt.Println(err)
        }

        fmt.Println(fr)
    }

    // read closing bracket
    _, err = dec.Token()
    if err != nil {
        fmt.Println(err)
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима
{0 apple red sweet}
{1 banana yellow sweet}
{2 lemon yellow sour}
{3 grapefruit red sour}
Войти в полноэкранный режим Выход из полноэкранного режима
// jay-son.json
[
    {"id": 0, "fruit": "apple", "color": "red", "taste": "sweet"},
    {"id": 1, "fruit": "banana", "color": "yellow", "taste": "sweet"},
    {"id": 2, "fruit": "lemon", "color": "yellow", "taste": "sour"},
    {"id": 3, "fruit": "grapefruit", "color": "red", "taste": "sour"}
]
Войти в полноэкранный режим Выход из полноэкранного режима

Вот простой способ чтения файлов JSON. Обычно JSON-файлы приходят в виде потоков. То есть, они приходят в виде списка объектов. Go обрабатывает потоки с помощью декодера.

Давайте сначала посмотрим на это:

type fruit struct {
    Id    int    `json:"id"`
    Fruit string `json:"fruit"`
    Color string `json:"color"`
    Taste string `json:"taste"`
}
Вход в полноэкранный режим Выход из полноэкранного режима

Эта структура выступает в качестве модели. Поскольку JSON может иметь различные формы и размеры, в идеале вы хотите иметь модель, которая отражает структуру JSON. Поля struct должны быть открытыми и иметь метку справа, обозначающую, к какому ключу они относятся. Если вы заранее не знаете, как будет выглядеть JSON, Go просто использует map[string]interface{}.

  • Сначала мы откроем файл и сохраним его экземпляр в f. Не забудьте отложить вызов закрытия f на потом!

  • Мы создаем наш объект декодера dec с помощью json.NewDecoder. Как и csv.NewReader, он принимает io.Reader. Вы можете начать видеть силу интерфейсов — детали чтения абстрагированы, а рабочий процесс чтения согласован для множества различных типов файлов.

  • Как только dec создан, мы можем читать наш JSON. Правда, есть одна проблема. Нам нужно убедиться, что мы поймали открывающие и закрывающие скобки с помощью dec.Token. Не делать этого — все равно что пытаться съесть сэндвич из метро, не снимая обертки. Блеа.

  • Подобно тому, как мы читаем CSV-файл строка за строкой, мы читаем JSON-поток объект за объектом. Мы выполняем цикл dec.More, который возвращает true до тех пор, пока еще остались объекты для чтения.

  • Мы создаем экземпляр fruit для хранения данных нашего объекта. Используем dec.Decode для сброса данных объекта в f. Теперь вы можете делать с этим все, что захотите.

  • После завершения чтения не забудьте поймать закрывающую скобку.

Запись

Запись в файл JSON также проста. Мы называем это кодированием.

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type Fruit struct {
    Id    int    `json:"id"`
    Fruit string `json:"fruit"`
    Color string `json:"color"`
    Taste string `json:"taste"`
}

func main() {
    f, err := os.Create("output.json")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    enc := json.NewEncoder(f)

    apple := Fruit{Id: 0, Fruit: "apple", Color: "red", Taste: "sweet"}
    banana := Fruit{Id: 1, Fruit: "banana", Color: "yellow", Taste: "sweet"}
    lemon := Fruit{Id: 2, Fruit: "lemon", Color: "yellow", Taste: "sour"}
    grapefruit := Fruit{Id: 3, Fruit: "grapefruit", Color: "red", Taste: "sour"}

    fruits := []Fruit{apple, banana, lemon, grapefruit}

    err = enc.Encode(fruits)
    if err != nil {
        fmt.Println(err)
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима
[{"id":0,"fruit":"apple","color":"red","taste":"sweet"},{"id":1,"fruit":"banana","color":"yellow","taste":"sweet"},{"id":2,"fruit":"lemon","color":"yellow","taste":"sour"},{"id":3,"fruit":"grapefruit","color":"red","taste":"sour"}]
Вход в полноэкранный режим Выход из полноэкранного режима

Этот пункт также очень прост.

  • Мы создаем файл output.json для сброса данных в него.

  • Мы создаем новый кодировщик enc с помощью json.NewEncoder.

  • Мы подготавливаем наши данные, которые представляют собой фрагмент объектов Fruit.

  • Наконец, мы кодируем этот фрагмент с помощью enc.Encode.

Excel

По умолчанию Go не поддерживает чтение и запись файлов Excel. Однако существует популярная библиотека qax-os/excelize, которая поможет вам это сделать. Если разобрать исходный код, то можно увидеть, что пакет широко использует *os.File, который также является io.Reader и io.Writer. Я думаю, это показывает красоту интерфейсов io.Reader и io.Writer, потому что, немного подправив их, вы можете создать собственные читатель и писатель, реализующие эти интерфейсы, что позволит вам поддерживать больше типов файлов.

Заключение

Надеюсь, этот пост послужил быстрым введением в чтение и запись файлов в Go, и насколько мощными являются интерфейсы io.Reader и io.Writer. Я думаю, это одна из прелестей Go — интерфейсы позволяют создавать очень гибкий и многократно используемый код. Конечно, существует больше файлов, которые мы не рассмотрели в этой заметке, но общая суть та же: откройте файл, создайте читателя или писателя и читайте/пишите из него. Спасибо!

Вы также можете прочитать этот пост на Medium и на моем личном сайте.

Оцените статью
devanswers.ru
Добавить комментарий