转载请注明出处:http://blog.csdn.net/mazhimazh/article/details/17684701
来看一道有关的面试题:
字符串转换为整数错误的方法为(D)
A int i=new Integer("10") B int i=Integer.parseInt("10");
C int i=Integer.decode("10") D int i=Integer.getInteger("10")
解释:getInteger()主要是返回系统属性相关的值,而D项由于系统属性"10"不存在,所以返回为null。decode()可以转换16进制、8进制、2进制等,所以功能比较强大,下面会进行源代码介绍。
首先来说一下位运算,位运算应用是非常广的。无论是名企的笔试、面试,还是Java的源代码,这种应用随处可见。关于位运算我不想说太多,可以给大家推荐一篇非常不错的博文,地址如下:
http://blog.csdn.net/morewindows/article/details/7354571
同样,在Integer类的源代码中,Java的设计师们为了提高效率使用了大量的位运算,首先来看一个简单的进制转换源代码:
//无符号整数 16进制,向右移动4位 public static String toHexString(int i) { return toUnsignedString(i, 4); } //无符号整数 8进制,向右移动3位 public static String toOctalString(int i) { return toUnsignedString(i, 3); } //无符号整数 2进制,向右移动2位 public static String toBinaryString(int i) { return toUnsignedString(i, 1); } // 对无符号整数进行转换 private static String toUnsignedString(int i, int shift) { char[] buf = new char[32]; int charPos = 32; int radix = 1 << shift; int mask = radix - 1; do { buf[--charPos] = digits[i & mask]; i >>>= shift;//i=i>>>shift } while (i != 0); return new String(buf, charPos, (32 - charPos)); }可以看一下对于10进制无符号整数的进制转换。由于缺少边界等的检查,所以toUnsignedString()方法并没有公开,而是提供了多个常用进制转换的方法。最后返回了转换后的字符串表示形式。至于源码中为什么要定义一个32的字符数组后面将会分析到。
我们知道,Java中的int数值类型占用4个字节,有时候需要对表示一个int数值的4个字节做一些特殊的处理,如字节反转、位反转等,源代码如下:
// 将4个字节的顺序进行逆转,并不是对位进行逆转 public static int reverseBytes(int i) { return ((i >>> 24) ) | ((i >> 8) & 0xFF00) | ((i << 8) & 0xFF0000) | ((i << 24)); } // 对位进行逆转 public static int reverse(int i) { // HD, Figure 7-1 i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555; i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333; i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f; i = (i << 24) | ((i & 0xff00) << 8) | ((i >>> 8) & 0xff00) | (i >>> 24); return i; }
大家可以好好研究一下如上的代码是怎么实现他们的功能的。继续看下面的两个方法:
public static int highestOneBit(int i) { // HD, Figure 3-1 i |= (i >> 1); i |= (i >> 2); i |= (i >> 4); i |= (i >> 8); i |= (i >> 16); return i - (i >>> 1); } public static int lowestOneBit(int i) { return i & -i; }
highestOneBit()作用是取 i 这个数的二进制形式最左边的最高一位且高位后面全部补零,最后返回int型的结果。而lowestOneBit()自然就是取最右边的第一位1,其前面全部置0后的结果。代码类中还提供了其他的一些方法,这些方法也大量用到了位操作,有兴趣的可以自己去看一下。
来看一下类中其他重要的方法。
// 如下的这些ASCII字符可能会表示为数字.26个字符和10个数字,加起来是36 final static char[] digits = { '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z' }; public static String toString(int i, int radix) { // 基数的取值必须在一个范围,也就是2~36之间 if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) radix = 10; /* Use the faster version */ if (radix == 10) { return toString(i);// 按进制进行转换,默认为10进制 } char buf[] = new char[33]; // 一个整数由32位来表示,负数时前面还需要一个“-”号 boolean negative = (i < 0); int charPos = 32; // 当i为一个正数时,变为负数 if (!negative) { i = -i; } while (i <= -radix) { buf[charPos--] = digits[-(i % radix)]; i = i / radix; } buf[charPos] = digits[-i]; if (negative) { buf[--charPos] = '-'; } return new String(buf, charPos, (33 - charPos)); }如上源代码片段首先定义了一个digits字符数组,这些数组是可以表示数值类型的。如在十六进制中,a可以表示10,b可以表示11一样,26个字母同样可以代表一个两位数的整数,加上10个数字后,共有36个字符可以用来表示数值。由此可知,Java中支持的最大进制为32,如果大于32,则按10进制进行处理。接下来有一个toString()方法,功能是将十进制的i转换为radix进制的数,并以字符串的形式进行返回。如下测试代码:
System.out.println(Integer.toString(13,19)); System.out.println(Integer.toString(173,29));运行结果如下:d // 13 5s // 5*29+28
需要注意的是toString()方法的实现代码,可以看到对于一个有符号数i,都是转换为负数来进行进制转换的,这样可以统一进行有符号数的处理。那么可不可以转换为正数进行处理呢?这时候就需要考虑边界的问题了。举个例子,如byte类型,表示的最小整数为-128,而最大的整数为127,这时候将负数转换为正数就会超出类型所表示的范围,在这里的道理是一样的。
看一下将数值默认按10进制进行字符串转换的toString()方法:
public static String toString(int i) { if (i == Integer.MIN_VALUE) return "-2147483648"; //举例:如果是9,这时size为1,而如果为-9时,为2,还要存储一个"-"号 int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);// 计算出保存整数需要的字符数组大小 char[] buf = new char[size]; getChars(i, size, buf); return new String(0, size, buf); } //如下存储的是10进制的数 final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE };// 最大的MAX_VALUE为2147483647 // Requires positive x static int stringSize(int x) { for (int i=0; ; i++)v高效率,采用查表法 return i+1; }
功能就是将一个有符号的整数转换为字符串,首先是将这个整数转换为字符数组,然后将这个字符数组转换为字符串即可。当i等于Integer.MIN_VALUE时,直接返回值,而不采用算法。这是因为调用getChars()方法进行转换时,最大的负数要换为正数进行计算,而这会造成溢出。
getChars()方法的源代码如下:
final static char [] DigitTens = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', } ; final static char [] DigitOnes = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', } ; static void getChars(int i, int index, char[] buf) { int q, r; int charPos = index; char sign = 0; if (i < 0) { sign = '-'; i = -i; } // Generate two digits per iteration // 处理超过4个字节能表示的最大数范围的数,也就是处理Unicode的增补字符 while (i >= 65536) {// 2^16=65536 q = i / 100; // 2^6+2^5+2^4=100 r = i - ((q << 6) + (q << 5) + (q << 2));// 相当于r = i - (q * 100); i = q; buf [--charPos] = DigitOnes[r]; buf [--charPos] = DigitTens[r]; } // Fall thru to fast mode for smaller numbers for (;;) { q = (i * 52429) >>> (16+3); r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ... buf [--charPos] = digits [r]; i = q; if (i == 0) break; } if (sign != 0) { buf [--charPos] = sign; } }
为了能够快速将数值转换为字符类型,程序采用了空间换时间的策略。DigitOnes 和DigitTwos数组,通过除100得到的余数匹配数组中的值就可以得到想要的char类型的数值。然后分别查DigitTens和DigitOnes两个表,直接获取十位和个位。
当i < 65536时,通过i - ( i / 10 * 10 )来获得每一位。
这里的 q = (i * 52429) >>> (16+3)可能会让人费解,实际2 ^ 19 = 524288,而52429 / 524288 = 0.10000038146972656,约为0.1,所以这一步实际就是在除以10,只不过是换种效率更高的方法而已。
值得一提的是除了52429外,还有很多数可以选,如:
2^10=1024, 103/1024=0.1005859375
2^11=2048, 205/2048=0.10009765625
...
2^18=262144, 26215/262144=0.10000228881835938
2^19=524288, 52429/524288=0.10000038146972656
当然,选52429也不是偶然,而是因为它是在不超出整型范围内,精度最高的一个。
最后就是看是否为负数,如果是负数的话,加入‘-’字符到buf字符数组中就可以解决问题了。
好了,我们继续往下看,再一个主要的方法就是decode()了,源代码如下:
public static Integer decode(String nm) throws NumberFormatException { int radix = 10; int index = 0; boolean negative = false; Integer result; if (nm.length() == 0) throw new NumberFormatException("Zero length string"); char firstChar = nm.charAt(0); // Handle sign, if present if (firstChar == '-') { negative = true; index++; } else if (firstChar == '+') index++; // Handle radix specifier, if present if (nm.startsWith("0x", index) || nm.startsWith("0X", index)) { index += 2; radix = 16; } else if (nm.startsWith("#", index)) { index ++; radix = 16; } else if (nm.startsWith("0", index) && nm.length() > 1 + index) { index ++; radix = 8; } if (nm.startsWith("-", index) || nm.startsWith("+", index)) throw new NumberFormatException("Sign character in wrong position"); try { result = Integer.valueOf(nm.substring(index), radix); result = negative ? Integer.valueOf(-result.intValue()) : result; } catch (NumberFormatException e) { // If number is Integer.MIN_VALUE, we'll end up here. The next line // handles this case, and causes any genuine format error to be // rethrown. String constant = negative ? ("-" + nm.substring(index)) : nm.substring(index); result = Integer.valueOf(constant, radix); } return result; }
如上函数的功能就是将字符串转换为整数,接受以八进制、十进制和十六进制格式书写的字符串,测试如上方法:
System.out.println(Integer.decode("0x0011")); System.out.println(Integer.decode("0X0011")); System.out.println(Integer.decode("011")); System.out.println(Integer.decode("-011"));最后运行的结果如下:17 17 9 -9
合法的字符串表示的数值最后都是以十进制的形式返回的,或者还可以使用parseInt()方法,源代码如下:
public static int parseInt(String s, int radix)throws NumberFormatException{ /* * WARNING: This method may be invoked early during VM initialization * before IntegerCache is initialized. Care must be taken to not use * the valueOf method. */ if (s == null) { throw new NumberFormatException("null"); } if (radix < Character.MIN_RADIX) { throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX"); } if (radix > Character.MAX_RADIX) { throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX"); } int result = 0; boolean negative = false; int i = 0, len = s.length(); int limit = -Integer.MAX_VALUE; int multmin; int digit; if (len > 0) { char firstChar = s.charAt(0); if (firstChar < '0') { // Possible leading "+" or "-" if (firstChar == '-') { negative = true; limit = Integer.MIN_VALUE; } else if (firstChar != '+') throw NumberFormatException.forInputString(s); if (len == 1) // Cannot have lone "+" or "-" throw NumberFormatException.forInputString(s); i++; } multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE digit = Character.digit(s.charAt(i++),radix); if (digit < 0) { throw NumberFormatException.forInputString(s); } if (result < multmin) { throw NumberFormatException.forInputString(s); } result *= radix; if (result < limit + digit) { throw NumberFormatException.forInputString(s); } result -= digit; } } else { throw NumberFormatException.forInputString(s); } return negative ? result : -result; }由于这个函数是公开的,所以对边界条件的检查特别严格。将指定进制的有符号整数转换为十进制的整数返回,测试代码如下:
parseInt("0", 10) returns 0 parseInt("473", 10) returns 473 parseInt("+42", 10) returns 42 parseInt("-0", 10) returns 0 arseInt("-FF", 16) returns -255 parseInt("1100110", 2) returns 102 parseInt("2147483647", 10) returns 2147483647 parseInt("-2147483648", 10) returns -2147483648 parseInt("2147483648", 10) throws a NumberFormatException parseInt("99", 8) throws a NumberFormatException parseInt("Kona", 27) returns 411787需要注意的是:
parseInt("Kona", 10) throws a NumberFormatException //因为十进制不可能出现K字符,所以出现异常
程序会出现错误,因为10进制的数值中只能出现0到9的数字,这主要是通过Character.digit()函数进行判断的。关于这个函数的源代码将在字符源码剖析中解析。
Integer中也提供了hashCode()、equals()、compareTo()等常用的方法,由于这些方法在前一篇文章 Java 7源码分析第2篇 - Java整数类型(1)中已经介绍过,所以不再介绍。