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