자바에서는 List를 컬렉션으로 제공한다. 알고리즘 문제를 풀 때 list자료구조를 많이 사용하는데 매번 list에 데이터를 담을 일이 생겼다. 매번 List를 초기화 해야하는데 new는 cost가 많이 드니까 clear를 사용해볼까? 하는 생각에 clear를 사용했는데 문득 clear는 어떤방식으로 list를 비우는지 궁금했다. 그래서 구현체를 들여다 보았다.
List의 두가지 구현체인 ArrayList와 LinkedList이다.
//ArrayList
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
//LinkedList
/**
* Removes all of the elements from this list.
* The list will be empty after this call returns.
*/
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
두가지 구현체를 보면 결국 처음부터 끝까지 순회하며 null로 채우는 방식이다.
List의 길이가 길어지면 clear가 더 느릴 것 같긴한데라는 그저 그런 생각이 들긴했다.
실제로 속도면에서 어떤게 빠를까? 라는 생각에 간단하게 테스트 해보았다.
List에 각각 데이터를 삽입하고 10^3개,10^5개,10^7개로 비교해보았다.
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ClearTest {
private List<Integer> clearArrayList = new ArrayList<>();
private List<Integer> clearLinkedList = new LinkedList<>();
private List<Integer> newArrayList = new ArrayList<>();
private List<Integer> newLinkedList = new LinkedList<>();
ClearTest(int size){
for(int i=0;i<size;i++) {
clearArrayList.add(i);
clearLinkedList.add(i);
newArrayList.add(i);
newLinkedList.add(i);
}
}
public long getClearTime(List<Integer> list) {
long startTime = System.currentTimeMillis();
list.clear();
long endTime = System.currentTimeMillis();
return endTime-startTime;
}
public long getNewArrayListTime(List<Integer> list) {
long startTime = System.currentTimeMillis();
list = new ArrayList<>();
long endTime = System.currentTimeMillis();
return endTime-startTime;
}
public long getNewLinkedListTime(List<Integer> list) {
long startTime = System.currentTimeMillis();
list = new LinkedList<>();
long endTime = System.currentTimeMillis();
return endTime-startTime;
}
public static void main(String[] args) {
int size = 1000;
ClearTest test = new ClearTest(size);
System.out.println("size가 "+size+"일때 clear를 사용하였을 때 ArrayList "+ test.getClearTime(test.clearArrayList)+"ms");
System.out.println("size가 "+size+"일때 clear를 사용하였을 때 LinkedList"+ test.getClearTime(test.clearLinkedList)+"ms");
System.out.println("size가 "+size+"일때 new를 사용하였을 때 ArrayList "+ test.getNewArrayListTime(test.newArrayList)+"ms");
System.out.println("size가 "+size+"일때 new를 사용하였을 때 LinkedList "+ test.getNewLinkedListTime(test.newLinkedList)+"ms");
System.out.println();
size = 100000;
test = new ClearTest(size);
System.out.println("size가 "+size+"일때 clear를 사용하였을 때 ArrayList "+ test.getClearTime(test.clearArrayList)+"ms");
System.out.println("size가 "+size+"일때 clear를 사용하였을 때 LinkedList "+ test.getClearTime(test.clearLinkedList)+"ms");
System.out.println("size가 "+size+"일때 new를 사용하였을 때 ArrayList "+ test.getNewArrayListTime(test.newArrayList)+"ms");
System.out.println("size가 "+size+"일때 new를 사용하였을 때 LinkedList "+ test.getNewLinkedListTime(test.newLinkedList)+"ms");
System.out.println();
size = 10000000;
test = new ClearTest(size);
System.out.println("size가 "+size+"일때 clear를 사용하였을 때 ArrayList "+ test.getClearTime(test.clearArrayList)+"ms");
System.out.println("size가 "+size+"일때 clear를 사용하였을 때 LinkedList "+ test.getClearTime(test.clearLinkedList)+"ms");
System.out.println("size가 "+size+"일때 new를 사용하였을 때 ArrayList "+ test.getNewArrayListTime(test.newArrayList)+"ms");
System.out.println("size가 "+size+"일때 new를 사용하였을 때 LinkedList "+ test.getNewLinkedListTime(test.newLinkedList)+"ms");
System.out.println();
}
}
아래는 실행결과이다.

정말 간단한 테스트라 절대적일수는 없지만 대충 결과를 보면 이렇다.
size가 작을때는 별차이가 없지만 배열의 크기가 커지면 생각보다 많은 시간 차이가 난다. 그렇기 때문에 list의 size가 클때는 new연산으로 날려버리는게 시간적으로 이득일 수 있을 것 같다.
그럼 GC에 영향이…..있을까요