OpenCSV
http://opencsv.sourceforge.net
OpenCSV는 자바에서 CSV 파일 읽기, 쓰기를 편하게 해주는 API들을 제공하는 라이브러리이다.
여기서는 OpenCsv의 파일 읽기에 대해서 알아보겠다.
import
다음 라이브러리를 추가한다. (Gradle 기준)
implementation 'com.opencsv:opencsv:5.5'
String 배열로 읽기
CSV 파일을 String[] 형식으로 읽어들이는 것이 가능하다.
public static void main(String[] args) throws CsvValidationException, IOException {
CSVReader csvReader = new CSVReader(new FileReader("input.csv"));
String[] line;
while ((line = csvReader.readNext()) != null) {
System.out.println(String.join(",", line));
}
}
<결과>
name,age
John,25
Julie,21
Tom,23
혹은 다음과 같이 모든 데이터를 한 번에 List<String[]> 형태로 읽을 수 있다.
public static void main(String[] args) throws CsvException, IOException {
CSVReader csvReader = new CSVReader(new FileReader("input.csv"));
List<String[]> lines = csvReader.readAll();
lines.forEach(line -> System.out.println(String.join(",", line)));
}
결과는 동일하다.
더 간단하게 할 수 있다. CSVReader는 iterator 기반으로 한 줄씩 읽는 것이 가능하다. Exception도 throws 할 필요 없다.
public static void main(String[] args) throws IOException {
CSVReader csvReader = new CSVReader(new FileReader("input.csv"));
csvReader.forEach(line -> System.out.println(String.join(",", line)));
}
결과는 동일하다.
이 밖에 내가 사용하면서 유용했던 메서드들을 몇 개 더 소개한다.
peek 메서드는 현재 읽고 있는 위치를 다음 줄로 넘어가지 않고 가져올 수 있는 메서드이다. 보통 header를 가져올 때 많이 사용했다.
public static void main(String[] args) throws IOException {
CSVReader csvReader = new CSVReader(new FileReader("input.csv"));
String[] header = csvReader.peek();
System.out.println(String.join(",", header));
}
<결과>
name,age
skip 메서드는 현재 읽고 있는 위치를 인자만큼 skip할 수 있다.
public static void main(String[] args) throws IOException {
CSVReader csvReader = new CSVReader(new FileReader("input.csv"));
String[] header = csvReader.peek();
System.out.println(String.join(",", header));
System.out.println("-----------------");
csvReader.skip(1);
csvReader.forEach(line -> System.out.println(String.join(",", line)));
}
<결과>
name,age
-----------------
John,25
Julie,21
Tom,23
peek()을 사용해 header를 가져오고, 1줄 skip시켜 실제 데이터가 있는 두 번째 줄부터 출력하도록 했다.
header가 필요 없는 경우, 아얘 처음부터 한 줄 스킵해서 CSVReader를 가져올 수 있다.
public static void main(String[] args) throws IOException {
CSVReader csvReader = new CSVReaderBuilder(new FileReader("input.csv"))
.withSkipLines(1)
.build();
csvReader.forEach(line -> System.out.println(String.join(",", line)));
}
<결과>
John,25
Julie,21
Tom,23
객체로 읽기
CSV는 일정한 형식을 가진 데이터이기 때문에 CSV 파일과 대응되는 특정 객체 형태로 변환할 수 있다.
CSV header와 동일한 필드를 갖는 객체가 있다고 하자.
다음과 같이 객체 필드에 @CSVBindByName 애노테이션을 붙여 준다.
@Getter
public class Member {
@CsvBindByName
private String name;
@CsvBindByName
private int age;
}
이제 CsvToBeanBuilder를 사용하면 CSV 파일을 한 번에 List<객체> 형태로 읽을 수 있다.
public static void main(String[] args) throws IOException {
List<Member> members = new CsvToBeanBuilder<Member>(new FileReader("input.csv"))
.withType(Member.class)
.build()
.parse();
members.forEach(member -> System.out.println(member.getName()+", "+member.getAge()));
}
<결과>
John, 25
Julie, 21
Tom, 23
굉장히 편리하다.
만약 header와 필드의 이름이 동일하지 않다면 직접 매칭되는 column 이름을 부여할 수 있다.
@Getter
public class Member {
@CsvBindByName(column = "name")
private String n;
@CsvBindByName(column = "age")
private int a;
}
결과는 동일하다.
만약 CSV 파일 내에 객체에 정의한 필드명이 정의되어 있지 않다면 해당 필드는 타입의 기본값으로 세팅된다.
@Getter
public class Member {
@CsvBindByName
private String name;
@CsvBindByName
private int age;
@CsvBindByName
private String hobby;
}
public static void main(String[] args) throws IOException {
List<Member> members = new CsvToBeanBuilder<Member>(new FileReader("input.csv"))
.withType(Member.class)
.build()
.parse();
members.forEach(member -> System.out.println(member.getName() + ", " + member.getAge() + ", " + member.getHobby()));
}
<결과>
John, 25, null
Julie, 21, null
Tom, 23, null
String 타입이기 때문에 null로 세팅되었다.
만약 header가 존재하지 않는 CSV 파일이라면 값의 순서대로 객체의 필드에 매핑할 수 있다.
@Getter
public class Member {
@CsvBindByPosition(position = 0)
private String name;
@CsvBindByPosition(position = 1)
private int age;
}
그러나 CSV 파일의 순서가 언제 바뀔지 모르기 때문에, 이 방법은 권장하지 않는다. CSV 파일에 header를 추가하자.
+) OpenCsv 공식 매뉴얼에서는 객체의 필드에 @CsvBindByName과 같은 애노테이션을 붙이도록 하고 있지만, 실제로 아무 애노테이션 없이 테스트 해봤는데도, CsvToBeanBuilder가 정상적으로 작동하는 것을 확인했다.
@Getter
public class Member {
private String name;
private int age;
}
public static void main(String[] args) throws IOException {
List<Member> members = new CsvToBeanBuilder<Member>(new FileReader("input.csv"))
.withType(Member.class)
.build()
.parse();
members.forEach(member -> System.out.println(member.getName() + ", " + member.getAge()));
}
<결과>
John, 25
Julie, 21
Tom, 23
명확하게 하기 위해서는 필드에 OpenCsv가 제공하는 애노테이션을 붙이는 것이 좋겠지만, 나는 객체를 단순 CSV 읽기 전용이 아닌 다양한 곳에 활용하는데 은근 OpenCsv 애노테이션이 거슬려서 애노테이션을 제거하고 사용하고 있다.
이 밖에 공식 매뉴얼을 보면 컬렉션 필드 형태로 읽는 등 CSV와 java 코드 간에 다양한 방식으로 읽고 쓰는 방법을 제시하고 있기 때문에 필요에 따라 매뉴얼을 읽으며 코드에 적용시키는 것을 추천한다.
'java > java' 카테고리의 다른 글
[Java] 현재 실행 중인 메서드 이름 가져오기 (2) | 2022.03.12 |
---|---|
[Java] Map - Value 값으로 정렬 (0) | 2022.01.13 |
[Java] 람다식 (0) | 2021.09.28 |
[Java] Mockito (0) | 2021.09.28 |
[Java] JUnit 5 (0) | 2021.09.24 |