CSV-документы

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>.

Если в приложении несколько ключевых сущностей, то для каждой следует завести своё хранилище

© A. M. Васильев, 2026, CC BY-SA 4.0, andrey@crafted.su