在java中抛異常的性能是非常差的。通常來說,抛一個異常大概會消耗100到1000個時鐘節拍。
通常是出現了意想不到的錯誤,我們才會往外抛異常。也就是說,我們肯定不希望一個程序一秒鐘就抛出上千個異常。不過有時候你确實會碰到有些方法把異常當作事件一樣往外抛。我們在這篇文章中已經看到一個這樣的典範):sun.misc.base64decoder之是以性能很差就是因為它通過抛異常來對外請求道,”我還需要更多的資料“:
1
2
3
4
5
6
7
8
9
10
<code>at java.lang.throwable.fillinstacktrace(throwable.java:-1)</code>
<code>at java.lang.throwable.fillinstacktrace(throwable.java:782)</code>
<code>- locked <0x6c> (a sun.misc.cestreamexhausted)</code>
<code>at java.lang.throwable.<init>(throwable.java:250)</code>
<code>at java.lang.exception.<init>(exception.java:54)</code>
<code>at java.io.ioexception.<init>(ioexception.java:47)</code>
<code>at sun.misc.cestreamexhausted.<init>(cestreamexhausted.java:30)</code>
<code>at sun.misc.base64decoder.decodeatom(base64decoder.java:117)</code>
<code>at sun.misc.characterdecoder.decodebuffer(characterdecoder.java:163)</code>
<code>at sun.misc.characterdecoder.decodebuffer(characterdecoder.java:194)</code>
如果你用一個數字開頭,字母結尾的字元串來運作下這篇文章裡面的pack方法,你也會碰到類似的情況。我們來看下用那個方法打包"12345"和"12345a"需要多長的時間:
<code>made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345'</code> <code>: time = 12.109 sec</code>
<code>made 1.000.000 iterations</code><code>for</code> <code>string</code><code>'12345a'</code> <code>: time = 21.764 sec</code>
可以看到,’12345a'疊代的次數要比‘12345’少100倍。也就是說這個方法處理'12345a'慢了差不多200倍。大多數的處理時間都在填充異常的棧跟蹤資訊了:
11
12
13
14
<code> </code><code>at java.lang.throwable.fillinstacktrace(throwable.java:782)</code>
<code> </code><code>- locked <0x87> (a java.lang.numberformatexception)</code>
<code> </code><code>at java.lang.throwable.<init>(throwable.java:265)</code>
<code> </code><code>at java.lang.exception.<init>(exception.java:66)</code>
<code> </code><code>at java.lang.runtimeexception.<init>(runtimeexception.java:62)</code>
<code> </code><code>at java.lang.illegalargumentexception.<init>(illegalargumentexception.java:53)</code>
<code> </code><code>at java.lang.numberformatexception.<init>(numberformatexception.java:55)</code>
<code> </code><code>at java.lang.numberformatexception.forinputstring(numberformatexception.java:65)</code>
<code> </code><code>at java.lang.long.parselong(long.java:441)</code>
<code> </code><code>at java.lang.long.valueof(long.java:540)</code>
<code> </code><code>at com.mvorontsov.javaperf.strconvtests.pack(strconvtests.java:69)</code>
<code> </code><code>at com.mvorontsov.javaperf.strconvtests.test(strconvtests.java:38)</code>
<code> </code><code>at com.mvorontsov.javaperf.strconvtests.main(strconvtests.java:29)</code>
通過手動解析數字,我們可以很容易提升pack方法的性能。不過不要忘了——不到萬不得已,不要随便優化。如果你隻是解析幾個輸入參數而已—— keep it simple,就用jdk的方法就好了。如果你要解析大量的消息,又必須調用一個類似pack這樣的方法——那确實得去優化一下了。
新的pack方法和舊的實作差不太多——把一個字元串轉化成一個盡可能小的character/integer/long/double/string類型,使得result.tostring().equals(orginalstring)為true。
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<code>public static object strtoobject( final string str )</code>
<code>{</code>
<code> </code><code>if</code> <code>( str ==</code><code>null</code> <code>|| str.length() > 17 )</code>
<code> </code><code>{ </code><code>//out of long range</code>
<code> </code><code>return</code> <code>str;</code>
<code> </code><code>}</code>
<code> </code><code>if</code> <code>( str.equals(</code><code>""</code> <code>) )</code>
<code> </code><code>return</code> <code>""</code><code>;</code><code>//ensure interned string is returned</code>
<code> </code><code>if</code> <code>( str.length() == 1 )</code>
<code> </code><code>return</code> <code>str.charat( 0 );</code><code>//return character</code>
<code> </code><code>//if starts with zero - support only "0" and "0.something"</code>
<code> </code><code>if</code> <code>( str.charat( 0 ) ==</code><code>'0'</code> <code>)</code>
<code> </code><code>{</code>
<code> </code><code>if</code> <code>( str.equals(</code><code>"0"</code> <code>) )</code>
<code> </code><code>return</code> <code>0;</code>
<code> </code><code>if</code> <code>( !str.startswith(</code><code>"0."</code> <code>) ) </code><code>//this may be a double</code>
<code> </code><code>return</code> <code>str;</code>
<code> </code>
<code> </code><code>long res = 0;</code>
<code> </code><code>int sign = 1;</code>
<code> </code><code>for</code> <code>( int i = 0; i < str.length(); ++i )</code>
<code> </code><code>final char c = str.charat( i );</code>
<code> </code><code>if</code> <code>( c <=</code><code>'9'</code> <code>&& c >=</code><code>'0'</code> <code>)</code>
<code> </code><code>res = res * 10 + ( c -</code><code>'0'</code> <code>);</code>
<code> </code><code>else</code> <code>if</code> <code>( c ==</code><code>'.'</code> <code>)</code>
<code> </code><code>{</code>
<code> </code><code>//too lazy to write a proper double parser, use jdk one</code>
<code> </code><code>try</code>
<code> </code><code>{</code>
<code> </code><code>final double val = double.valueof( str );</code>
<code> </code><code>//check if value converted back to string equals to an original string</code>
<code> </code><code>final string reverted = val.tostring();</code>
<code> </code><code>return</code> <code>reverted.equals( str ) ? val : str;</code>
<code> </code><code>}</code>
<code> </code><code>catch</code> <code>( numberformatexception ex )</code>
<code> </code><code>return</code> <code>str;</code>
<code> </code><code>}</code>
<code> </code><code>else</code> <code>if</code> <code>( c ==</code><code>'-'</code> <code>)</code>
<code> </code><code>if</code> <code>( i == 0 )</code>
<code> </code><code>sign = -1;</code><code>//switch sign at first position</code>
<code> </code><code>else</code>
<code> </code><code>return</code> <code>str;</code><code>//otherwise it is not numeric</code>
<code> </code><code>else</code> <code>if</code> <code>( c ==</code><code>'+'</code> <code>)</code>
<code> </code><code>sign = 1;</code><code>//sign at first position</code>
<code> </code><code>else</code> <code>//non-numeric</code>
<code> </code><code>//cast to int if value is in int range</code>
<code> </code><code>if</code> <code>( res < integer.max_value )</code>
<code> </code><code>return</code> <code>( int ) res * sign;</code>
<code> </code><code>//otherwise return long</code>
<code> </code><code>return</code> <code>res * sign;</code>
<code>}</code>
很驚訝吧,新的方法解析數字比jdk的實作快多了!很大一個原因是因為jdk在解析的最後,調用了一個支援的解析方法,像這樣:
public static int parseint( string s, int radix ) throws numberformatexception
新的方法和舊的相比(注意方法調用的次數——對于非數字串pack隻調用了1百萬次,而别的情況能調用到千萬級别):
<code>pack: made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345'</code> <code>: time = 12.145 sec</code>
<code>pack: made 1.000.000 iterations</code><code>for</code> <code>string</code><code>'12345a'</code> <code>: time = 23.248 sec</code>
<code>strtoobject: made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345'</code> <code>: time = 6.311 sec</code>
<code>strtoobject: made 100.000.000 iterations</code><code>for</code> <code>string</code><code>'12345a'</code> <code>: time = 5.807 sec</code>
千萬不要把異常當成傳回碼一樣用,或者當作可能發生的事件(尤其是和io無關的方法)往外抛。抛異常的代價太昂貴了,對于一般的方法,至少要慢百倍以上。
如果你每條資料都需要解析,又經常會出現非數值串的時候,盡量不要用number子類型的parse*/valueof這些方法。為了性能考慮,你應當手動解析它們。
特别說明:尊重作者的勞動成果,轉載請注明出處哦~~~http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt276