任何傻瓜都能写出计算机可以理解的代码, 但只有优秀的程序眼才能写出人类可以理解的代码 — Martin Fowler
但是总有渴望编写高性能代码的程序员存在吧,让我们来看看如何编写运行更快的Java代码吧!
注意:JVM对代码进行了有效的优化。因此,您不需要针对一般用例对其进行优化。但是,如果您想让JVM发挥最大性能。我们开始吧。
所有测试用例都在Macbook Pro 2015的Java12 HotSpot(TM) 64-Bit Server VM上运行
如果你的 Collections
只初始化一次,则最好集合构造器中对它的值进行初始化,而不是实例化集合后使用 addall
或 add
方法设置值.
// Slower :walking:♂️ Set<String> set = new HashSet<>(); set.addAll(Arrays.asList("one", "two", "three")); // Faster :rocket: Set<String> set = new HashSet<>(Arrays.asList("one", "two", "three"));
让我们使用JMH基准测试来验证一下
结果的单位是每秒操作的次数(op/s),数值越大,性能越高
@State(Scope.Thread) public static class MyState { @Setup(Level.Trial) public void doSetup() { var arr = new Integer[100000]; for (var i = 0; i < 100000; i++) { arr[i] = i; } list = Arrays.asList(arr); } public List list; } // Faster :rocket: > ~148,344 op/s @Benchmark public HashSet usingConstructor() { var set = new HashSet<>(list); return set; } // Slower :walking:♂️ > ~112,061 op/s @Benchmark public HashSet usingAddAll() { var set = new HashSet<>(); set.addAll(list); return set; }
construtor
版本比 addall
版本提供~36000 op/s
addAll
比 add
更快
同样的, addAll
比 add
每秒提供更高的操作次数,所以下次当你向数组中添加的时,一定要先把它们收集起来,然后用 addAll
添加.
// Slower :walking:♂️ ~116116op/s @Benchmark public ArrayList<Integer> usingAdd() { var a = new int[1000]; for (var i = 0; i < 1000; i++) { a[i] = i; } var arr = new ArrayList<Integer>(); for (var i = 0; i < 1000; i++) { arr.add(a[i]); } return arr; } // Faster :rocket: ~299130 op/s @Benchmark public ArrayList<Integer> usingAddAll() { var a = new Integer[1000]; for (var i = 0; i < 1000; i++) { a[i] = i; } var arr = new ArrayList<Integer>(); arr.addAll(Arrays.asList(a)); return arr; }
addall的速度几乎是add版本的两倍。
EntrySet
,不要再使用 KeySet
// Slower :walking:♂️ ~37000 op/s @Benchmark public HashMap<Integer, Integer> keySetIteration(Blackhole blackhole) { var someMap = new HashMap<Integer, Integer>(); for (var i = 0; i < 1000; i++) { someMap.put(i, i); } var sum = 0; for(Integer i: someMap.keySet()) { sum += i; sum += someMap.get(i); } blackhole.consume(sum); return someMap; } // Faster :rocket: ~45000 op/s @Benchmark public HashMap<Integer, Integer> entrySetIteration(Blackhole blackhole) { var someMap = new HashMap<Integer, Integer>(); for (var i = 0; i < 1000; i++) { someMap.put(i, i); } var sum = 0; for(Map.Entry<Integer, Integer> e: someMap.entrySet()) { sum += e.getKey(); sum += e.getValue(); } blackhole.consume(sum); return someMap; }
EntrySet
在一秒钟内可以运行9000个操作,远超它的变种 KeySet
SingleList
代替只有单个元素的数组 // Faster :rocket: var list = Collections.singletonList("S"); // Slower :walking:♂️ var list = new ArrayList(Arrays.asList("S"));
EnumSet
代替 Hashset
, EnumSet
更快 // Faster :rocket: public enum Color { RED, YELLOW, GREEN } var colors = EnumSet.allOf(Color.class); // Slower :walking:♂️ var colors = new HashSet<>(Arrays.asList(Color.values()));
关于更多 EnumSet
看 这里
// Faster :rocket: var i = 0 ; i += addSomeNumber(); i -= minusSomeNumber(); return i; // Slower :walking:♂️ var i = 0 ; var j = addSomeNumber(); var k = minusSomeNumber(); var l = i + j - k; return l;
String.isEmpty()
方法来检查字符串是否为空
因为 String
是一个 byte[]
, isEmpty
方法只是检查数组的长度,所以更快
public boolean isEmpty() { return value.length == 0; }
// Faster :rocket: var r = 'R' ; var g = 'G' ; var b = 'B' ; // Slower :walking:♂️ var r = "R" ; var g = "G" ; var b = "B" ;
// Faster :rocket: StringBuilder str = new StringBuilder(); str.append("A"); str.append("B"); str.append("C"); str.append("D"); str.append("E"); .... // Slower :walking:♂️ var str = ""; str += "A"; str += "B"; str += "C"; str += "D"; str += "E"; ....
特别当你需要连接字符串的时候,使用 StringBuilder
比 +
更快