性能问题:将hexchar转换为Java中的数字值的最快方法?

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

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

我会经常调用这种方法,所以性能很重要。 有没有比使用预先初始化的HashMap更快的方法来获取值?

回答

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

 Character.getNumericValue: 8360 Character.digit: 8453 HashMap: 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 map = new HashMap() {{ 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:" ); 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 = '0' && c = 'a' && 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  '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] ); } } 

预初始化的数组比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的直接方法)进行基准测试,以确定哪种方法对您的应用程序来说最快。

哈希表会相对较慢。 这很快:

 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语句。 如果数组在缓存中,则arrays可能没问题,但是错过可能很昂贵。

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

 (char | 32) % 39 - 9 

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

 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 

使用数组应该是最快的。

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

将数字转换为比一个更大的组可以提高性能。

最值得一提的是甜蜜点,可能是4位数(64k表)。

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

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

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

简单但缓慢:

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

快点:

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

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

我不认为你可以击败直接arrays查找。

 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']); 

这是我对Greg代码的调整版本。 在我的盒子上它稍微快一些 – 但可能在噪音中。 它避免了下限检查,并且不需要进行任何减法。 创建64Karrays并避免任何限制检查似乎减慢了速度 – 但同样,在这样的时间下,几乎不可能确定什么是真实的和什么是噪声。

 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; } } 

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

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

老兄,我是微控制器程序员,在这个小小的世界里,速度真的很重要。 将’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”或其他运算符!

请享用...

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

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

根据您可以承受的空间大小,32位值的表格应该更快。