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 пришлось порыться в интернете. Вот список ресурсов, которые я анализировал:
- http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
- http://tools.ietf.org/html/rfc4180
- http://en.wikipedia.org/wiki/Comma-separated_values
- http://www.csvreader.com/csv_format.php
Сразу хочу обратить ваше внимание на то, что стандартное поведение библиотеки рассчитано на чтение и запись 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 файла по сети. В этом случае можно переконнектиться и начать чтение/запись заново. Если она возникла при других условиях, то тут уже ничего не поделаешь…
23 Comments »
RSS feed for comments on this post.
Скачал библиотеку, посмотрел исходник. Код понятный, читабельный, не понял только зачем заводить свой эксеп?н да еще наследуя его от RuntimeException.
З.Ы. Понравился файл с лицензией ) ?мхо можно было бы выбрать что-нибудь стандартное вроде BSDL. Впрочем ва? код - ва?е право.
З.Ы.Ы. Спасибо за библиотеку.
Спасибо боль?ое за комментарий. Если будете ее где-нибудь использовать, напи?ите, что было удобно, что нет.
На счет лицензирования: К сожалению эту область я еще не изучал.
На счет Exception: Мне боль?е нравится использовать unchecked exceptions, но только в том случае, если ситуация действительно исключительная и не может быть обработана пользователем класса в боль?инстве случаев.
В случае с невалидным CSV пользователь ничего не может сделать: точнее при записи в CSV он должен переписать свой неправильный код, а при чтении - отказаться от парсинга невалидного файла.
Единственное, на счет чего я сомневаюсь - то что для о?ибок невалидности формата я использовал тот же Exception, что и для обертки IOException.
Наверное для о?ибок формата луч?е бросать IllegalStateException с соответствующим текстом.
Ну а IOException я оборачиваю в RuntimeException для того, чтобы пользователь моего класса не матюкался добавляя в своих методах некрасивые конструкции типа throws или try/catch/finally :).
Если программист посчитает нужным ловить Csv.Exception, он напи?ет соответствующий код. Но ловить ее надо только в случае чтения/записи CSV через сеть, так как только в этом случае можно попробовать корректно ре?ить проблему - переконнектится и даль?е совер?ить чтение/запись.
Хм… я сторонник несколько иной стратегии использования исключений. Хотя может быть я не понимаю всего смысла. Предположим что возникнет рантайм-эксеп?н в обычном однопоточном приложении. Оно не прибьется?
Кстати согласен с вами что для о?ибки парсера надо использовать другое исключение. Все-таки чтение файла и парсинг - разные операции. По поводу использования IllegalStateException - я думаю луч?е написать свои типы сообщений, их и пробрасывать. Код использующий библиотеку будет читабельнее.
Здравствуйте! А подскажите, пожалуйста, если я все 4 параметра подставлю по умолчанию, получится Excel или Pure CSV?
Стандартное поведение библиотеки рассчитано на чтение и запись CSV файлов, которые понимает Excel.
Спасибо за коды, возможно пригодиться, завтра дам компьютерику на фирме.
А вот нащёт лицензирования вы зря так. ?зучайте если можете создавать подобные доработки!
Напи?у завтра отзыв своего хакера, он вечно чем то недоволен в Excel, может как раз то что ему нужно.
я не пойму 2 вещи
зачем делать внутренними классы Writer и Reader
и
зачем делать свои исключения копиями стандартных, да еще и называть их также…а как же повторное использование кода?
Во первых непонятно зачем велосипед изобретать.. Есть довольно много готовых CSV библиотек.
patrick:
Про внутренние класы согласен.
Про исключения просто - необходимо, если хочется снаружи ловить отдельно прочие исключения и исключения именно этой библиотеки.
2 patrick & ognivo777:
Вложенные классы для того, чтобы не засорять class-space.
На счет велосипеда - я не встречал простых и удобных в использовании библиотек. ? потом, мне кажется, что чтение и запись CSV это не та задача, для которой нужно подключать целую библиотеку. Намного проще скопировать кусок кода и вмержить в проект.
Собственно для того, чтобы любой человек мог вмержить в свой проект этот код, я его и выкладывал.
Спасибо! Ты настоящий мужик!
Может, луч?ее сделать JDBC драйвер? читающий/пи?ущий в csv?
Можете посоветовать, как прочитать правильно файл раз?ифровки разговоров по мобильному телефону. Все номера, которые не в корпоративе, записаны в экспоненциальном виде, замена формата ячеек в Excel результата не дает. Спасибо.
Код содержит о?ибку.
Метод unmarkDoubleQuotes класса Csv должен выглядеть так:
private String unmarkDoubleQuotes(String s) { return s.replace(impossibleString, “\”\”"); }
а не так:
private String unmarkDoubleQuotes(String s) { return s.replace(impossibleString, “\”"); }
Это не о?ибка. Метод unmarkDoubleQuotes приватный и используется в методе для unescape токенов. Может назван визуально он и не совсем правильно, но семантически название подобрано верно.
? потом, все TestCases проходят. А как они могли бы проходить с о?ибками в коде?
Не пробовали использовать какие-нибудь уже существующие реализации, например, OpenCSV (http://opencsv.sourceforge.net/)?
Moderated: Вы?е уже описан смысл статьи и объяснено, почему я не использовал готовые реализации.
Привет, не подскаже?ь, с какой версией java ты компилил библиотеку для .csv?
Уже на?ел, спасибо
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 спокойно справляется с такими строками.
В ва?ей библиотеке есть о?ибка.
Падает на
if (result.contains(”\”")) throw new FormatException(”invalid csv: misplaced quote”); // could this ever happen at all?
Разбираться было лень, взял opencsv
@zlob & Oleg:
Пробовал на разных данных, в том числе и на тех, которые привел zlob, - воспроизвести не удалось.
Вообще такая о?ибка происходит в случае, если некорректно сформирован CSV-файл, а именно не проэскейплена двойная кавычка.
Первое, что приходит на ум - одна из кавычек не является кавычкой, а просто похожим символом (такое иногда происходит при копировании из Word). В частности тот фрагмент CSV, который привел zlob, содержит 2 разных кавычки (если конечно это не проделки Wordpress’a).
Давно хотела сказать боль?е спасибо за статьи! Всегда все так понятно изложено, приятно читать и разбираться. Очень помогает в учебе=)
Подскажите, пожалуйста, как можно скачать Вашу библиотеку?
Как еще один вариант решения проблемы с пожиранием памяти при формировании Excel-документов в java через POI - это написать свою библиотеку, которая бы формировала файл в формате xlsx, причем делала это не в памяти, а сразу в файловой системе. Видел такое решение (у людей было правда самописное), работало на ура!
Да и xlsx формат к таму же имеет менее жесткие ограничения на количества строк и прочее, нежели xls.
Может каму пригодится данная наводка в работе