性能问题:将十六进制char转换为Java中的数字值的最快方法?

时间:2008-10-21 06:53:43

标签: java performance algorithm

我想从表示十六进制值(大写或小写)的字符转换为字节,如

'0'->0, '1' -> 1, 'A' -> 10, 'a' -> 10, 'f' -> 15 etc...

我会经常调用这种方法,因此性能很重要。有没有比使用预先初始化的HashMap<Character,Byte>从中获取值的更快的方法?

答案

在使用switch-case和Jon Skeet的直接计算解决方案之间似乎是一个折腾 - 尽管如此,交换机案例解决方案似乎有点微不足道。 Greg的阵列方法胜出。以下是各种方法的200,000,000次运行的性能结果(以ms为单位):

Character.getNumericValue:
8360

Character.digit:
8453

HashMap<Character,Byte>:
15109

Greg's Array Method:
6656

JonSkeet's Direct Method:
7344

Switch:
7281

谢谢你们!

基准方法代码

你好,JonSkeet,你是​​老竞争对手。 ; - )

public class ScratchPad {

    private static final int NUMBER_OF_RUNS = 200000000;

    static byte res;

    static HashMap<Character, Byte> map = new HashMap<Character, Byte>() {{
        put( Character.valueOf( '0' ), Byte.valueOf( (byte )0 ));
        put( Character.valueOf( '1' ), Byte.valueOf( (byte )1 ));
        put( Character.valueOf( '2' ), Byte.valueOf( (byte )2 ));
        put( Character.valueOf( '3' ), Byte.valueOf( (byte )3 ));
        put( Character.valueOf( '4' ), Byte.valueOf( (byte )4 ));
        put( Character.valueOf( '5' ), Byte.valueOf( (byte )5 ));
        put( Character.valueOf( '6' ), Byte.valueOf( (byte )6 ));
        put( Character.valueOf( '7' ), Byte.valueOf( (byte )7 ));
        put( Character.valueOf( '8' ), Byte.valueOf( (byte )8 ));
        put( Character.valueOf( '9' ), Byte.valueOf( (byte )9 ));
        put( Character.valueOf( 'a' ), Byte.valueOf( (byte )10 ));
        put( Character.valueOf( 'b' ), Byte.valueOf( (byte )11 ));
        put( Character.valueOf( 'c' ), Byte.valueOf( (byte )12 ));
        put( Character.valueOf( 'd' ), Byte.valueOf( (byte )13 ));
        put( Character.valueOf( 'e' ), Byte.valueOf( (byte )14 ));
        put( Character.valueOf( 'f' ), Byte.valueOf( (byte )15 ));
        put( Character.valueOf( 'A' ), Byte.valueOf( (byte )10 ));
        put( Character.valueOf( 'B' ), Byte.valueOf( (byte )11 ));
        put( Character.valueOf( 'C' ), Byte.valueOf( (byte )12 ));
        put( Character.valueOf( 'D' ), Byte.valueOf( (byte )13 ));
        put( Character.valueOf( 'E' ), Byte.valueOf( (byte )14 ));
        put( Character.valueOf( 'F' ), Byte.valueOf( (byte )15 ));
    }};
    static int[] charValues = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
                    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10, 11, 12, 13,14,15};
    static char[] cs = new char[]{'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','A','B','C','D','E','F'};

    public static void main(String args[]) throws Exception {
        long time = System.currentTimeMillis();
        for( int i = 0; i < NUMBER_OF_RUNS; i++ ) {
            res = getNumericValue( i );
        }
        System.out.println( "Character.getNumericValue:" );
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        for( int i = 0; i < NUMBER_OF_RUNS; i++ ) {
            res = getDigit( i );
        }
        System.out.println( "Character.digit:" );
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        for( int i = 0; i < NUMBER_OF_RUNS; i++ ) {
            try {
                res = getValueFromArray( i );
            } catch (IllegalArgumentException e) {
            }
        }
        System.out.println( "Array:" );
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        for( int i = 0; i < NUMBER_OF_RUNS; i++ ) {
            res = getValueFromHashMap( i );
        }
        System.out.println( "HashMap<Character,Byte>:" );
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        for( int i = 0; i < NUMBER_OF_RUNS; i++ ) {
            char c = cs[i%cs.length];
            res = getValueFromComputeMethod( c );        
        }
        System.out.println( "JonSkeet's Direct Method:" );
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        for( int i = 0; i < NUMBER_OF_RUNS; i++ ) {
            res = getValueFromSwitch( i );

        }
        System.out.println( "Switch:" );
        System.out.println( System.currentTimeMillis()-time );
    }

    private static byte getValueFromSwitch( int i ) {
        byte res;
        char ch = cs[i%cs.length];
        switch( ch ) {
            case '0':
                res = 0;
                break;
            case '1':
                res = 1;
                break;
            case '2':
                res = 2;
                break;
            case '3':
                res = 3;
                break;
            case '4':
                res = 4;
                break;
            case '5':
                res = 5;
                break;
            case '6':
                res = 6;
                break;
            case '7':
                res = 7;
                break;
            case '8':
                res = 8;
                break;
            case '9':
                res = 9;
                break;
            case 'a':
            case 'A':
                res = 10;
                break;
            case 'b':
            case 'B':    
                res = 11;
                break;
            case 'c':
            case 'C':    
                res = 12;
                break;
            case 'd':
            case 'D':    
                res = 13;
                break;
            case 'e':
            case 'E':    
                res = 14;
                break;
            case 'f':
            case 'F':    
                res = 15;
                break;
            default:
                throw new RuntimeException("unknown hex character: " + ch );
        }
        return res;
    }

    private static byte getValueFromComputeMethod( char c ) {
        byte result = 0;
        if (c >= '0' && c <= '9')
        {
            result =  (byte)(c - '0');
        }
        if (c >= 'a' && c <= 'f')
        {
            result = (byte)(c - 'a' + 10);
        }
        if (c >= 'A' && c <= 'F')
        {
            result =  (byte)(c - 'A' + 10);
        }
        return result;
    }

    private static byte getValueFromHashMap( int i ) {
        return map.get( Character.valueOf( cs[i%cs.length] ) ).byteValue();
    }

    private static byte getValueFromArray( int i ) {
        char c = cs[i%cs.length];
        if (c < '0' || c > 'f') {
            throw new IllegalArgumentException();
        }
        byte result = (byte)charValues[c-'0'];
        if (res < 0) {
            throw new IllegalArgumentException();
        }
        return result;
    }

    private static byte getDigit( int i ) {
        return (byte)Character.digit( cs[i%cs.length], 16 );
    }

    private static byte getNumericValue( int i ) {
        return (byte)Character.getNumericValue( cs[i%cs.length] );
    }

}

