크지 않은 json 데이터를 파싱할 때는 한번에 파싱해도 메모리 사용량이 많지 않지만,
파싱해야할 json 데이터가 굉장히 크다면 메모리 사용량이 많기 때문에 주의가 필요한 부분입니다.
참고삼아 제 로컬 PC에서 jackson 라이브러리로 json 파일을 객체로 파싱하는 로직을 실행해서 visualvm으로 확인해본 결과는 다음과 같았습니다.
- 약 300 KB 파일을 한번에 객체로 파싱 : 약 60 MB 힙메모리 사용
- 약 50 MB 파일을 한번에 객체로 파싱 : 약 150 MB 힙메모리 사용
- 약 150 MB 파일을 한번에 객체로 파싱 : 약 750 MB 힙메모리 사용
jackson 사용 예제
streaming api는 json 데이터를 다음과 같은 토큰 단위로 읽어서 처리합니다.
- 객체의 시작( { ), 끝( } )
- 배열의 시작( [ ), 끝( ] )
- 필드명
- 필드값
토큰을 순회하며 원하는 데이터가 나왔을 때 처리를 하는 방식으로 구현하면 됩니다. (이터레이터 처리 방법과 유사)
예를 들어서 설명하면,
<sample.json>
- {
- "contentsId": "123456",
- "title": "꽹과리",
- "metadata": [
- {
- "propertyName": "category",
- "propertyContent": "타악기"
- },
- {
- "propertyName": "category",
- "propertyContent": "동양악기"
- }
- ]
- }
위 데이터를 객체로 파싱하는 코드는 다음과 같이 작성할 수 있습니다.
- import com.fasterxml.jackson.core.JsonFactory;
- import com.fasterxml.jackson.core.JsonParser;
- import com.fasterxml.jackson.core.JsonToken;
- import com.fasterxml.jackson.databind.MappingJsonFactory;
- public class JacksonStream {
- public static void main(String[] args) throws IOException {
- JsonFactory jsonFactory = new MappingJsonFactory();
- JsonParser jsonParser = jsonFactory.createParser(new File("sample.json")); // json 파서 생성
- AudioContent audioContent = new AudioContent(); // 맵핑할 객체
- while (jsonParser.nextToken() != JsonToken.END_OBJECT) { // END_OBJECT(}) 가 나올 때까지 토큰 순회
- String fieldName = jsonParser.getCurrentName(); // 필드명, 필드값 토큰인 경우 필드명, 나머지 토큰은 null 리턴
- if ("contentsId".equals(fieldName)) {
- jsonParser.nextToken(); // 필드값 토큰으로 이동
- audioContent.setContentsId(jsonParser.getText());
- } else if ("title".equals(fieldName)) {
- jsonParser.nextToken();
- audioContent.setTitle(jsonParser.getText());
- } else if ("metadata".equals(fieldName)) {
- // metadata array 요소들 파싱
- while (jsonParser.nextToken() != JsonToken.END_ARRAY) { // END_ARRAY(]) 가 나올 때까지 토큰 순회
- Metadata metadata = parseMetadata(jsonParser);
- audioContent.getMetadata().add(metadata);
- }
- }
- }
- jsonParser.close();
- System.out.println(audioContent);
- }
- private static Metadata parseMetadata(JsonParser jsonParser) throws IOException {
- Metadata metadata = new Metadata();
- while (jsonParser.nextToken() != JsonToken.END_OBJECT) { // END_OBJECT(}) 가 나올 때까지 토큰 순회
- String fieldName = jsonParser.getCurrentName();
- if ("propertyName".equals(fieldName)) {
- jsonParser.nextToken();
- metadata.setPropertyName(jsonParser.getText());
- } else if ("propertyContent".equals(fieldName)) {
- jsonParser.nextToken();
- metadata.setPropertyContent(jsonParser.getText());
- }
- }
- return metadata;
- }
- }
토큰 단위로만 데이터를 읽을 수 있는 것은 아니고 한번에 읽을 수도 있습니다.
metadata 파싱하는 부분을 한번에 처리하는 방식으로 다음과 같이 변경할 수 있습니다.
- private static Metadata parseMetadata(JsonParser jsonParser) throws IOException {
- jsonParser.nextToken();
- return jsonParser.readValueAs(Metadata.class);
- }
하지만 한번에 파싱하는 데이터 범위를 적절히 설정하지 않으면 이 역시 메모리를 많이 사용할 수 있으니
주의가 필요할 것 같습니다.
예를 들어, 위처럼 metadata 배열 중 하나의 항목이 아니라, 전체 metadata 배열을 한번에 파싱하게 했는데
배열에 항목 수가 굉장히 많다던가 하는 경우입니다.
그리고 파싱하는 것뿐만 아니라 객체로 json 데이터를 생성하는 것도 stream api로 처리가 가능합니다.
'개발 > 자바' 카테고리의 다른 글
Effective Java 3/E - 7장 람다와 스트림 요약정리 (0) | 2019.05.19 |
---|---|
JSON 스키마 (0) | 2017.06.30 |
FileLock (0) | 2017.01.11 |
checked exception과 unchecked exception (0) | 2016.07.18 |
Mybatis 쿼리 파라미터에서 jdbcType (0) | 2016.07.08 |