Статьи

Как читать файлы CSV на Java — пример использования Iterator и Decorator

В этой статье я расскажу о том, как читать файлы CSV (значения, разделенные запятыми), используя Apache Common CSV . Из этого примера мы узнаем, как использовать Iterator и Decorator в контексте шаблона проектирования для улучшения возможности повторного использования в различных ситуациях. Но прежде чем мы начнем, я должен сначала ответить на два вопроса.

  1. Зачем мне нужна сторонняя библиотека, если в ней более чем достаточно публикаций, рассказывающих о том, как читать файлы CSV?
    Это правда, что, когда вы гуглите «java csv parser», вы получите несколько похожих постов. Но даже если вы новичок, вы не будете удовлетворены этими мелкими методами. Конечно, используя BufferedReader и String . split () успешно проанализирует типичный CSV-файл, но вы не узнаете НИЧЕГО из него, кроме как сделать избыточным. С другой стороны, подобно тому, что я покажу ниже, использование и изучение Apache Common CSV научит вас нескольким темам в Design Pattern, например, итератор и декоратор.
  2. Почему Apache Common CSV, а не другие?
    Насколько я знаю, есть несколько других библиотек на Sourceforge или в коде Google. Однако, если вы посмотрите на детали их кода, простите мою критику, ни один из них не является гибким и управляемым: некоторые слишком просты, чтобы удовлетворить различные требования пользователей; другие слишком сложны и болезненны для использования. Кроме того, большинство из них, с которыми я сталкивался, не имеют коммерческих лицензий. Знаете, иногда это действительно пугает пользователей.

Apache Common CSV все еще находится в «песочнице», что означает отсутствие официальной загрузки и стабильной версии. Но ночные сборки могут быть доступны.

Использование Iterator для скрытия базового представления

Позвольте мне начать с примера файла CSV, где каждая запись расположена на отдельной строке, разделенной разрывом строки. Первая строка — это заголовок, содержащий два имени COL1 и COL2 соответствующие полям в файле. Остальная часть файла содержит три записи с полями, разделенными запятыми.

1
2
3
4
COL1,COL2
a,b
c,d
e,f

Код, использующий Apache Common CSV для чтения этого файла:

01
02
03
04
05
06
07
08
09
10
11
public void test() throws FileNotFoundException, IOException {
  CSVParser parser = new CSVParser(
      new FileReader("test.csv"),
      CSVFormat.DEFAULT.withHeader());
  for (CSVRecord record : parser) {
    System.out.printf("%s\t%s\n",
      record.get("COL1"),
      record.get("COL2"));
  }
  parser.close();
}

CSVParser используется для анализа файлов CSV в соответствии с указанным форматом. Здесь я использую CSVFormat по умолчанию вместе с настройкой withHeader () без аргументов. Это позволяет синтаксическому анализатору обрабатывать первую строку CSV-файла как заголовок и делать record.get("COL1") допустимой. CSVParser обеспечивает итеративный способ чтения записей. Здесь мы встречаем первый шаблон проектирования Iterator . Он предоставляет способ последовательного доступа к записям файла CSV, не раскрывая его базовое представление, например, как пропустить строку комментария и как сопоставить имя столбца со значением поля. Для каждой записи мы используем CSVRecord.get (имя строки), чтобы получить значение поля по его имени.

CSVRecord предоставляет различные способы доступа к значению поля: по имени или по индексу. Если вы не уверены, что поле имеет значение или является пустым, CSVRecord.isSet(String name) может быть вызван раньше. Если вы просто хотите проверить, было ли определено имя для синтаксического анализатора, вместо этого вызовите CSVRecord.isMapped(String name) .

Использование Decorator для разрешения различного поведения

CSVFormat.DEFAULT или CSVFormat.RFC4180 соответствуют формату RFC4180. Таким образом, поля, заключенные в двойные кавычки, также могут быть обработаны, такие как

1
2
3
4
"COL1","COL2"
"a","b"
"c","d"
"e","f"

В RFC4180 поля в файле CSV должны быть разделены запятыми. Но в целом библиотека может обрабатывать произвольные разделители, такие как TAB или пробел. Чтобы сделать код повторно используемым, библиотека предоставляет способ создать свой собственный CSVFormat ,

1
2
3
CSVFormat format = CSVFormat.newFormat(',')
    .withQuoteChar('"')
    .withHeader();

Вышеуказанный формат такой же, как CSVFormat.DEFAULT . Здесь мы сталкиваемся с другим шаблоном проектирования Decorator , который позволяет добавлять поведение к отдельному объекту, статически или динамически, без влияния на поведение других объектов из того же класса. В случае CSVFormat каждый метод withXXX () возвращает новый CSVFormat, который равен вызывающему, но с одним измененным атрибутом. Вопрос здесь может заключаться в том, почему бы просто не вернуть эту ссылку на себя? Я думаю, что это потому, что поздний способ не сможет следующий код

1
2
3
CSVFormat format = CSVFormat.newFormat(',');
CSVFormat format1 = format.withQuoteChar('"');
CSVFormat format2 = format.withHeader();

Если мы просто вернем это , format1 будет равен format2 , что сейчас абсолютно соответствует ожиданиям.

CSVFormat предоставляет довольно гибкие способы указания формата CSV. Подробности можно найти в его javadoc, который хорошо документирован. Мы можем установить символ разделителя, маркер начала комментария, символ кавычки и т. Д. Поэтому для следующего CSV-файла, где поля разделяются TAB, а комментарии начинаются с # ,

1
2
3
4
5
COL1    COL2
# comments
a       b
c       d
e       f

Мы можем создать формат

1
2
3
4
5
CSVFormat format = CSVFormat.newFormat('\t')
    .withCommentStart('#')
    .withIgnoreEmptyLines(true)
    .withNullString("")
    .withHeader();

Таким образом, Apache Common CSV был запущен для унификации общего и простого интерфейса для чтения и записи файлов CSV под лицензией ASL. Он все еще находится в песочнице, но он достаточно гибкий, чтобы соответствовать различным требованиям. Наконец, я хотел бы подчеркнуть, что чтение сложных кодов действительно полезно для улучшения навыков программирования. Поэтому я очень рекомендую вам прочитать этот исходный код проекта, который очень простой, но мощный.