java.lang之java.lang.Integer源码阅读及分析

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字节的padding
64位系统下,分两种情况
---未开启指针压缩,一个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的作用,看下面的表格

a22^a3a2/2^a3
1031024=2^100.1005859375
2052048=2^110.10009765625
13108131072=2^170.100006103515625
26215262144=2^180.100002288818359375
52429524288=2^190.1000003814697265625
1048581048576=2^200.1000003814697265625
2097162097152=2^210.1000003814697265625
4194314194304=2^220.1000001430511474609375
8388618388608=2^230.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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值