April
Чтение и запись CSV файлов в Java (comma separated values file format)
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 файла по сети. В этом случае можно переконнектиться и начать чтение/запись заново. Если она возникла при других условиях, то тут уже ничего не поделаешь…
6 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, может как раз то что ему нужно.