13 个答案:

答案 0 :(得分:16)

预初始化的数组比HashMap快。像这样:

int CharValues['f'-'0'+1] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, ... -1, 10, 11, 12, ...};

if (c < '0' || c > 'f') {
    throw new IllegalArgumentException();
}
int n = CharValues[c-'0'];
if (n < 0) {
    throw new IllegalArgumentException();
}
// n contains the digit value

您应该将此方法与其他方法(例如Jon Skeet's直接方法)进行基准测试,以确定哪种方法对您的应用程序来说最快。

答案 1 :(得分:14)

哈希表会比较慢。这很快:

if (c >= '0' && c <= '9')
{
    return c - '0';
}
if (c >= 'a' && c <= 'f')
{
    return c - 'a' + 10;
}
if (c >= 'A' && c <= 'F')
{
    return c - 'A' + 10;
}
throw new IllegalArgumentException();

另一个选择是尝试切换/ case语句。如果数组在缓存中,则阵列可能没问题,但是错过可能很昂贵。

答案 2 :(得分:4)

int CharValues[256] = 
{
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,0,1,2,3,4,5,6,7,8,9,16,16,16,16,16,16,16,
16,10,11,12,13,14,15,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,10,11,12,13,14,15,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16
}

int n = CharValues[c];

if (n == 16)
 throw new IllegalArgumentException();

// n contains the digit value

答案 3 :(得分:4)

我不记得之前看过这种方法,但是Mikko Rantanen在对这个问题的评论中指出了这个等式,Code golf - hex to (raw) binary conversion

(char | 32) % 39 - 9

我不知道它的基准是什么(可能有人可以将它添加到上面的基准测试并运行它,但我猜测%会杀死性能) - 但它是一个简洁的单线程单线程字符十六进制到十进制转换。处理0-9,A-F,a-f。

答案 4 :(得分:3)

使用数组应该是最快的。

数组的大小可以是16,16 ^ 2,16 ^ 3,16 ^ 4等。

将数字转换为大于1的数组可以提高性能。

最有价值的地方,可能是4位数(64k表)。

答案 5 :(得分:2)

Character.getNumericValue(char)是另一种方式:

char c = 'a';
System.out.println(c + "->" + Character.getNumericValue(c));

打印'a-&gt; 10'就像你想要的那样。其他人必须评论静态metod调用与HashMap查找的效率,或者你可以自己检查一下。虽然它看起来更干净/更易读。

答案 6 :(得分:2)

简单,但很慢:

int i = Integer.parseInt(String.ValueOf(c), 16);

更快:

int i = Character.digit(c, 16);

我不会使用任何特殊代码来解决“性能问题”。如果你经常使用它,JIT将创建编译代码并且执行将变得快速。保持代码清洁。你可以尝试编写一个性能测试,比较上面代码和任何特殊实现的执行时间 - 我打赌你不会得到重大的改进。

答案 7 :(得分:2)

我认为你无法击败直接数组查找。

static final int[] precalc = new int['f'+1];
static {
    for (char c='0'; c<='9'; c++) precalc[c] = c-'0';
    for (char c='A'; c<='F'; c++) precalc[c] = c-'A';
    for (char c='a'; c<='f'; c++) precalc[c] = c-'a';
}

System.out.println(precalc['f']);

答案 8 :(得分:2)

这是我对Greg代码的调整版本。在我的盒子上它略微更快 - 但可能在噪音范围内。它避免了下限检查,并且不需要进行任何减法。创建一个64K阵列并避免 绑定检查似乎减慢了速度 - 但是再次,这样的时间几乎不可能确定什么是真实的和什么是噪声。

public class HexParser
{
    private static final byte VALUES = new int['f'];

    // Easier to get right for bozos like me (Jon) than
    // a hard-coded array :)
    static
    {
        for (int i=0; i < VALUES.length; i++)
        {
            VALUES[i] = (byte) -1;
        }
        for (int i='0'; i <= '9'; i++)
        {
            VALUES[i] = (byte) i-'0';
        }
        for (int i='A'; i <= 'F'; i++)
        {
            VALUES[i] = (byte) (i-'A'+10);
        }
        for (int i='a'; i <= 'f'; i++)
        {
            VALUES[i] = (byte) (i-'a'+10);
        }
    }

    public static byte parseHexChar(char c)
    {
        if (c > 'f')
        {
            throw new IllegalArgumentException();
        }
        byte ret = VALUES[c];
        if (ret == -1)
        {
            throw new IllegalArgumentException();
        }
        return ret;
    }
}

答案 9 :(得分:2)

值得注意的是,在大多数测试中,您正在计算%运算的时间。此操作所需的时间与其他一些选项大致相同。

private static byte lookUpTest(int i) {
    return (byte) cs[i%cs.length];
}

答案 10 :(得分:2)

老兄,我是微控制器程序员,在这个小小的世界里,速度真的很重要。将'ASCII'数字转换为字节(从'A'到0x0A)的最快方法是使用这一小段代码

c|=0x20;
return c<='9'? c+0xD0 : c+0xA9;

这个命令的神奇之处很简单,如果你看一下ascii表,你会发现以0x3_开头的数字,以及分别在第0x4_和0x6_列的字母。 1)如果您使用0x20“或”传入的字符(变量“c”),您将永远不会更改数字...但对于字母,您将转换为大写字母。因为我们只寻找A..F(a..f)值...我不关心别人。 2)现在的魔力。为了避免减法,我使用加法运算符。 0xD0与-0x30相同,0xA9与-'a'+ 10相同;

