Skip to content

Commit 3e75807

Browse files
committed
add: JCF 자료구조의 초기 용량을 지정하면 좋은 점
1 parent f0e3f51 commit 3e75807

2 files changed

Lines changed: 200 additions & 0 deletions

File tree

documents/.vuepress/const.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ exports.JavaList = [
3838
"Java/자바에서-클래스-정보-알아내는-법.md",
3939
"Java/String-타입-캐스팅과-String.valueOf()-차이.md",
4040
"Java/자바-프로그램이-실행되는-흐름.md",
41+
"Java/JCF-자료구조의-초기-용량을-지정하면-좋은-점.md",
4142
];
4243

4344
exports.OperatingSystemList = [
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# JCF 자료구조의 초기 용량을 지정하면 좋은 점
2+
## JCF란?
3+
* Java Collection Framework
4+
* 다수의 데이터를 쉽고 효과적으로 처리하기 위한 표준화된 방법을 제공하는 클래스의 집합
5+
* JCF 이전에는 사용 목적은 동일해도 각 Collection 마다 사용하는 메서드, 문법, 생성자가 달라 혼동하기 쉬웠음
6+
7+
![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdna%2FvpsPl%2FbtreMDD0tUL%2FAAAAAAAAAAAAAAAAAAAAAOY0W9SN-qHzOM8p1vGHVkmeJj42JNeVkBF66uB9owSB%2Fimg.png%3Fcredential%3DyqXZFxpELC7KVnFOS48ylbz2pIh7yKj8%26expires%3D1767193199%26allow_ip%3D%26allow_referer%3D%26signature%3DAihdRCzUy803ZfNlp4aO0%252BgUx48%253D)
8+
9+
* 이러한 문제를 해결하기 위해 공통의 인터페이스를 설계하였고, 이를 JCF(Java Collections Framework)라 함
10+
### JCF 도입 전
11+
```java
12+
// 배열 생성 - 크기 고정
13+
String[] names = new String[3];
14+
names[0] = "김철수";
15+
names[1] = "이영희";
16+
names[2] = "박민수";
17+
18+
// 요소 접근
19+
String firstName = names[0];
20+
21+
// 크기 확인
22+
int size = names.length;
23+
24+
// 새 요소 추가 불가 - 배열 재생성 필요
25+
String[] newNames = new String[4];
26+
System.arraycopy(names, 0, newNames, 0, 3);
27+
newNames[3] = "최지훈";
28+
29+
// 요소 삭제 불가 - 직접 구현 필요
30+
31+
// ----------
32+
33+
import java.util.Vector;
34+
35+
// Vector 생성
36+
Vector names = new Vector(); // 제네릭 없음
37+
38+
// 요소 추가 - addElement() 메서드
39+
names.addElement("김철수");
40+
names.addElement("이영희");
41+
names.addElement("박민수");
42+
43+
// 요소 접근 - elementAt() 메서드
44+
String firstName = (String) names.elementAt(0); // 타입 캐스팅 필요
45+
46+
// 크기 확인
47+
int size = names.size();
48+
49+
// 요소 삭제 - removeElementAt() 메서드
50+
names.removeElementAt(1);
51+
```
52+
### JCF 도입 후 (JDK 1.2 이후)
53+
```java
54+
import java.util.List;
55+
import java.util.ArrayList;
56+
57+
// List 생성 - 인터페이스 타입으로 선언
58+
List<String> names = new ArrayList<>(); // 제네릭 사용
59+
60+
// 요소 추가 - add() 메서드 (통일된 인터페이스)
61+
names.add("김철수");
62+
names.add("이영희");
63+
names.add("박민수");
64+
65+
// 요소 접근 - get() 메서드
66+
String firstName = names.get(0); // 타입 캐스팅 불필요
67+
68+
// 크기 확인 - size() 메서드 (통일됨)
69+
int size = names.size();
70+
71+
// 요소 삭제 - remove() 메서드 (통일된 인터페이스)
72+
names.remove(1);
73+
74+
// 다른 Collection으로 쉽게 전환 가능
75+
List<String> linkedNames = new LinkedList<>(names);
76+
```
77+
78+
* 도입 후 List 인터페이스를 통해 통일된 메서드 사용 가능 및 제네릭으로 타입 안정성도 확보됨
79+
80+
## JCF에서 초기 용량을 지정하면 좋은 점
81+
* JCF에서 가변 크기의 자료 구조를 사용하는 경우, 초기 용량을 설정하면 리사이징을 줄이고 메모리와 연산 비용을 절약할 수 있음
82+
83+
### 실험 코드
84+
```java
85+
import java.lang.management.ManagementFactory;
86+
import java.lang.management.MemoryMXBean;
87+
import java.lang.management.MemoryUsage;
88+
import java.util.ArrayList;
89+
import java.util.List;
90+
91+
public class Main {
92+
private static final int MAX = 5_000_000;
93+
94+
public static void main(String[] args) {
95+
testWithDefaultCapacity();
96+
System.gc();
97+
try { Thread.sleep(1000); } catch (InterruptedException e) {}
98+
99+
testWithInitialCapacity();
100+
}
101+
102+
private static void testWithDefaultCapacity() {
103+
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
104+
105+
long beforeUsed = getUsedHeap(memoryMXBean);
106+
System.out.println("=== Default Capacity Test ===");
107+
System.out.println("Before: " + beforeUsed + " MB");
108+
109+
long startTime = System.currentTimeMillis();
110+
List<String> arr = new ArrayList<>(); // 기본 capacity = 10
111+
for (int i = 0; i < MAX; i++) {
112+
arr.add("a");
113+
}
114+
long endTime = System.currentTimeMillis();
115+
116+
long afterUsed = getUsedHeap(memoryMXBean);
117+
System.out.println("After: " + afterUsed + " MB");
118+
System.out.println("Memory used: " + (afterUsed - beforeUsed) + " MB");
119+
System.out.println("Time: " + (endTime - startTime) + " ms");
120+
System.out.println("Final capacity: ~6,153,400 (33 resizes)\n");
121+
}
122+
123+
private static void testWithInitialCapacity() {
124+
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
125+
126+
long beforeUsed = getUsedHeap(memoryMXBean);
127+
System.out.println("=== Initial Capacity Test ===");
128+
System.out.println("Before: " + beforeUsed + " MB");
129+
130+
long startTime = System.currentTimeMillis();
131+
List<String> arr = new ArrayList<>(MAX); // 초기 capacity = 5,000,000
132+
for (int i = 0; i < MAX; i++) {
133+
arr.add("a");
134+
}
135+
long endTime = System.currentTimeMillis();
136+
137+
long afterUsed = getUsedHeap(memoryMXBean);
138+
System.out.println("After: " + afterUsed + " MB");
139+
System.out.println("Memory used: " + (afterUsed - beforeUsed) + " MB");
140+
System.out.println("Time: " + (endTime - startTime) + " ms");
141+
System.out.println("Final capacity: 5,000,000 (0 resizes)");
142+
}
143+
144+
private static long getUsedHeap(MemoryMXBean memoryMXBean) {
145+
MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
146+
return heapUsage.getUsed() / 1024 / 1024;
147+
}
148+
}
149+
```
150+
151+
### 결과
152+
```bash
153+
=== Default Capacity Test ===
154+
Before: 3 MB
155+
After: 81 MB
156+
Memory used: 78 MB
157+
Time: 29 ms
158+
Final capacity: ~6,153,400 (33 resizes)
159+
160+
=== Initial Capacity Test ===
161+
Before: 1 MB
162+
After: 21 MB
163+
Memory used: 20 MB
164+
Time: 19 ms
165+
Final capacity: 5,000,000 (0 resizes)
166+
```
167+
168+
### 기본 용량 사용
169+
* 기본 용량(10)으로 시작한 경우, 용량이 가득 차면 기존 크기의 1.5배로 증가함
170+
```java
171+
// ArrayList.java
172+
173+
private Object[] grow(int minCapacity) {
174+
int oldCapacity = elementData.length;
175+
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
176+
int newCapacity = ArraysSupport.newLength(oldCapacity,
177+
minCapacity - oldCapacity, /* minimum growth */
178+
oldCapacity >> 1 /* preferred growth */);
179+
return elementData = Arrays.copyOf(elementData, newCapacity);
180+
} else {
181+
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
182+
}
183+
}
184+
```
185+
* 위와 같이 여러 리사이징이 발생해 최종적으로 약 80MB 사용
186+
187+
### 초기 용량 설정 사용
188+
* 불필요한 리사이징 없이 처음 설정한 크기로 유지되며 약 20MB만 사용함
189+
* 즉 JCF에서 가변 크기의 자료 구조를 사용하는 경우, 초기 용량을 설정하면 리사이징을 줄이고 메모리와 연산 비용 절약 가능
190+
191+
## 로드 팩터와 임계점이란?
192+
* 로드 팩터(load factor)란 특정 크기의 자료 구조에 데이터가 얼마나 적재되었는지를 나타내는 비율
193+
* 임계점(threshold)란 가변적인 크기를 가진 자료구조에서 얼마나 크기를 증가시켜야 하는지를 나타내는 수치
194+
* 로드 팩터와 임계점을 사용하는 이유는 꽉 차기 전에 미리 확장하여 성능 저하를 방지하기 위함
195+
196+
### 예시
197+
* JCF에서 HashMap의 경우에는 내부적으로 배열을 사용하며, 초기 사이즈는 16
198+
* 이때, HashMap의 기준 로드 팩터는 0.75이므로 임계점은 12(capacity * load factor = threshold)이고
199+
* 만약, HashMap 내부 배열의 사이즈가 12를 넘는 경우 내부 배열의 크기를 2배 늘리고, 재해싱을 수행

0 commit comments

Comments
 (0)