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 файла по сети. В этом случае можно переконнектиться и начать чтение/запись заново. Если она возникла при других условиях, то тут уже ничего не поделае?ь…
21 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).
Давно хотела сказать боль?е спасибо за статьи! Всегда все так понятно изложено, приятно читать и разбираться. Очень помогает в учебе=)