PriorityQueue를 적재적소에 사용하자

Posted by epicdev Archive : 2012. 3. 27. 21:07

데이터가 정렬된 상태로 컨테이너에 들어있어야 할 경우에는 PriorityQueue를 자주 사용한다.

PriorityQueue는 Comparable 인터페이스를 구현하는 클래스(boolean compareTo를 구현하여 priority를 매김)를 데이터로 갖거나

생성자에서 따로 Comparator를 받아서 어떻게 priority를 매길지 정할 수 있다.

나같은 경우에는 평소에 데이터들의 순차적 access가 필요 할 때 PrioirtyQueue를 자주 사용한다.

하지만 얼마전에 PriorityQueue를 남용한 잘못을 저질렀다.


PriorityQueue가 사용되기에 적절한 곳은 (내 생각)

1. 컨테이너 안의 데이터가 priority에 따라 사용 순서가 결정

2. 컨테이너의 내용이 자주 바뀜 (poll과 add가 교차적으로 빈번하게 발생함)

3. 데이터를 재사용 할 일이 별로 없음


그런데 나같은 경우는 1번의 경우만 생각하고 2, 3번의 경우는 생각하지 않아서 문제가 발생했다.

어떤 raw 데이터를 통째로 읽어와서 PriorityQueue에 저장을 해놓고, PriorityQueue에서 데이터를 순차적으로 뽑아서, 데이터들 마다 "어떤 처리"를 한 다음 다시 파일로 쓰는 작업이었다.

여기서 나는 2번째 내용을 위반하였다.

나는 그냥 raw 데이터를 읽어오는 족족 PriorityQueue에다가 넣었는데, 이는 "오로지" 나중에 순차적으로 데이터를 뽑아 쓰려고 이렇게 하였다 (1번 이유).

그런데 이 경우 2번의 경우처럼 poll과 add가 교차적으로 빈번하게 발생하지 않는다.

즉, add가 한꺼번에 연속적으로 전부 발생하고나서, poll을 계속 하면서 데이터들을 처리한다.

따라서 이 경우에는 그냥 ArrayList에다가 데이터를 전부 add한 다음 그냥 Collections.sort로 ArrayList를 정렬해서 사용하면 그만이다.

(add와 poll의 교차수행이 빈번할 경우에 ArrayList보다 PriorityQueue가 좋은 점: ArrayList를 사용할 경우 데이터를 add하게되면 ArrayList의 "정렬됨"이라는 상태가 깨지기 때문에, valid한 ordered ArrayList를 유지하려면, add를 할때마다 매번 정렬을 해주어야 한다 (혹은, poll을 요청하기 전까지 add만 하다가 poll 요청이 들어오면 정렬을 해도 된다. 하지만 교차수행이 빈번한 경우에는 매번 정렬을 해야 할 수도 있다). 물론 ordered ArrayList는 정렬된 상태이므로, ArrayList를 traverse한 다음 적절한 위치에다가 add를 해주는 것도 가능하나, 이 또한 ArrayList의 특성상 비효율적일 수가 있다.)


사실 위의 경우에서는 PriorityQueue를 사용하거나 ArrayList와 Collections.sort를 사용하거나 별다른 차이가 없다.

왜냐하면 "재사용"이 없기 때문이다. 하지만 나는 재사용이 필요하였다 (3번 조건 위반).

만약 PriorityQueue의 데이터를 하나씩 뽑아와서 while (!pq.isEmpty()) 처리한다음 파일로 써버리면

PriorityQueue에 남아있는 내용이 없기 때문에, 만약 raw 데이터의 사용이 다시 필요하다면, 파일에서 또 읽어들여야 했다.

(PriorityQueue는 정렬된 list 상태가 아니라 heap 상태라서, iterator로 PriorityQueue를 traverse하게 되면 priority순으로 데이터가 traverse되지 않는다.

priority순으로 PriorityQueue의 데이터를 access하는 방법은 오로지 poll밖에 없다!)

문제는 이 파일이 용량이 꽤나 커서 읽어들이는데 10초정도의 시간이 소요된다는 것이었다.


따라서 내가 겪은 케이스에는 PriorityQueue를 사용하는것 보다 그냥 ArrayList와 Collections.sort를 사용하는 것이 낫다.


결론

1. 도구는 적재적소에 사용해야 한다.

2. 떄론 없어보이는 도구일지라도, 있어보이는 도구보다 나을때도 있다 (특히나 있어보이는 도구가 오로지 특정 상황에서만 최고의 성능을 발휘할 때).

  
출처: http://www.javaworld.com/javaworld/javaqa/2001-06/03-qa-0622-vector.html

QVector or ArrayList -- which is better and why?

ASometimes Vector is better; sometimes ArrayList is better; sometimes you don't want to use either. I hope you weren't looking for an easy answer because the answer depends upon what you are doing. There are four factors to consider:

  • API
  • Synchronization
  • Data growth
  • Usage patterns


Let's explore each in turn.

API

In The Java Programming Language (Addison-Wesley, June 2000) Ken Arnold, James Gosling, and David Holmes describe the Vectoras an analog to the ArrayList. So, from an API perspective, the two classes are very similar. However, there are still some major differences between the two classes.

Synchronization

Vectors are synchronized. Any method that touches the Vector's contents is thread safe. ArrayList, on the other hand, is unsynchronized, making them, therefore, not thread safe. With that difference in mind, using synchronization will incur a performance hit. So if you don't need a thread-safe collection, use the ArrayList. Why pay the price of synchronization unnecessarily?

Data growth

Internally, both the ArrayList and Vector hold onto their contents using an Array. You need to keep this fact in mind while using either in your programs. When you insert an element into an ArrayList or a Vector, the object will need to expand its internal array if it runs out of room. A Vector defaults to doubling the size of its array, while the ArrayList increases its array size by 50 percent. Depending on how you use these classes, you could end up taking a large performance hit while adding new elements. It's always best to set the object's initial capacity to the largest capacity that your program will need. By carefully setting the capacity, you can avoid paying the penalty needed to resize the internal array later. If you don't know how much data you'll have, but you do know the rate at which it grows, Vector does possess a slight advantage since you can set the increment value.

Usage patterns

Both the ArrayList and Vector are good for retrieving elements from a specific position in the container or for adding and removing elements from the end of the container. All of these operations can be performed in constant time -- O(1). However, adding and removing elements from any other position proves more expensive -- linear to be exact: O(n-i), where n is the number of elements and i is the index of the element added or removed. These operations are more expensive because you have to shift all elements at index i and higher over by one element. So what does this all mean?

It means that if you want to index elements or add and remove elements at the end of the array, use either a Vector or anArrayList. If you want to do anything else to the contents, go find yourself another container class. For example, theLinkedList can add or remove an element at any position in constant time -- O(1). However, indexing an element is a bit slower -- O(i) where i is the index of the element. Traversing an ArrayList is also easier since you can simply use an index instead of having to create an iterator. The LinkedList also creates an internal object for each element inserted. So you have to be aware of the extra garbage being created.

 Finally, in "PRAXIS 41" from Practical Java (Addison-Wesley, Feb. 2000) Peter Haggar suggests that you use a plain old array in place of either Vector or ArrayList -- especially for performance-critical code. By using an array you can avoid synchronization, extra method calls, and suboptimal resizing. You just pay the cost of extra development time.

Read more about Core Java in JavaWorld's Core Java section.

  
 «이전 1  다음»