업무를 하던 중 이중 for문을 쓰면 해결될 일이 생겼습니다...
하지만 이중 for문을 정말 쓰기 싫었고 개선할 수 있는 무언가 아쉬운 포인트들이 자꾸 눈에 보였습니다...
그래서 for문을 안쓰고 해결할 방법을 찾으려고 노력했고 노력한 끝에 Map을 사용해서 해결해보았습니다.
다만, 메모리나 속도 측면에서 아직 디테일한 체크는 못해봐서 오히려 손해인 로직일 수 있습니다. 이런 방법도 있구나 이렇게 봐주시면 좋을 것 같습니다!!
상황
- A서버에서 B서버에 데이터를 요청합니다.
- B서버에서는 데이터의 이름은 모르고 id값만 알고 있습니다.
- 따라서, B서버에서 받은 데이터의 id값을 기준으로 A서버에서 데이터의 이름을 넣어줍니다.
public void test(Long id){
// 특정 id를 기준으로 A서버에서 데이터를 뽑아냄
List<data> datas = dataRepository.findAllById(id);
// 뽑아낸 데이터에서 id만 set형태로 뽑아냄
Set<Long> ids = datas.stream().map(data -> data.getId()).collect(Collectors.toSet());
// B서버와 통신
List<bServerData> bServerDatas = bserverSenderService(ids);
// B서버에서 받은 데이터에 A서버에서 이름 넣기 (forEach안에 forEach 존재)
bServerDatas.stream().forEach(bDatas -> {
datas.forEach(aDatas -> {
if(aDatas.getId() == bDatas.getId()){
bDatas.updateName(aDatas.getName());
}
})
})
}
위의 코드처럼 forEach안에 forEach가 또 존재하는 코드가 생겼습니다.
이번에는 제가 생각한 방식으로 코드를 개선해 보겠습니다!
public void test(Long id){
// 특정 id를 기준으로 A서버에서 데이터를 뽑아냄
List<data> datas = dataRepository.findAllById(id);
// 뽑아낸 데이터에서 id만 set형태로 뽑아냄, map을 id를 key name을 value로 만들어보겠습니다.
Map<Long, String> kIdVNameMap = new HashMap<>();
Set<Long> ids = datas.stream().map(data -> {
kIdVNameMap.put(data.getId(), data.getName())
return data.getId()
}).collect(Collectors.toSet());
// B서버와 통신
List<bServerData> bServerDatas = bserverSenderService(ids);
// B서버에서 받은 데이터에 A서버에서 이름 넣기 (Map을 이용해서 이름 update)
bServerDatas.stream().forEach(bDatas -> {
bDatas.updateName(kIdVNameMap.get(bDatas.getId()));
})
}
위의 코드처럼 for문을 한번만 사용하고 내부에서 map을 이용해서 이름을 update하는 로직을 만들었습니다.
아직 성능에 대해서는 테스트 하지 못했기 때문에 이런 방법으로 이중 for문을 안 쓸수도 있구나 생각해주시면 감사하겠습니다!
업그레이드!!!!
추가된 내용입니다!! 이번에는 저의 이론 및 생각이 맞는지 체크를 해보겠습니다.
우선, 코드는 돌아가게만 만들었다는 점... 양해부탁드립니다.
테스트 시나리오~
- TestBox에는 id와 name 모두 값을 채웁니다.
- TestResponse에는 id값만 채웁니다.
- map 혹은 이중 for문을 이용해서 TestResponse의 name을 채웁니다
- 이제 로직을 실행시키기 전과 후에 시간을 측정하고 전과 후의 시간을 뺍니다.
- 마지막으로 그 시간을 비교하면 됩니다.
// TestBox 코드
package maptest;
public class TestBox {
private int id;
private String name;
public static TestBox of(int id, String name) {
TestBox instance = new TestBox();
instance.id = id;
instance.name = name;
return instance;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
// TestResponse 코드
package maptest;
public class TestResponse {
private int id;
private String name;
public static TestResponse of(int id, String name) {
TestResponse instance = new TestResponse();
instance.id = id;
instance.name = name;
return instance;
}
public void updateName(String name) {
this.name = name;
}
public int getId() {
return id;
}
}
package maptest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestLoginService {
// map 테스트
public void mapTest(int count) {
List<TestBox> testBoxes = new ArrayList<>();
List<TestResponse> testResponses = new ArrayList<>();
Map<Integer, String> map = new HashMap<>();
createTestCase(testBoxes, testResponses, map, count);
testResponses.forEach(testResponse -> {
testResponse.updateName(map.get(testResponse.getId()));
});
}
// for 테스트
public void forTest(int count) {
List<TestBox> testBoxes = new ArrayList<>();
List<TestResponse> testResponses = new ArrayList<>();
Map<Integer, String> map = new HashMap<>();
createTestCase(testBoxes, testResponses, map, count);
for(TestResponse testResponse : testResponses) {
for(TestBox testBox : testBoxes) {
if(testResponse.getId() == testBox.getId()) {
testResponse.updateName(testBox.getName());
}
}
}
}
// 공통 로직
private void createTestCase(List<TestBox> testBoxes, List<TestResponse> testResponses, Map<Integer, String> map, int count) {
for (int i = 0; i < count; i++) {
String name = "이름" + i;
testBoxes.add(TestBox.of(i, name));
map.put(i, name);
testResponses.add(TestResponse.of(i, null));
}
}
}
import maptest.TestLoginService;
public class Main {
public static void main(String[] args) {
TestLoginService testLoginService = new TestLoginService();
long start = System.currentTimeMillis();
// 원하는 테스트에 맞춰 주석 처리~
testLoginService.mapTest(100);
testLoginService.forTest(100);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
위의 일련의 코드들이 지나갔는데요~
이제는 테스트 결과를 보시겠습니다.
밑 i, 오른쪽 테스트 종류 | map | for |
100 | 5 | 6 |
1000 | 8 | 15 |
10000 | 13 | 184 |
100000 | 36 | 22313 |
와우... 이게 시간이 지날수록 격차가 많이 커지는 것을 확인할 수 있었습니다...
역시 이중 for문의 시간 복잡도는 O(n^2)인 만큼 데이터가 많아질 수록 시간이 오래 걸리네요... ㅠㅠ
반면, map에서 데이터를 가져오는 시간 복잡도는 O(logN)인 만큼 데이터가 많을 때 확실히 효과적으로 계산이 가능합니다.
데이터가 정말 많은 경우에는 이중 for문은 최대한 피해야겠습니다!!
'자바' 카테고리의 다른 글
[자바] 문자열리스트안에 특정 문자열 제거 후 맨 마지막으로 이동시키기 (1) | 2023.05.08 |
---|---|
자바가상머신 (JVM) (0) | 2021.08.13 |
자바 8 stream API (0) | 2021.07.24 |
자바 8 stream 기본 (0) | 2021.07.23 |
인터페이스 기본 메소드(Default Methods) (0) | 2021.07.16 |
댓글