CSV-документы
#
Васильев Андрей Михайлович
Версии презентации
Стандарты для обмена данными
#
Каждому разработчику доступна возможность работы с файлами:
- В формате строкового представления
- В формате бинарного представления
Разработчик может реализовать любую схему хранения данных
Почему стоит разработать своё представление данных:
- Максимальная эффективность хранения данных
- Максимальная эффективность операций чтения и записи данных
- Дополнительная защита данных от неискушённого пользователя
Почему стоит использовать стандартные представления:
- Средства чтения и записи доступны в виде библиотек (или части ЯП)
- Разработчику необходимо понять только семантику данных, не форму
Comma Separated Values, CSV
#
Формат CSV (значения, разделённые запятыми) чисто технически не является
стандартом, т.к. не существует формального документа, описывающего его структуру
Однако данный формат явился одним из первых подходов к представлению табличных
данных:
- Представление данных является текстовым, т.е. не требует специального
дорогостоящего офисного пакета
- В каждой строке размещаются ячейки таблицы, разделённые запятыми
- Первая строка может содержать не значения, а названия столбцов
- Все значения ячеек — строковые, их необходимо преобразовать к корректному
числовому
Пример CSV-документа
#
Рассмотрим следующий пример:
Id,SideA,SideB,SideC,RegistrationDateTime,BorderColor,FillColor,Description
66ade672-2301-4408-9471-8a17aa883d65,1,2,5,2024-08-26T19:01:41.114511727,GREEN,RED,"странные данные, это неправильный треугольник"
a6be5e23-b8d4-4d78-8aa3-1c204d0befc8,2,3,5,2024-07-10T17:26:41.129567382,RED,GREEN,"точки расположены на отрезке"
baa30333-23d6-4eba-a8ed-7c235adf4228,2,3,4,2024-07-27T00:10:41.129642513,RED,GREEN,"обычный треугольник"
- Первая строка содержит название столбцов
- Следующие три строки содержат описание треугольников
- Если в значении поля нет запятой, то оно пишется без кавычек
- Вручную учесть все особенности будет трудно, особенно если они нигде явно не
специфицированы
Библиотека kotlin-csv
#
- Исходный код библиотеки: https://github.com/jsoizo/kotlin-csv
- Строка подключения библиотеки для шаблона:
"com.jsoizo:kotlin-csv:2.0.0"
- Распространяется под лицензией Apache 2.0, позволяющей коммерческое
использование
- Позволяет считывать данные из CSV-документов
- Позволяет записывать данные в CSV-документы
- Используется в рамках автоматизированной системы проверки
Чтение данных из CSV-документа
#
Для чтения данных необходимо создать объект класса
com.github.doyaaaaaken.kotlincsv.client.CsvReader через конструктор или
функцию-помощник com.github.doyaaaaaken.kotlincsv.dsl.csvReader()
Название классов отличается от названия библиотеки, т.к. сменился ответственный
разработчик
Данный объект предоставляет следующие функции:
readAll(): List<List<String>> — преобразует CSV-документ в список списков
строк. Каждой строке входного файла соответствует список строк в результате
работы функции
readAllWithHeader(): List<Map<String,String>> — преобразует CSV-документ в
список словарей. Ключами словаря являются строки, записанные в строке-заголовке
Данные могут быть считаны из строки, файла, входного потока данных
Пример чтения файла:
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
import java.io.File
fun main() {
val rows = csvReader().readAll(File("../data/triangles.csv"))
// Первая строка — заголовок, далее строки с данными
rows.forEach { println(it) }
val rowsByHeader = csvReader().readAllWithHeader(File("../data/triangles.csv"))
rowsByHeader.forEach { row ->
println("${row["Id"]} ${row["SideA"]} ${row["FillColor"]}")
}
}
Обратите внимание: readAll() включает строку-заголовок в результат, а
readAllWithHeader() — нет, зато возвращает словарь по именам столбцов.
Запись данных в CSV-документ
#
Для записи данных необходимо создать объект класса
com.github.doyaaaaaken.kotlincsv.client.CsvWriter через конструктор или
функцию-помощник com.github.doyaaaaaken.kotlincsv.dsl.csvWriter()
Данный объект предоставляет функцию
writeAll(rows: List<List<String>>, file: File, append: Boolean = false)
- Первым аргументом указывается список строк для записи
- Вторым аргументом указывается файл, либо название файла, либо поток вывода
- Параметр
append со значением true дописывает строки в конец
существующего файла вместо перезаписи
Хранилище данных
#
Ввиду увеличения объёма кода внутри приложения становится вопрос о выделении
различных компонентов, решающих конкретные задачи
Первым таким компонентом может стать хранилище данных
- Обеспечение чтения данных с жёсткого диска при старте приложения
- Предоставление единого взгляда на данные внутри всего приложения
- Реализация сохранения данных при завершении работы приложения
Для обеспечения данной функции достаточным видится следующий набор функций:
- Считывание данных из файла, принимает путь к файлу
- Запись данных хранилища в файл, принимает путь к файлу
- Получение списка всех элементов из хранилища, возвращает список элементов
- Получение конкретного элемента по идентификатору
Назовём такой класс TriangleStorage. Набросок реализации:
class TriangleStorage {
private val triangles = mutableListOf<Triangle>()
fun read(file: File) {
val rows = csvReader().readAllWithHeader(file)
rows.forEach { row -> triangles.add(triangleFrom(row)) }
}
fun write(file: File) {
val rows = triangles.map { triangleToList(it) }
csvWriter().writeAll(rows, file)
}
fun getAll(): List<Triangle> = triangles.toList()
fun getById(id: UUID): Triangle? = triangles.firstOrNull { it.id == id }
}
Преобразование строки CSV в объект Triangle удобно вынести в отдельную
функцию triangleFrom(row: Map<String, String>): Triangle. Обратное
преобразование объекта Triangle в список строк — в функцию
triangleToList(triangle: Triangle): List<String>.
Если в приложении несколько ключевых сущностей, то для каждой следует завести
своё хранилище