Чтение и запись файлов CSV в Kotlin с помощью Apache Commons

Введение В этой статье мы рассмотрим, как читать и записывать файлы CSV в Kotlin, в частности, с помощью Apache Commons. Зависимость Apache Commons Поскольку мы работаем с внешней библиотекой, давайте продолжим и импортируем ее в наш проект Kotlin. Если вы используете Maven, просто включите зависимость commons-csv:<dependency><groupId> org.apache.commons</groupId><artifactId> Commons-CSV</artifactId><version> 1.5</version></dependency> Или, если вы используете Gradle: impl

Вступление

В этой статье мы рассмотрим, как читать и записывать файлы CSV в Kotlin , в частности, с помощью Apache Commons.

Зависимость Apache Commons

Поскольку мы работаем с внешней библиотекой, давайте продолжим и импортируем ее в наш проект Kotlin. Если вы используете Maven, просто включите зависимость commons-csv

 <dependency> 
 <groupId>org.apache.commons</groupId> 
 <artifactId>commons-csv</artifactId> 
 <version>1.5</version> 
 </dependency> 

Или, если вы используете Gradle:

 implementation 'org.apache.commons:commons-csv:1.5' 

Наконец, добавив библиотеку в наш проект, давайте определим CSV-файл, который мы собираемся прочитать - students.csv :

 101,John,Smith,90 
 203,Mary,Jane,88 
 309,John,Wayne,96 

Он будет расположен в /resources/students.csv .

Кроме того, поскольку мы будем считывать эти записи в пользовательские объекты, давайте создадим класс данных:

 data class Student ( 
 val studentId: Int, 
 val firstName: String, 
 val lastName: String, 
 val score: Int 
 ) 

Чтение файла CSV в Kotlin

Давайте сначала прочитаем этот файл с помощью BufferedReader , который принимает Path к ресурсу, который мы хотим прочитать:

 val bufferedReader = new BufferedReader(Paths.get("/resources/students.csv")); 

Затем, как только мы прочитали файл в буфер, мы можем использовать сам буфер для инициализации экземпляра CSVParser

 val csvParser = CSVParser(bufferedReader, CSVFormat.DEFAULT); 

Учитывая, насколько изменчивым может быть формат CSV - чтобы CSVFormat догадок, вам нужно будет указать CSVFormat при инициализации синтаксического анализатора. Этот синтаксический анализатор, инициализированный таким образом, может использоваться только для этого формата CSV.

CSVFormat.DEFAULT примеру формата CSV и используем разделитель по умолчанию, запятую ( , ) - мы передадим CSVFormat.DEFAULT в качестве второго аргумента.

Теперь CSVParser - это Iterable , содержащий экземпляры CSVRecord Каждая строка - это запись CSV. Естественно, затем мы можем csvParser экземпляр csvParser и извлечь из него записи:

 for (csvRecord in csvParser) { 
 val studentId = csvRecord.get(0); 
 val studentName = csvRecord.get(1); 
 val studentLastName = csvRecord.get(2); 
 var studentScore = csvRecord.get(3); 
 println(Student(studentId, studentName, studentLastName, studentScore)); 
 } 

Для каждой CSVRecord вы можете получить соответствующие ячейки, используя метод get() и передав индекс ячейки, начиная с 0 . Затем мы можем просто использовать их в конструкторе нашего класса данных Student

Этот код приводит к:

 Student(studentId=101, firstName=John, lastName=Smith, score=90) 
 Student(studentId=203, firstName=Mary, lastName=Jane, score=88) 
 Student(studentId=309, firstName=John, lastName=Wayne, score=96) 

Хотя такой подход не очень хорош. Нам нужно знать порядок столбцов, а также количество столбцов для использования get() , а изменение чего-либо в структуре файла CSV полностью нарушает наш код.

Чтение CSV-файла с заголовками в Kotlin

Разумно знать, какие столбцы существуют , но немного меньше, в каком порядке они расположены.

Обычно файлы CSV имеют строку заголовка, в которой указываются имена столбцов, такие как StudentID , FirstName и т. Д. При CSVParser экземпляра CSVParser, следуя шаблону проектирования Builder , мы можем указать, имеет ли файл, который мы читаем, строку заголовка. или нет, в CSVFormat .

По умолчанию CSVFormat предполагает, что файл не имеет заголовка. Давайте сначала добавим строку заголовка в наш CSV-файл:

 StudentID,FirstName,LastName,Score 
 101,John,Smith,90 
 203,Mary,Jane,88 
 309,John,Wayne,96 

