반응형
크지 않은 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": "동양악기"
}
]
}
위 데이터를 객체로 파싱하는 코드는 다음과 같이 작성할 수 있습니다.
토큰 단위로만 데이터를 읽을 수 있는 것은 아니고 한번에 읽을 수도 있습니다.
metadata 파싱하는 부분을 한번에 처리하는 방식으로 다음과 같이 변경할 수 있습니다.
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 배열 중 하나의 항목이 아니라, 전체 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 |