Java - Map Compute, To JSON
진행하는 프로젝트에서 static 메모리에 저장하는 Map 객체에 접근하는 로직을 구성해야했다. Map 객체는 아래와 같이 하나의 클래스 내에 정의되어 있다.
private static final Map<Long, Map<Integer, Map<Long, JsonObject>>> MAP = new ConcurrentHashMap<>();
프로젝트에 사용된 것은 여러 번 중첩된 Map 객체였다. 위와 같은 형식을 지닌 Map 에 값을 저장하고, 특정 Key 값을 기반으로 값을 불러오는 로직을 작성해야 했던 것이다. 우선 저장을 구현했다. MAP 내에 무한대로 데이터가 저장되면 메모리가 부족해질 수 있으니 따로 지정해둔 시간을 지나게되면 해당 데이터를 삭제해줄 수 있게끔 한다. (이를 위해 위 MAP 의 가장 바깥 Map 의 Key 를 현재시간으로 저장한다)
long maxAge = MAX_AGE;
synchronized (MAP) {
for (Long inputTime: MAP.keySet()) {
if (inputTime < maxAge) {
MAP.remove(inputTime);
}
}
}
MAP 에 데이터를 추가해줄 때마다, 먼저 위와 같은 코드로 for 반복문을 통해 Map 의 원소들을 삭제해준다.
그 다음으로는 아래와 같은 코드로 MAP 에 데이터를 추가해줄 수 있다.
Map<Integer, Map<Long, JsonObject>> map1 = MAP.compute(System.currentTimeMillis(), (k, v) -> (v == null) ? new ConcurrentHashMap<>() : v);
Map<Long, JsonObject> map2 = map1.compute(key1, (k, v) -> (v == null) ? new ConcurrentHashMap<>() : v);
map2.put(key2, data);
위 두 번의 compute 메서드를 실행하기 전에, MAP 내에 넣을 데이터들의 리스트를 인자로 받아줘야 한다. 그 다음, 우선 첫 번째 compute 에 대해서 살펴본다. Compute 는 주어진 첫 번째 인자, Key 의 Value 에 대해서 어떠한 연산을 진행할지 정의해줄 수 있다. 위 경우에는 현재 시간에 대한 값이 있으면 그 값 그대로 두고, 없을 경우 ( v==null ) 새로운 ConcurrentHashMap 을 put 하는 것이다. 그 다음의 경우도 마찬가지이다. 따로 외부에서 받을 key1 값에 대해서 동일한 compute 메서드를 진행한다. 최종적으로는, 두 번째 compute 메서드로 얻은 가장 내부의 Map 에 마지막 key 값과 value 값을 put 해주는 것이다.
위 경우에는 데이터가 있을 경우, 없을 경우에 대해서 나누어 대응해야 했기에 compute 메서드가 사용되었지만, 한 가지 특정 경우에만 명령을 수행하고 그 외의 경우에는 동작이 필요없다면 compute 말고 다른 메서드를 사용해볼 수 있다. computeIfAbsent() 혹은 computeIfPresent() 를 사용하면 된다. compute() 와 형식은 동일하고, 둘 다 메서드 이름에서 유추할 수 있듯이 뒤에 들어오는 함수가 각각 Key 에 대한 값이 없을 때, 혹은 있을 때만 실행되게끔 하는 메서드인 것이다.
위와 같이 저장을 진행한 MAP 에서 JSON 데이터를 만들어내는 코드는 아래와 같이 작성될 수 있다.
JsonObject jsonOutter = new JsonObject();
JsonObject jsonMiddle = new JsonObject();
JsonObject jsonInner = new JsonObject();
synchronized(MAP) {
for (Entry<Long, Map<Integer, Map<Long, JsonObject>>> entry : MAP.entrySet()) {
long inputTime = entry.getKey();
Map<Integer, Map<Long, JsonObject>> dataMap = entry.getValue();
for (int key1 : keys) {
Map<Long, JsonObject> dataMap2 = dataMap.get(key1);
if (dataMap2 != null) {
for (Entry<Long, JsonObject> entry3 : dataMap2.entrySet()) {
JsonObject data = entry3.getValue();
Long key2 = entry3.getKey();
jsonInner.add(Long.toString(key2), data);
}
jsonMiddle.add(Integer.toString(key1), jsonInner);
}
}
if (!jsonMiddle.entrySet().isEmpty()) {
jsonOutter.add(Long.toString(jobMillis), jsonMiddle);
}
}
}
중첩 반복문을 통해, 각각의 Map 에서 JsonObject 를 만들고 이를 바깥 JsonObject 에 넣어주는 방식으로 JSON 객체를 만들어낸다. (위 경우는 key1 값을 외부에서 리스트로 (keys) 받아 그 값에 해당하는 값들만 검색하는 경우이다) NullPointerException 이 발생하지 않도록 null 에 대한 체크들이 필요했다. 또, 마지막 줄에는 빈 껍데기만 반환되지 않도록 JSON 객체 내부가 비었는지 확인할 수 있는 JsonObject.entrySet().isEmpty() 로 체크해주었다.