HHeLiBeXの日記 正道編

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

列挙型専用のMap - java.util.EnumMap

Java 5から列挙型(enum)が導入されたのに合わせて、列挙型の値をキーとするEnumMapというクラスが提供されているのを知り、またメモリ効率がよいという記述をどこかで見たので、ちょっと実験。
まず列挙型。

enum A {
    A_1,
    A_2,
    A_3;
}

次にテスト用のクラス。EnumMapを一つ作り、そのコピーを指定した個数だけ生成してリストに格納する。

import java.util.EnumMap;
import java.util.List;
import java.util.Map;

public class EnumMapTester {
    public void test(int n, List<Map<A, String>> list) {
        Map<A, String> m1 = new EnumMap<A, String>(A.class);
        for (A a : A.values()) {
            m1.put(a, a.toString());
        }
        for (int i = 0; i < n; ++i) {
            list.add(new EnumMap<A, String>(m1));
        }
    }
}

もう一つテスト用。比較対象はEnumMapのJavaDocに挙がっているHashMap。処理内容はEnumMapの場合と同じ。

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HashMapTester {
    public void test(int n, List<Map<A, String>> list) {
        Map<A, String> m1 = new HashMap<A, String>(1);
        for (A a : A.values()) {
            m1.put(a, a.toString());
        }
        for (int i = 0; i < n; ++i) {
            list.add(new HashMap<A, String>(m1));
        }
    }
}

これらの処理の呼び出し側。処理の呼び出しの前後でメモリ使用量を標準出力に出力する。

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Main {

    public static void main(String[] args) {
        int n = 100000;

        List<Map<A, String>> list = new ArrayList<Map<A, String>>(n);

        dumpMemoryUsage("before");
        new HashMapTester().test(n, list);
//        new EnumMapTester().test(n, list);
        dumpMemoryUsage("after");
    }

    private static void dumpMemoryUsage(String msg) {
        NumberFormat nf = new DecimalFormat("#.###");
        Runtime rt = Runtime.getRuntime();
        long max = rt.maxMemory();
        long free = rt.freeMemory();
        long total = rt.totalMemory();
        System.out.printf("%-7s: %-7s   %-7s   %-7s   %-7s\n", msg, "max", "total", "free", "used");
        System.out.printf("%-7s: %7sMB %7sMB %7sMB %7sMB\n", "",
                nf.format(max / 1024.0 / 1024),
                nf.format(total / 1024.0 / 1024),
                nf.format(free / 1024.0 / 1024),
                nf.format((total - free) / 1024.0 / 1024));
    }
}

これをHashMap、EnumMapのそれぞれの場合で実行してみると、出力されるメモリ使用量は以下のような感じ。
HashMapの場合。

before : max       total     free      used   
       :  63.562MB   1.938MB   1.273MB   0.665MB
after  : max       total     free      used   
       :  63.562MB  28.004MB   9.100MB  18.904MB

EnumMapの場合。

before : max       total     free      used   
       :  63.562MB   1.938MB   1.273MB   0.665MB
after  : max       total     free      used   
       :  63.562MB   7.777MB   1.092MB   6.685MB

まぁ、100000個もMapを作ることはめったにないだろうけど、明らかな差があるのは確かなようだ。
念のため、EnumMapのソースコードを見てみると、なるほど、値は配列に格納し、java.lang.Enumのordinal()メソッドが0(zero)から(要素数-1)の値を返すことを利用して、それをそのまま配列の添え字に使用している。