Mapに対するiteration
こんなコード:
Map<String, Integer> map = new HashMap<String, Integer>(); ‥ for (String key : map.keySet()) { Integer value = map.get(key); ‥ }
を見るとモヤモヤしてしまう(謎)今日この頃。
そもそも、java.util.Mapに対するiterationには3種類ある。
- keyのみが必要である場合 - Map#keySet()
- valueのみが必要である場合 - Map#values()
- keyとvalueが必要である場合 - Map#entrySet()
これらのケースに応じて適切に使い分けるべきなんだな、と考える。上記のコードはvalueが必要なので、values()に対してiterationすべき。
というだけだと、「コードの意味は同じなんだからいいじゃん」*1と言われかねないので、簡単に計ってみる(何)
import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.HashMap; import java.util.Map; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; public class TestMap { private static NumberFormat nf = new DecimalFormat("0.000"); private static int loopCount = 1000; private static int mapSize = 100000; private static Map<String, Integer> map; private long start; private long end; private String name; @BeforeClass public static void setUpBeforeClass() throws Exception { map = new HashMap<String, Integer>(); for (int i = 0; i < mapSize; ++i) { map.put(String.valueOf(i), Integer.valueOf(i)); } } @AfterClass public static void tearDownAfterClass() throws Exception { } @Before public void setUp() throws Exception { start = System.currentTimeMillis(); } @After public void tearDown() throws Exception { end = System.currentTimeMillis(); System.out.printf("%-16s %7s x %7s %24s\n", name, mapSize, loopCount, nf.format((end - start) / 1000.0) + " sec"); } @Test public void testKeySetAndGet() { name = "keySet & get"; for (int i = 0; i < loopCount; ++i) { for (String key : map.keySet()) { Integer value = map.get(key); } } } @Test public void testKeySet() { name = "keySet"; for (int i = 0; i < loopCount; ++i) { for (String key : map.keySet()) { } } } @Test public void testEntrySet() { name = "entrySet"; for (int i = 0; i < loopCount; ++i) { for (Map.Entry<String, Integer> entry : map.entrySet()) { Integer value = entry.getValue(); } } } @Test public void testValues() { name = "values"; for (int i = 0; i < loopCount; ++i) { for (Integer value : map.values()) { } } } }
そして結果。
mapSize = 100000, loopCount = 1000 の場合:
keySet & get 100000 x 1000 30.031 sec keySet 100000 x 1000 20.969 sec entrySet 100000 x 1000 23.085 sec values 100000 x 1000 20.413 sec
mapSize = 10000, loopCount = 10000 の場合:
keySet & get 10000 x 10000 16.198 sec keySet 10000 x 10000 9.310 sec entrySet 10000 x 10000 11.270 sec values 10000 x 10000 9.202 sec
ま、それなりに差がありそうということで(謎)
*1:厳密に言うと、keySet()とvalues()で順序が異なる可能性がある(?)ので、処理によっては結果が異なる