概述
1,Integer对象在内存中的大小
Integer类中包含了一个成员变量,private final int value;
参考书籍《深入理解Java虚拟机 JVM高级特性与最佳实践》中关于java对象内存分布的章节,在hotspot虚拟机中,对象在内存中的存储可以分为3块区域: 对象头,实例数据和对齐填充。对象头其中的一部分官方称为"Mark Word",在32位和64位虚拟机上分别占4字节,8字节,其中包含了对象hash码,对象分代年龄等,另一部分为类型指针,32位虚拟机下占4字节,64虚拟机下站8字节。64位下若开启指针压缩(默认开启),类型指针占4字节。
32位系统下,一个Integer对象占用16字节,其中Integer对象头部8字节,成员变量为int类型,占4字节,因为hotspot是8字节对其,所以这里有4字节的padding64位系统下,分两种情况
---未开启指针压缩,一个Integer对象占用24字节,其中Integer对象头部16字节,成员变量占4字节,因为hotspot是8字节对其,所以这里加上4字节的padding
---开启指针压缩,一个Integer对象占用16字节,其中Integer对象头部12字节,成员变量占4字节.
上面的描述终觉得空洞,下面编写一段代码,我们来看下Integer对象在内存究竟是长啥样。
public class IntegerMain{
public static void main(String[] args){
IntegerTest t = new IntegerTest();
print();
}
public static void print(){
}
}
class IntegerTest{
private Integer a = new Integer(10);
}
为了方便查找程序运行时生成的Integer对象,写了个IntegerTest类,借助hotspot调试工具,在本人的32位机器上,运行时生成的Integer对象的地址信息如下:
对象在0x3cb57e8这个地址上,从上图中可以看到一个Integer对象在内存中站16字节,上图中0x03cb57e8这里存放的就是"mark_word",值为0x00000001,4字节。0x03cb57f0存放的是对象的值10,接下来的就是对其填充的4字节。类型指针_metadata._klass存放在0x03cb57ec,指向的地址为0x04670398,也是4字节,这个地址存储了Integer类型的信息 (以后再细细看):
2,toString方法的实现
Integer支持将一个数字转让为2~36进制的字符串,这主要是因为在Integer的源码中有如下定义,列出了字符可能代表的数字
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'
};
默认是按照十进制的格式转换为字符串.对于十进制,Integer源码中提供了一个更为快速的处理方法,如下:
public static String toString(int i) {
if (i == Integer.MIN_VALUE)//如果是int所能表示的最小值,直接返回固定的字符串
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);//判断该使用多少个字符来表示,如果为负数,长度+1
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
主要的实现方法在getChars中
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
while (i >= 65536) {// 2的16次方
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}
// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16+3);// q/10,乘法比除法速度快
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;
}
}
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',
} ;
仔细看上面的代码,发现有两个问题值得思考:
问题一:
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
r = i - ((q << 6) + (q << 5) + (q << 2));
i = q;
buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];
}
当数字大于等于65536时,每次计算出末位的两个数字到buf中,数字65536的来源或者依据是啥?换个数字是否可以?
问题二:
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;
}
代码中的数字52429又是怎么来的,换个数字是否可以?
先来看问题二,q=(i*52429)>>>(16+3)是为了实现i/10的作用,因为使用移位和乘法运算比除法运算快。更改该代码为q=(a1*a2)>>>a3,不难发现a1,a2,a3这三个数直接需要满足一定的约束关系。
1,该代码进行的是无符号移位,所以a1*a2不能大于2^32-1(无符号整数能表示的最大值),否则会出现溢出.a1越大,a2就会越小
2,(a1*a2)>>>a3的结果需要满足一定的精度,不然起不了除以10的作用,看下面的表格
a2 | 2^a3 | a2/2^a3 |
103 | 1024=2^10 | 0.1005859375 |
205 | 2048=2^11 | 0.10009765625 |
13108 | 131072=2^17 | 0.100006103515625 |
26215 | 262144=2^18 | 0.100002288818359375 |
52429 | 524288=2^19 | 0.1000003814697265625 |
104858 | 1048576=2^20 | 0.1000003814697265625 |
209716 | 2097152=2^21 | 0.1000003814697265625 |
419431 | 4194304=2^22 | 0.1000001430511474609375 |
838861 | 8388608=2^23 | 0.10000002384185791015625 |
从上面的表格看,下一个精度较高的是2^22,是不是可以作为一个选择。个人认为可以,但源码中使用的a3=19,目前我分析下来认为应该是经过了多次试验而选择的值(希望日后能想到合适的论证),同时a3取19时能够满足精度要求。a1最大值为2^32/52429=81919,而源码中取的a1=65536=2^16,这里应该是处于取数与81919最接近且方便CPU取数的目的来选择65536.
综上,这两个问题还没有较为完美的解释与论证,希望以后能找到关于这两个问题的文章。
3.valueOf方法
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
IntegerCache默认缓存了-128到127的数字,因为这部分数字在日常应用中用的比较多, 避免了多次生成对象。通过参数
-XX:AutoBoxCacheMax
可以更改缓存数字的上限。所以默认情况下以下的输出应该成立:
Integer a = 127;
Integer b = Integer.valueOf(127);
Integer c = new Integer(127);
System.out.println(a == b);//true
System.out.println(a ==c);//false
a = 128;
b = Integer.valueOf(128);
System.out.println(a == b);//false
4,
highestOneBit方法
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);
}
作用是取出i转换成二进制后最左边的“1”表示的值,比如i=5,那么最左边的1表示的数字为4,如果i是负数,那就表示Integer.MIN_VALUE;实现思路是从最左边的“1”开始,通过移位和或操做,将之后的“0”全部变成“1”,最后用得到的数字减去该数字右移一位的数字得到最终的值。
5,lowestOneBit方法
public static int lowestOneBit(int i) {
// HD, Section 2-1
return i & -i;
}
作用:将数字i转换成二进制后最右边的“1”所代表的数字,比如i=5,方法的返回为1,i=4,方法的返回为4。
6,numberOfLeadingZeros方法
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; }
if (i >>> 24 == 0) { n +=
8; i <<=
8; }
if (i >>> 28 == 0) { n +=
4; i <<=
4; }
if (i >>> 30 == 0) { n +=
2; i <<=
2; }
n -= i >>> 31;
return n;
}
作用:数字i的二进制表示最左端的“1”之前有多少个“0”。实现思路采用了二分查找法,减少了比较次数。
7,numberOfTrailingZeros方法
public static int numberOfTrailingZeros(int i) {
// HD, Figure 5-14
int y;
if (i == 0) return 32;
int n = 31;
y = i <<16; if (y != 0) { n = n -16; i = y; }
y = i << 8; if (y != 0) { n = n - 8; i = y; }
y = i << 4; if (y != 0) { n = n - 4; i = y; }
y = i << 2; if (y != 0) { n = n - 2; i = y; }
return n - ((i << 1) >>> 31);
}
作用:数字i的二进制表示最右端的“1”之之后有多少个“0”。实现思路采用了二分查找法,减少了比较次数。
8,bitCount方法,计算数字的二级制表示中有多少个1
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
乍一看,没看明白这个实现是怎么玩的。看这篇文章---http://15838341661-139-com.iteye.com/blog/1642525
参考资料:
http://rednaxelafx.iteye.com/blog/1847971
最后
以上就是暴躁时光为你收集整理的java.lang之java.lang.Integer源码阅读及分析的全部内容,希望文章能够帮你解决java.lang之java.lang.Integer源码阅读及分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复