这一篇主要介绍Java中经常使用的整数类型 - Integer
首先来说一下位运算,位运算应用是非常广的。无论是名企的笔试、面试,还是Java的源代码,这种应用随处可见。关于位运算我不想说太多,可以给大家推荐一篇非常不错的博文,地址如下:
http://blog.youkuaiyun.com/morewindows/article/details/7354571
同样,在Integer.java类的源代码中,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) {
// 基数的取值必须在一个范围
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
radix = 10;
/* Use the faster version */
if (radix == 10) {
return toString(i);
}
char buf[] = new char[33];
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个数字后,共有32个字符可以用来表示数值。由此可知,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,这时候将负数转换为正数就会超出类型所表示的范围,在这里的道理是一样的。
继续看另外一个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++)
if (x <= sizeTable[i])
return i+1;
}
功能就是将一个有符号的整数转换为字符串,首先是将这个整数转换为字符数组,然后将这个字符数组转换为字符串,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;
}
}
为了能够快速将数值转换为字符类型,程序采用了空间换时间的策略,预先使用数组存储了一些需要的数。
这段代码看的我火急火燎的,主要有几个问题没弄明白:
(1)为什么要对大于和小于65536的数要分开处理?
(2)q=(i*52429)>>>(16+3)不太看得懂
哪位大神给指点一下感激不尽~~~
好了,我们继续往下看,再一个主要的方法就是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;
}
如上函数的功能就是将字符串转换为整数,接受以八进制、十进制和16进制格式书写的字符串,测试如上方法:
System.out.println(Integer.decode("0x0011"));
System.out.println(Integer.decode("0X0011"));
System.out.println(Integer.decode("011"));
System.out.println(Integer.decode("-011"));
最后运行的结果如下:19 19 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()函数进行判断的。关于这个函数的源代码将在字符源码剖析中解析。
PS:后续要讲解一下bigInteger类