HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

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()で順序が異なる可能性がある(?)ので、処理によっては結果が異なる