Skip to content

JDD: CSV

seralekseenko edited this page Oct 19, 2021 · 7 revisions

csv.png

Содержание

Введение

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

В этом формате, каждая строчка в файле — это одна строка таблицы, а разделителем (delimiter) полей (колонок, столбцов) служит чаще всего символ запятой (,), однако на практике могут использоваться и другие разделители. Если разделитель встречается в значении ячейки, то значение обрамляется двойными кавычками. Если в значении встречаются кавычки — они представляются в файле в виде двух кавычек подряд (Samsung U600 24" будет выглядеть как "Samsung U600 24""").

Пример CSV в виде текста

Рассмотрим, пример табличного представления данных, которое нужно представить в CSV:

csv_example.png

В этом примере видим 3 строки (rows) и 4 поля (fields). Каждая строка должна иметь одинаковое количество полей. Первая строка, с точки зрения пользователя, логически представляет собой строку-заголовок (header), а две последующие строки — строки-значения (values).

В этом примере, у таблицы есть заголовок, но его может и вовсе не быть. Если вы получили файл в CSV формате, то узнать есть ли в нем заголовок или нет, можно только когда вы его откроете и сами убедитесь, или же заранее договоритесь с поставщиком CSV, о том что всякий файл в этом формате будет всегда содержать строку-заголовок. Такого понятия как строка-заголовок в CSV нет, технически это строка-значение, которая логически выполняет роль заголовка.

Итоговый CSV с использованием запятой как разделителя, будет выглядеть следующим образом:

Фамилия,Имя,Отчество,Телефон
Иванов,Иван,Иванович,"+7 906 710 56 10, 222-22-22"
Петров,Петр,Петрович,01;02

Как видите, из-за того что значение колонки с телефоном для второй строки содержит знак запятой, пришлось его обрамить в двойные кавычки. Также нужно отметить, что CSV никак не обозначает, какие типы данных он содержит. Если логически мы имеем строки, числа, даты и т.д., то в CSV это просто строки.

Представление CSV в виде обьекта в памяти

CSV хранятся в виде текстовых файлов, а вот работать с ними в коде может быть удобнее в виде какой-то абстрации. Так как в Java у нас все является обьектом, и принято программировать в парадигме ООП, то абстракции здесь строят через классы. Поэтому, создадим класс с названием Csv, который будет инкапсулировать в себе поля header и values, а также геттеры header() и values() для того чтобы получать эти поля:

public class Csv {
  private String[] header;
  private String[][] values;

  public Csv(String[] header, String[][] values) {
    this.header = header;
    this.values = values;
  }
  
  public String[] header() {
    return this.header;
  }

  public String[][] values() {
    return this.values;
  }  
}

Если у вас Java 15 или выше, то код можно упростить, используя record-class

public record Csv(String[] header, String[][] values) {}

А, для того чтобы превращать Csv обьект в файл и наоборот, можно создать класс-хелпер, который будет иметь методы со следующей сигнатурой:

public class CsvHelper {
  public static Csv parseFile(FileReader reader) throws FileNotFoundException {
    // Передаем FileReader для какого-то CSV файла, с которого построчно читаем строку-header и строки-values и с помощью них строим CSV обьект, который потом возвращаеи
  }

  public static void writeCsv(FileWriter writer, Csv csv) throws IOException {
    // Передаем FileWriter (с помощь. когторого будем писать в файл) и CSV обьект, header и values поля которого превращаем в строки, со значениями, разделенными запятой
  }
}

Простая абстракция над CSV

Если продолжать строить абстракции дальше и попробовать реализовать очень простое подобие БД над нашим классом Csv для выполнения над ним простых операций, на подобии тех, которые существуют в языке SQL (select/insert/update/delete/join), то эти операции можно представить в виде соответсвующих классов с прибавлением суфикса, например, в нашем случае - суфиксом Request (суфикс в названии служит просто для их визуального выделения среди других классов, и может быть Operation, или какой-либо другой):

public class InsertRequest implements Request<Csv> {
  public InsertRequest(Csv target, String[] line) {
    // TODO
  }

  @Override
  public Csv execute() {
    // TODO
  }
}
public class DeleteRequest implements Request<Csv> {

  public DeleteRequest(Csv target, Selector whereSelector) {
    // TODO
  }

  @Override
  public Csv execute() {
    // TODO
  }
}
public class UpdateRequest implements Request<Csv> {
  public UpdateRequest(Csv target, Selector whereSelector, Selector updateToSelector) {
    // TODO
  }

  @Override
  public Csv execute() {
    // TODO
  }
}
public class SelectRequest implements Request<Csv> {
  public SelectRequest(Csv target, Selector selector, String[] columns) {
    // TODO
  }

  @Override
  public String[][] execute() {
    // TODO
  }
}
public class JoinRequest implements Request<Csv> {
  public JoinRequest(Csv from, Csv on, String by) {
    // TODO
  }

  @Override
  public Csv execute() {
    // TODO
  }
}

Все эти классы-операции обьединяет то, что они должны реализовать метод execute() интерфейса Request, в котором у каждого класса происходит непосредственная логика их работа над Csv:

public interface Request<T> {
    abstract T execute();
}

Также в некоторых классах используется вспомогательный record-класс Selector, который инкапсуляцирует название какого-то поля и его значение.

public record Selector(String fieldName, String value) {}

Insert

Принимает в конструктор target типа Csv и line типа массив строк, что логически представляют собой строку-значение. При выполнении, должен создать новый обьект типа Csv, но уже с учетом добавленой этой новой строки-значения

Дано:

target (Csv):

name, surname, age
Petr, Petrov, 18

line (String[]):

Vasya, Vasiliev, 19

Результат:

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 19

Delete

Принимает в конструктор target типа Csv и whereSelector типа Selector, что логически представляют собой название поля (fieldName) и его значение (value). При выполнении, удалить из target любую строку-значение, у которой значение поля совпадает с value

Дано:

target (Csv):

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 19

whereSelector:

fieldName: name
value Vasya

Результат:

name, surname, age
Petr, Petrov, 18

В результати из Csv удалили целую строку Vasya, Vasiliev, 19, т.к. у нее значение поля совпало с переданным value.

Update

Работает по похожему принципу как DeleteRequest, но только у строки, у которой значение поля совпало с переданным value обновляет поле на значения с селектора updateToSelector

Дано:

target (Csv):

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 19

whereSelector:

fieldName: name
value Vasya

updateToSelector:

fieldName: age
value: 31

Результат:

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 31

Select

Csv target, Selector selector, String[] columns Принимает в конструктор target типа Csv и selector типа Selector, что логически представляют собой название поля (fieldName) и его значение (value), а также названия полей (полей) с именем columns в виде массива строк. При выполнении, ищет в target все строки-значения, которые удовлетвоняют селектор selector, и потом позвращает только те поля, названия которых есть в массиве аргумента columns

Пример 1.

Дано:

target (Csv):

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 19

selector:

fieldName: surname
value Petrov

columns:

[name, surname]

Результат:

[
  [Petr, Petrov]
]

Пример 2.

Дано:

target (Csv):

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 18

selector:

fieldName: surname
value Petrov

columns:

[]

Результат:

[
  [Petr, Petrov, 18]
]

Пример 3.

Дано:

target (Csv):

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 18

selector:

fieldName: age
value 18

columns:

[name]

Результат:

[
  [Petr],
  [Vasya]
]

Join

Получает два Cvs и обьединяет их по полю by

Дано:

from (Csv):

name, surname, age
Petr, Petrov, 18
Vasya, Vasiliev, 18

on (Csv):

name, city, pet
Vasya, Berlin, dog
Petr, London, cat

by (String):

name

Результат:

name, surname, age, city, pet
Petr, Petrov, 18, London, cat
Vasya, Vasiliev, 18, Berlin, dog

Дополнительные материалы