4
April
2008

Работа с CSV. Чтение и запись CSV файлов в Java (comma separated values file)

Posted in: Java open-source проекты, Java technologies, J2SE, Полезные программы |

Ре?ил сделать свой вклад в мировое open-source сообщество. Как вы уже догадались из названия статьи, на этот раз речь пойдет о формате CSV.

?так, проблема заключалась в том, что генерация отчетов в формате Excel через библиотеку POI ку?ала много оперативной памяти и процессорного времени.

Сам по себе Excel, наверное, один из немногих хоро?их продуктов компании Microsoft, но вот формат файлов оставляет желать луч?его. Короче говоря, при генерации документа необходимо полностью держать его в памяти.

Выходом из ситуации является использование формата CSV (comma-separated values file format), который к счастью тоже читается с помощью Excel.

Пока что не существует строго описанной спецификации CSV формата. Поэтому для создания удобной библиотеки на Java при?лось порыться в интернете. Вот список ресурсов, которые я анализировал:

Сразу хочу обратить ва?е внимание на то, что стандартное поведение библиотеки рассчитано на чтение и запись CSV файлов, которые понимает Excel, так как это самый распространенный вариант использования CSV файлов.

Все различия между Excel CSV и Pure CSV вынесены в удобно конфигурируемые свойства:
delimiter - разделитель, по умолчанию - “;”
preserveSpaces - сохранять ли пробелы при чтении, по умолчанию - “true”
ignoreEmptyLines - игнорировать ли пустые линии (то есть те, в которых нет значений и разделителей), по умолчанию - “false”
ignoreComments - игнорировать ли комментарии, по умолчанию - “false”

?значально, конечно же, очень хотелось наворотить библиотеку всякими “полезными” функциями: например, поддержкой заголовков, Unix-подобным эскейпингом и т.д.. Но разум взял верх и получилась, на мой взгляд, вполне хоро?ая библиотека без ли?него мусора.

Ниже представлены примеры кода на Java с использованием библиотеки.

Для создания и записи в CSV файл:

  Csv.Writer writer = new Csv.Writer("filename").delimiter(',');
  writer.comment("example of csv")
      .value("a").value("b").newLine()
      .value("c").close();

В результате выполнения этого кода будет сгенерирован следующий файл:

#example of csv
a,b
c

А это вариант для чтения данных из CSV (предположим, что мы читаем файл сгенерированный в предыдущем примере):

  Csv.Reader reader = new Csv.Reader(new FileReader("filename"))
      .delimiter(',').ignoreComments(true);
  System.out.println(reader.readLine());

Результат будет таким:

[a, b]

Для более изощренных вариантов использования посмотрите файл CsvTestCase из папки src библиотеки. В jar этот тест кейс я не вносил, чтобы не создавать ли?ние dependency на библиотеку.

Библиотека является свободной для использования и распространения. Скачать ее можно здесь - Java CSV Library

Хочется сделать библиотеку луч?е и исправить о?ибки, если они там не дай бог есть. Поэтому очень жду ва?их вопросов и замечаний.

Updated (07.04.2008): Сме?но, конечно, но уже 3-ий раз меняю логику обработки исключений в библиотеке, а точнее имена и иерархию исключений. При этом остальной код остался без изменений.

?так, базовое исключение для всех специфичных для библиотеки о?ибок - Csv.Exception (extends RuntimeException). Его можно ловить и игнорировать в случае, если это исключение не критично для дальней?ей работы приложения.

При возникновении о?ибки связанной с форматом CSV бросается Csv.FormatException (extends Csv.Exception). Если эта о?ибка возникла при записи в файл, то ловить ее не имеет смысла (это значит,что программист неправильно генерирует файл). Если же о?ибка возникла при чтении, то ловить или не ловить ее ре?ает программист.

При возникновении java.io.IOException она оборачивается в Csv.IOException (exstends Csv.Exception). Ловить или не ловить исключение ре?ает программист. Ловить ее можно и нужно, например, при чтении/записи CSV файла по сети. В этом случае можно переконнектиться и начать чтение/запись заново. Если она возникла при других условиях, то тут уже ничего не поделае?ь…

21 Comments »

RSS feed for comments on this post.



April 4, 2008 #

Скачал библиотеку, посмотрел исходник. Код понятный, читабельный, не понял только зачем заводить свой эксеп?н да еще наследуя его от RuntimeException.

З.Ы. Понравился файл с лицензией ) ?мхо можно было бы выбрать что-нибудь стандартное вроде BSDL. Впрочем ва? код - ва?е право.

З.Ы.Ы. Спасибо за библиотеку.

April 4, 2008 #

Спасибо боль?ое за комментарий. Если будете ее где-нибудь использовать, напи?ите, что было удобно, что нет.

На счет лицензирования: К сожалению эту область я еще не изучал.

На счет Exception: Мне боль?е нравится использовать unchecked exceptions, но только в том случае, если ситуация действительно исключительная и не может быть обработана пользователем класса в боль?инстве случаев.

В случае с невалидным CSV пользователь ничего не может сделать: точнее при записи в CSV он должен переписать свой неправильный код, а при чтении - отказаться от парсинга невалидного файла.

Единственное, на счет чего я сомневаюсь - то что для о?ибок невалидности формата я использовал тот же Exception, что и для обертки IOException.
Наверное для о?ибок формата луч?е бросать IllegalStateException с соответствующим текстом.
Ну а IOException я оборачиваю в RuntimeException для того, чтобы пользователь моего класса не матюкался добавляя в своих методах некрасивые конструкции типа throws или try/catch/finally :).
Если программист посчитает нужным ловить Csv.Exception, он напи?ет соответствующий код. Но ловить ее надо только в случае чтения/записи CSV через сеть, так как только в этом случае можно попробовать корректно ре?ить проблему - переконнектится и даль?е совер?ить чтение/запись.

