2019年08月19日(星期一)  农历:己亥年七月十九

作者:三年。分类: JAVA

 在Java中抛异常的性能是非常差的。通常来说,抛一个异常大概会消耗100到1000个时钟节拍。

通常是出现了意想不到的错误,我们才会往外抛异常。也就是说,我们肯定不希望一个进程一秒钟就抛出上千个异常。不过有时候你确实会碰到有些方法 把异常当作事件一样往外抛。我们在这篇文章中已经看到一个这样的典范):sun.misc.BASE64Decoder之所以性能很差就是因为它通过抛异 常来对外请求道,"我还需要更多的数据":

<BR>at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)

<BR>at java.lang.Throwable.fillInStackTrace(Throwable.java:782)

<BR>- locked <0x6c> (a sun.misc.CEStreamExhausted)

<BR>at java.lang.Throwable.<INIT>(Throwable.java:250)

<BR>at java.lang.Exception.<INIT>(Exception.java:54)

<BR>at java.io.IOException.<INIT>(IOException.java:47)

<BR>at sun.misc.CEStreamExhausted.<INIT>(CEStreamExhausted.java:30)

<BR>at sun.misc.BASE64Decoder.decodeAtom(BASE64Decoder.java:117)

<BR>at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:163)

<BR>at sun.misc.CharacterDecoder.decodeBuffer(CharacterDecoder.java:194)

<BR></INIT></INIT></INIT></INIT></0X6C>

如果你用一个数字开头,字母结尾的字符串来运行下这篇文章里面的pack方法,你也会碰到类似的情况。我们来看下用那个方法打包"12345"和"12345a"需要多长的时间:

<BR>Made 100.000.000 iterations for string '12345' : time = 12.109 sec

<BR>Made 1.000.000 iterations for string '12345a' : time = 21.764 sec

<BR>

可以看到,'12345a'迭代的次数要比'12345'少100倍。也就是说这个方法处理'12345a'慢了差不多200倍。大多数的处理时间都在填充异常的栈跟踪信息了:

<BR>at java.lang.Throwable.fillInStackTrace(Throwable.java:-1)

<BR>        at java.lang.Throwable.fillInStackTrace(Throwable.java:782)

<BR>        - locked <0x87> (a java.lang.NumberFormatException)

<BR>        at java.lang.Throwable.<INIT>(Throwable.java:265)

<BR>        at java.lang.Exception.<INIT>(Exception.java:66)

<BR>        at java.lang.RuntimeException.<INIT>(RuntimeException.java:62)

<BR>        at java.lang.IllegalArgumentException.<INIT>(IllegalArgumentException.java:53)

<BR>        at java.lang.NumberFormatException.<INIT>(NumberFormatException.java:55)

<BR>        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

<BR>        at java.lang.Long.parseLong(Long.java:441)

<BR>        at java.lang.Long.valueOf(Long.java:540)

<BR>        at com.mvorontsov.javaperf.StrConvTests.pack(StrConvTests.java:69)

<BR>        at com.mvorontsov.javaperf.StrConvTests.test(StrConvTests.java:38)

<BR>        at com.mvorontsov.javaperf.StrConvTests.main(StrConvTests.java:29)

<BR></INIT></INIT></INIT></INIT></INIT></0X87>

通过手动解析数字,我们可以很容易提升pack方法的性能。不过不要忘了--不到万不得已,不要随便优化。如果你只是解析几个输入参数而已 --keep it simple,就用JDK的方法就好了。如果你要解析大量的消息,又必须调用一个类似pack这样的方法--那确实得去优化一下了。

新的pack方法和旧的实现差不太多--把一个字符串转化成一个尽可能小的Character/Integer/Long/Double/String类型,使得result.toString()。equals(orginalString)为true.

<BR>public static Object strToObject( final String str )

<BR>{

<BR>    if ( str == null || str.length() > 17 )

<BR>    {  //out of Long range

<BR>        return str;

<BR>    }

<BR>    if ( str.equals( "" ) )

<BR>        return ""; //ensure interned string is returned

<BR>    if ( str.length() == 1 )

<BR>        return str.charAt( 0 ); //return Character

<BR>    //if starts with zero - support only "0" and "0.something"

<BR>    if ( str.charAt( 0 ) == '0' )

<BR>    {

<BR>        if ( str.equals( "0" ) )

<BR>            return 0;

<BR>        if ( !str.startsWith( "0." ) )  //this may be a double

<BR>            return str;

<BR>    }

<BR>

<BR>    long res = 0;

<BR>    int sign = 1;

<BR>    for ( int i = 0; i < str.length(); ++i )

<BR>    {

<BR>        final char c = str.charAt( i );

<BR>        if ( c <= '9' && c >= '0' )

<BR>            res = res * 10 + ( c - '0' );

<BR>        else if ( c == '.' )

<BR>        {

<BR>            //too lazy to write a proper Double parser, use JDK one

<BR>            try

<BR>            {

<BR>                final Double val = Double.valueOf( str );

<BR>                //check if value converted back to string equals to an original string

<BR>                final String reverted = val.toString();

<BR>                return reverted.equals( str ) ? val : str;

<BR>            }

<BR>            catch ( NumberFormatException ex )

<BR>            {

<BR>                return str;

<BR>            }

<BR>        }

<BR>        else if ( c == '-' )

<BR>        {

<BR>            if ( i == 0 )

<BR>                sign = -1; //switch sign at first position

<BR>            else

<BR>                return str; //otherwise it is not numeric

<BR>        }

<BR>        else if ( c == '+' )

<BR>        {

<BR>            if ( i == 0 )

<BR>                sign = 1; //sign at first position

<BR>            else

<BR>                return str; //otherwise it is not numeric

<BR>        }

<BR>        else //non-numeric

<BR>            return str;

<BR>    }

<BR>    //cast to int if value is in int range

<BR>    if ( res < Integer.MAX_VALUE )

<BR>        return ( int ) res * sign;

<BR>    //otherwise return Long

<BR>    return res * sign;

<BR>}

<BR>

很惊讶吧,新的方法解析数字比JDK的实现快多了!很大一个原因是因为JDK在解析的最后,调用了一个支持的解析方法,像这样:

public static int parseInt( String s, int radix ) throws NumberFormatException

新的方法和旧的相比(注意方法调用的次数--对于非数字串pack只调用了1百万次,而别的情况能调用到千万级别):

<BR>Pack: Made 100.000.000 iterations for string '12345' : time = 12.145 sec

<BR>Pack: Made 1.000.000 iterations for string '12345a' : time = 23.248 sec

<BR>strToObject: Made 100.000.000 iterations for string '12345' : time = 6.311 sec

<BR>strToObject: Made 100.000.000 iterations for string '12345a' : time = 5.807 sec

<BR>

总结

千万不要把异常当成返回码一样用,或者当作可能发生的事件(尤其是和IO无关的方法)往外抛。抛异常的代价太昂贵了,对于一般的方法,至少要慢百倍以上。

如果你每条数据都需要解析,又经常会出现非数值串的时候,尽量不要用Number子类型的parse*/valueOf这些方法。为了性能考虑,你应当手动解析它们。

温馨提示如有转载或引用以上内容之必要,敬请将本文链接作为出处标注,谢谢合作!

已有 0/1533 人参与

发表评论:



手Q扫描加入Java初学者群