Javenue logo

Javenue

Программирование на Java

Информационные технологии

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

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

Итак, проблема заключалась в том, что генерация отчетов в формате 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 файла по сети. В этом случае можно переконнектиться и начать чтение/запись заново. Если она возникла при других условиях, то тут уже ничего не поделаешь...



Комментариев: 6

  Выйти

  * для публикации комментариев нужно  

Samolisov:

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

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

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

c0nst:

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

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

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

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

Единственное, на счет чего я сомневаюсь - то что для ошибок невалидности формата я использовал тот же Exception, что и для обертки IOException.

Наверное для ошибок формата лучше бросать IllegalStateException с соответствующим текстом.

Ну а IOException я оборачиваю в RuntimeException для того, чтобы пользователь моего класса не матюкался добавляя в своих методах некрасивые конструкции типа throws или try/catch/finally :).

Если программист посчитает нужным ловить Csv.Exception, он напишет соответствующий код. Но ловить ее надо только в случае чтения/записи CSV через сеть, так как только в этом случае можно попробовать корректно решить проблему - переконнектится и дальше совершить чтение/запись.

hos:

Как еще один вариант решения проблемы с пожиранием памяти при формировании Excel-документов в java через POI - это написать свою библиотеку, которая бы формировала файл в формате xlsx, причем делала это не в памяти, а сразу в файловой системе. Видел такое решение (у людей было правда самописное), работало на ура!
Да и xlsx формат к таму же имеет менее жесткие ограничения на количества строк и прочее, нежели xls. Может каму пригодится данная наводка в работе

Артем Вершинин:

Хорошая библиотека, но что то этот код криво с русскими буквами работает, его можно как-нибудь заставить не выдавать кракозябр вместо них?

Александр Бессонов:

Вопрос новичка! Конечно, тупой... А какого типа результат возвращает reader.readLine? Я весь исходник просмотрел, но этого метода там не нашел. И как мне узнать длину файла. Мне этот файл нужно не на System.out вывести, а в двухмерный массив прочитать. Заранее благодарю за ответ, ногами прошу не бить.

kolyaaa pupkin:

Добавь тест ;) Csv.Reader reader = new Csv.Reader(new StringReader("a;\"b\nc;\n\"\"\nd;;\ne\";d\")); assertEquals(Arrays.asList("a", "b\nc;\n\"\nd;;\ne", "d"), reader.readLine());