Теперь давайте инициализируем CSVParser и попутно установим несколько дополнительных параметров в CSVFormat :

 val bufferedReader = new BufferedReader(Paths.get("/resources/students.csv")); 
 
 val csvParser = CSVParser(bufferedReader, CSVFormat.DEFAULT 
 .withFirstRecordAsHeader() 
 .withIgnoreHeaderCase() 
 .withTrim()); 

Таким образом, первая запись (строка) в файле будет рассматриваться как строка заголовка, а значения в этой строке будут использоваться как имена столбцов.

Мы также указали, что регистр заголовка не имеет большого значения для нас, превращая формат в нечувствительный к регистру.

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

 CSVFormat.DEFAULT 
 .withDelimiter(',') 
 .withQuote('"') 
 .withRecordSeparator("\r\n") 

Они используются, если вы хотите изменить поведение по умолчанию, например установить новый разделитель, указать, как обрабатывать кавычки, поскольку они часто могут нарушить логику синтаксического анализа и указать разделитель записей, присутствующий в конце каждой записи.

Наконец, как только мы загрузили файл и проанализировали его с этими настройками, вы можете получить CSVRecord как было показано ранее:

 for (csvRecord in csvParser) { 
 val studentId = csvRecord.get("StudentId"); 
 val studentName = csvRecord.get("FirstName); 
 val studentLastName = csvRecord.get("LastName); 
 var studentScore = csvRecord.get("Score); 
 println(Student(studentId, studentName, studentLastName, studentScore)); 
 } 

Это гораздо более щадящий подход, поскольку нам не нужно знать порядок самих столбцов. Даже если они будут изменены в любой момент, CSVParser о нас.

Выполнение этого кода также приводит к:

 Student(studentId=101, firstName=John, lastName=Smith, score=90) 
 Student(studentId=203, firstName=Mary, lastName=Jane, score=88) 
 Student(studentId=309, firstName=John, lastName=Wayne, score=96) 

Написание файла CSV на Kotlin

Подобно чтению файлов, мы также можем писать файлы CSV с помощью Apache Commons. На этот раз мы будем использовать CSVPrinter .

Как CSVReader принимает BufferedReader , CSVPrinter принимает BufferedWriter и CSVFormat мы хотели бы использовать при записи файла.

Давайте создадим BufferedWriter и CSVPrinter экземпляр CSVPrinter:

 val writer = new BufferedWriter(Paths.get("/resources/students.csv")); 
 
 val csvPrinter = CSVPrinter(writer, CSVFormat.DEFAULT 
 .withHeader("StudentID", "FirstName", "LastName", "Score")); 

Метод printRecord() CSVPrinter используется для записи записей. Он принимает все значения для этой записи и распечатывает их в новой строке. Вызов метода снова и снова позволяет нам писать много записей. Вы можете указать каждое значение в списке или просто передать список данных.

Нет необходимости использовать метод printRecord() для самой строки заголовка, поскольку мы уже указали его с помощью withHeader() CSVFormat . Без указания заголовка нам пришлось бы распечатать первую строку вручную.

В общем, csvPrinter можно использовать следующим образом:

 csvPrinter.printRecord("123", "Jane Maggie", "100"); 
 csvPrinter.flush(); 
 csvPrinter.close(); 

Не забудьте flush() и close() принтер после использования.

Поскольку здесь мы работаем со списком студентов, и мы не можем просто распечатать запись таким образом, мы printRecord() метод:

 val students = listOf( 
 Student(101, "John", "Smith", 90), 
 Student(203, "Mary", "Jane", 88), 
 Student(309, "John", "Wayne", 96) 
 ); 
 
 for (student in students) { 
 val studentData = Arrays.asList( 
 student.studentId, 
 student.firstName, 
 student.lastName, 
 student.score) 
 
 csvPrinter.printRecord(studentData); 
 } 
 csvPrinter.flush(); 
 csvPrinter.close(); 

В результате создается файл CSV, содержащий:

 StudentID,FirstName,LastName,Score 
 101,John,Smith,90 
 203,Mary,Jane,88 
 309,John,Wayne,96 

Заключение

В этом руководстве мы рассмотрели, как читать и записывать файлы CSV в Kotlin, используя библиотеку Apache Commons.

comments powered by Disqus