今天和大家一起测试下各种常见 Map 类型在不同操作场景下的性能。
制定一个测试模板类,通过泛型兼容各种 Map 类型;定义抽象方法 test() ,用于执行各项测试操作,返回值为测试所花费的时间。
public abstract class MapTest<T extends Map> { private T map; public abstract long test(); public MapTest(T map) { this.map = map; } public T getMap() { return map; } public void setMap(T map) { this.map = map; } }
再准备一个用于执行测试方法的工具类 MapPerformanceTest ,将后续待各项测试方法编写在其中,工具类提供构造测试数据的 getMap() 方法和打印测试信息的 displayHead() 方法。
public class MapPerformanceTest { /** * 获取指定长度的map * * @param map 容器 * @param size 容器长度 * @param <T> * @return */ public static <T extends Map> T getMap(T map, int size) { //清空遗留数据 map.clear(); for (int i = 0; i < size; i++) { map.put(i, i); } return map; } /** * 打印测试测试 * * @param t 容器 * @param methodName 测试项目 * @param <T> */ public static <T> void displayHead(T t, String methodName) { System.out.println(); System.out.println(t.getClass().getSimpleName() + "\t" + methodName); System.out.println("容器长度\t操作次数\t平均时间"); } }
/** * 添加测试 * * @param map 容器 * @param size 容器长度 * @param <T> */ public static <T extends Map> void put(T map, int size) { MapTest<T> mapTest = new MapTest<T>(map) { @Override public long test() { long startTime = System.nanoTime(); //复用公共方法 MapPerformanceTest.getMap(getMap(), size); long endTime = System.nanoTime(); return endTime - startTime; } }; //操作时间 long operateTime = mapTest.test(); //操作次数 int count = size; //平均操作时间 long avg = operateTime / count; System.out.println(size + "\t" + count + "\t" + avg); } public static void main(String[] args) { Map[] maps = {new TreeMap<Integer, Integer>(), new HashMap(), new Hashtable(), new LinkedHashMap(), new IdentityHashMap(), new WeakHashMap()}; for (Map map : maps) { displayHead(map, "put"); //使用移位操作符计算操作次数,增强代码可读性 put(map, 10 << 10); put(map, 10 << 12); put(map, 10 << 14); } }
TreeMap put 容器长度 操作次数 平均时间 10240 10240 791 40960 40960 290 163840 163840 141 HashMap put 容器长度 操作次数 平均时间 10240 10240 201 40960 40960 103 163840 163840 58 Hashtable put 容器长度 操作次数 平均时间 10240 10240 228 40960 40960 138 163840 163840 66 LinkedHashMap put 容器长度 操作次数 平均时间 10240 10240 275 40960 40960 133 163840 163840 243 IdentityHashMap put 容器长度 操作次数 平均时间 10240 10240 253 40960 40960 216 163840 163840 105 WeakHashMap put 容器长度 操作次数 平均时间 10240 10240 305 40960 40960 81 163840 163840 57
/** * 获取测试 * @param map 容器 * @param loops 循环次数 * @param size 容器长度 * @param <T> */ public static <T extends Map> void get(T map, int loops, int size) { MapTest<T> mapTest = new MapTest<T>(map) { @Override public long test() { long startTime = System.nanoTime(); //指定随机因子,保证每次运行获得的随机数都是一致的 Random random = new Random(47); for (int i = 0; i < loops; i++) { map.get(random.nextInt(size)); } long endTime = System.nanoTime(); return endTime - startTime; } }; //操作时间 long operateTime = mapTest.test(); //操作次数 int count = loops; //平均操作时间 long avg = operateTime / count; System.out.println(size + "\t" + count + "\t" + avg); } public static void main(String[] args) { Map[] maps = {new TreeMap<Integer, Integer>(), new HashMap(), new Hashtable(), new LinkedHashMap(), new IdentityHashMap(), new WeakHashMap()}; Integer[][] testParams = {{10 << 7, 10 << 10}, {10 << 8, 10 << 12}, {10 << 9, 10 << 14}}; for (Map map : maps) { displayHead(map, "get"); for (int i = 0; i < testParams.length; i++) { int loops = testParams[i][0]; int size = testParams[i][1]; map = getMap(map, size); get(map, loops, size); } } }
TreeMap get 容器长度 操作次数 平均时间 10240 1280 1468 40960 2560 521 163840 5120 841 HashMap get 容器长度 操作次数 平均时间 10240 1280 387 40960 2560 163 163840 5120 238 Hashtable get 容器长度 操作次数 平均时间 10240 1280 188 40960 2560 114 163840 5120 288 LinkedHashMap get 容器长度 操作次数 平均时间 10240 1280 173 40960 2560 265 163840 5120 303 IdentityHashMap get 容器长度 操作次数 平均时间 10240 1280 357 40960 2560 140 163840 5120 146 WeakHashMap get 容器长度 操作次数 平均时间 10240 1280 343 40960 2560 168 163840 5120 200
/** * 移除测试 * * @param map 容器 * @param loops 循环次数 * @param size 容器长度 * @param <T> */ public static <T extends Map> void remove(T map, int loops, int size) { MapTest<T> mapTest = new MapTest<T>(map) { @Override public long test() { long startTime = System.nanoTime(); //指定随机因子,保证每次运行获得的随机数都是一致的 Random random = new Random(47); for (int i = 0; i < loops; i++) { map.remove(random.nextInt(size - loops)); } long endTime = System.nanoTime(); return endTime - startTime; } }; //操作时间 long operateTime = mapTest.test(); //操作次数 int count = loops; //平均操作时间 long avg = operateTime / count; System.out.println(size + "\t" + count + "\t" + avg); } public static void main(String[] args) { Map[] maps = {new TreeMap<Integer, Integer>(), new HashMap(), new Hashtable(), new LinkedHashMap(), new IdentityHashMap(), new WeakHashMap()}; Integer[][] testParams = {{10 << 7, 10 << 10}, {10 << 8, 10 << 12}, {10 << 9, 10 << 14}}; for (Map map : maps) { displayHead(map, "remove"); for (int i = 0; i < testParams.length; i++) { int loops = testParams[i][0]; int size = testParams[i][1]; map = getMap(map, size); remove(map, loops, size); } } }
TreeMap remove 容器长度 操作次数 平均时间 10240 1280 3142 40960 2560 745 163840 5120 1172 HashMap remove 容器长度 操作次数 平均时间 10240 1280 366 40960 2560 206 163840 5120 335 Hashtable remove 容器长度 操作次数 平均时间 10240 1280 241 40960 2560 144 163840 5120 294 LinkedHashMap remove 容器长度 操作次数 平均时间 10240 1280 518 40960 2560 163 163840 5120 302 IdentityHashMap remove 容器长度 操作次数 平均时间 10240 1280 323 40960 2560 150 163840 5120 151 WeakHashMap remove 容器长度 操作次数 平均时间 10240 1280 432 40960 2560 157 163840 5120 217
/** * 遍历测试 * * @param map 容器 * @param size 容器长度 * @param <T> */ public static <T extends Map> void iter(T map, int size) { MapTest<T> mapTest = new MapTest<T>(map) { @Override public long test() { long startTime = System.nanoTime(); Set keySet = map.keySet(); for (Object key : keySet) { map.get(key); } long endTime = System.nanoTime(); return endTime - startTime; } }; //操作时间 long operateTime = mapTest.test(); //操作次数 int count = size; //平均操作时间 long avg = operateTime / count; System.out.println(size + "\t" + count + "\t" + avg); } public static void main(String[] args) { Map[] maps = {new TreeMap<Integer, Integer>(), new HashMap(), new Hashtable(), new LinkedHashMap(), new IdentityHashMap(), new WeakHashMap()}; Integer[] testParams = {10 << 10, 10 << 12, 10 << 14}; for (Map map : maps) { displayHead(map, "iter"); for (int i = 0; i < testParams.length; i++) { int size = testParams[i]; map = getMap(map, size); iter(map, size); } } }
TreeMap iter 容器长度 操作次数 平均时间 10240 10240 415 40960 40960 166 163840 163840 89 HashMap iter 容器长度 操作次数 平均时间 10240 10240 259 40960 40960 41 163840 163840 20 Hashtable iter 容器长度 操作次数 平均时间 10240 10240 276 40960 40960 99 163840 163840 28 LinkedHashMap iter 容器长度 操作次数 平均时间 10240 10240 159 40960 40960 11 163840 163840 15 IdentityHashMap iter 容器长度 操作次数 平均时间 10240 10240 178 40960 40960 38 163840 163840 66 WeakHashMap iter 容器长度 操作次数 平均时间 10240 10240 225 40960 40960 58 163840 163840 50
性能分别按高、中、低表示:
测试项目 | TreeMap | HashMap | Hashtable | LinkedHashMap | IdentityHashMap | WeakHashMap |
---|---|---|---|---|---|---|
添加 | 中 | 高 | 高 | 低 | 中 | 高 |
获取 | 低 | 中 | 中 | 中 | 高 | 中 |
移除 | 低 | 中 | 中 | 中 | 高 | 高 |
遍历 | 低 | 高 | 高 | 高 | 中 | 中 |
小结
1.Map 中需要频繁添加时,优先选择 HashMap 或者 Hashtable ,Hashtable 中所有的操作都是同步的,所以它是线程安全的,而 HaspMap 不是,故 HaspMap 的性能略强于 Hashtable,再不考虑线程安全的前提下,更推荐使用 HashMap ;WeakHashMap 虽然添加性能高,但其存储的 key 都是弱引用,当 key 值的引用被置为 null时,会自动被垃圾回收,一般用于做缓存,大家可以根据需求酌情选择;
2.在随机获取方面,IdentityHashMap 的性能表现最为优异,但其实根据 “==” 来判断 key 值是否重复,而一般的 Map 都是通过 equals() 和 hashCode() 来判断;
3.在随机移除方面,IdentityHashMap 和 WeakHashMap 的性能最高;
4.HashMap、Hashtable、LinkedHashMap 在遍历方面性能都很优异,如果你对插入顺序有要求,建议使用底层为链表结构的 LinkedHashMap ,其他两个无法保证插入顺序;
5.TreeMap 虽然各方面性能都不佳,但它可以控制 key 的排序,这是其他 Map 类无法替代的;
6.HashMap 一般是我们的首选,各方面性能都不错,如果有特定业务场景再考虑使用其他 Map;
若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!