比较生成的分支指令非常简单,并没有占用表查找的开销,也没有“mod”或其他运算符!

享受......

答案 11 :(得分:1)

一个16位值的表,您可以一次查找两个数字,应该比其他答案中建议的8位值数组更快。您将以两倍的速度迭代数据,经常访问数组的一半,访问短路而不是字节,所有这些都使CPU不那么做,而且更多地使用它。

对于x[Integer.toHexString(i).charAt[0]] = i,8位值将预先计算为0 <= i < 256。然后,您只需查找十六进制字符串中每个字符x[c]的{​​{1}}。您可能希望复制每个十六进制值的大写和小写变体的条目。

32位值的表格应该更快,具体取决于您可以承受多少空间。

答案 12 :(得分:1)

根据我的时间安排,以下内容要比Jon Skeet已经非常有效的方法要好。

该想法是利用对范围'A'..'F'的测试来也丢弃范围'0'..'9'或'a'.. f'中的一个。

public static int hex2Dig2(char c) {
    if (c < 'A') {
        if (c >= '0' && c <= '9')
            return c - '0';
    } else if (c > 'F') {
        if (c >= 'a' && c <= 'f')
            return c - 'a' + 10;
    } else {
        return c - 'A' + 10;
    }
    return -1; // or throw exception
}