April 5, 2008 #

Хм… я сторонник несколько иной стратегии использования исключений. Хотя может быть я не понимаю всего смысла. Предположим что возникнет рантайм-эксеп?н в обычном однопоточном приложении. Оно не прибьется?

Кстати согласен с вами что для о?ибки парсера надо использовать другое исключение. Все-таки чтение файла и парсинг - разные операции. По поводу использования IllegalStateException - я думаю луч?е написать свои типы сообщений, их и пробрасывать. Код использующий библиотеку будет читабельнее.

Alexander
April 8, 2008 #

Здравствуйте! А подскажите, пожалуйста, если я все 4 параметра подставлю по умолчанию, получится Excel или Pure CSV?

April 8, 2008 #

Стандартное поведение библиотеки рассчитано на чтение и запись CSV файлов, которые понимает Excel.

Крым
April 16, 2008 #

Спасибо за коды, возможно пригодиться, завтра дам компьютерику на фирме.
А вот нащёт лицензирования вы зря так. ?зучайте если можете создавать подобные доработки!
Напи?у завтра отзыв своего хакера, он вечно чем то недоволен в Excel, может как раз то что ему нужно.

patrick
July 20, 2008 #

я не пойму 2 вещи
зачем делать внутренними классы Writer и Reader
и
зачем делать свои исключения копиями стандартных, да еще и называть их также…а как же повторное использование кода?

ognivo777
August 6, 2008 #

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

October 24, 2008 #

2 patrick & ognivo777:
Вложенные классы для того, чтобы не засорять class-space.
На счет велосипеда - я не встречал простых и удобных в использовании библиотек. ? потом, мне кажется, что чтение и запись CSV это не та задача, для которой нужно подключать целую библиотеку. Намного проще скопировать кусок кода и вмержить в проект.
Собственно для того, чтобы любой человек мог вмержить в свой проект этот код, я его и выкладывал.

Genius
December 18, 2008 #

Спасибо! Ты настоящий мужик!

lonewolf
December 23, 2008 #

Может, луч?ее сделать JDBC драйвер? читающий/пи?ущий в csv?

Igor
December 26, 2008 #

Можете посоветовать, как прочитать правильно файл раз?ифровки разговоров по мобильному телефону. Все номера, которые не в корпоративе, записаны в экспоненциальном виде, замена формата ячеек в Excel результата не дает. Спасибо.

Mike
March 20, 2009 #

Код содержит о?ибку.
Метод unmarkDoubleQuotes класса Csv должен выглядеть так:
private String unmarkDoubleQuotes(String s) { return s.replace(impossibleString, “\”\”"); }

а не так:

private String unmarkDoubleQuotes(String s) { return s.replace(impossibleString, “\”"); }

March 21, 2009 #

Это не о?ибка. Метод unmarkDoubleQuotes приватный и используется в методе для unescape токенов. Может назван визуально он и не совсем правильно, но семантически название подобрано верно.
? потом, все TestCases проходят. А как они могли бы проходить с о?ибками в коде?

Alno
March 25, 2009 #

Не пробовали использовать какие-нибудь уже существующие реализации, например, OpenCSV (http://opencsv.sourceforge.net/)?

Moderated: Вы?е уже описан смысл статьи и объяснено, почему я не использовал готовые реализации.

Eugene
June 24, 2009 #

Привет, не подскаже?ь, с какой версией java ты компилил библиотеку для .csv?

Eugene
June 26, 2009 #

Уже на?ел, спасибо

zlob
April 8, 2010 #

Exception in thread “main” javenue.csv.Csv$FormatException: invalid csv: misplaced quote
at javenue.csv.Csv$Reader.unescape(Csv.java:203)
at javenue.csv.Csv$Reader.readLine(Csv.java:185)

//CSV.java:203
if (result.contains(”\”")) throw new FormatException(”invalid csv: misplaced quote”); // could this ever happen at all?

it happen :)

;”VW T4 2.4D-2.5 Syncro диск 15″” 98->”;

“Сразу хочу обратить ва?е внимание на то, что стандартное поведение библиотеки рассчитано на чтение и запись CSV файлов, которые понимает Excel…”

Excel спокойно справляется с такими строками.

Oleg
May 5, 2010 #

В ва?ей библиотеке есть о?ибка.
Падает на
if (result.contains(”\”")) throw new FormatException(”invalid csv: misplaced quote”); // could this ever happen at all?

Разбираться было лень, взял opencsv

May 8, 2010 #

@zlob & Oleg:
Пробовал на разных данных, в том числе и на тех, которые привел zlob, - воспроизвести не удалось.
Вообще такая о?ибка происходит в случае, если некорректно сформирован CSV-файл, а именно не проэскейплена двойная кавычка.

Первое, что приходит на ум - одна из кавычек не является кавычкой, а просто похожим символом (такое иногда происходит при копировании из Word). В частности тот фрагмент CSV, который привел zlob, содержит 2 разных кавычки (если конечно это не проделки Wordpress’a).

Александра
May 14, 2010 #

Давно хотела сказать боль?е спасибо за статьи! Всегда все так понятно изложено, приятно читать и разбираться. Очень помогает в учебе=)

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong> <pre>