Integer源码解析

简介

     Integer类主要的作用就是对基本类型int进行封装,提供了一些处理int类型的方法,比如int到String类型的转换方法或String类型到int类型的转换方法,当然也包含与其他类型之间的转换方法。除此之外还有一些位相关的操作 。

 

说明:该源码来自于 jdk_1.8.0_162 版本。

 

结构及常用方法

IntegerCache 内部类

说明:IntegerCache 是 Integer 的一个内部类,默认缓存的范围是 [-128,127],当Integer的值范围在 [-128,127] 时则直接从缓存中获取对应的Integer对象,不必重新实例化。这些缓存值都是静态且 final 的,避免重复的实例化和回收。另外我们可以改变这些值缓存的范围,在启动JVM时通过:-Djava.lang.Integer.IntegerCache.high=xxx 就可以改变缓存值的最大值,比如:-Djava.lang.Integer.IntegerCache.high=500 则会缓存 [-128,500] 。

注意:新建 Integer对象时尽量使用 Integer.valueOf 方法以优化性能。

 

我们通过两道面试题来了解一下这个缓存

第一题:

public static void main(String[] args) {
    Integer a = 1;
    Integer b = 2;
    System.out.println("交换前:" + "a = " + a + " " + "b = " + b);
    swap(a, b);
    System.out.println("交换后:" + "a = " + a + " " + "b = " + b);
}

private static void swap(Integer numA, Integer numB) {
    Integer temp = numA;
    numA = numB;
    numB = temp;
}

输出结果:
交换前:a = 1 b = 2
交换后:a = 1 b = 2

注意:关于值传递和引用传递的问题 。这里要特殊考虑String,以及Integer、Double等基本类型包装类,它们的类前面都有final修饰,为不可变的类对象,每次操作(new或修改值)都是新生成一个对象,对形参的修改时,实参不受影响,与值传递的效果类似,但实际上仍是引用传递。

说明:Integer a = 1 实际上等价于 Integer a = Integer.valueOf(1) ,这就是自动装箱(后面会详解) 。尽管 a 和 b 都是 Integer 类型的对象,但是由于 Integer 这个类本身就跟 String 一样,是用 final 修饰的,并且 value 属性也是 final 修饰的,所以无论是调用方法重新赋值或者直接在 main 方法重新赋值,都会产生一个新的对象,原来的那个对象不会做任何改变,表面上都是同一个变量名,实际上却是一个新的对象 。

我们来看一个伪代码:

Integer a = Integer.valueOf(5);
a = Integer.valueOf(10);

等价于

Integer a = Integer.valueOf(5);
Integer newA = Integer.valueOf(10);
a = newA;

结论:所以常规的方法不能对他们原始的对象做任何的改变,在调用 swap() 方法时,实际上 numA 和 numB 的改变跟 a 和 b 并没有任何关系,因为 numA 和 numB 一旦修改,所谓改变后的对象那都是新建的对象,所以无论 numA 和 numB 如何改变,都不会影响 a 和 b 的值 。那么如果我们想改变 a 和 b 的值怎么办呢,那就是绕过这些 final 和 private 的限制,这里我们想到了反射,接着看第二题 。

 

第二题:

public static void main(String[] args) throws Exception {
    Integer a = 1;
    Integer b = 2;
    System.out.println("交换前:" + "a = " + a + " " + "b = " + b);
    swap(a, b);
    System.out.println("交换后:" + "a = " + a + " " + "b = " + b);
    Integer c = 1;
    System.out.println("c = " + c);
}

private static void swap(Integer numA, Integer numB) throws Exception {
    Field field = Integer.class.getDeclaredField("value");
    field.setAccessible(true);
    int temp = numA;
    field.set(numA, numB);
    field.set(numB, temp);
}

输出结果:
交换前:a = 1 b = 2
交换后:a = 2 b = 2
c = 2

说明:这一次我们使用反射来尝试替换 a 和 b 的值,但是看到运行结果,可能会疑惑:

(1) 为什么交换后 a = 2  b =2 

(2) 为什么 c = 2 

要解开这些疑问,我们得先把 Integer 的拆箱装箱的问题解决,拆箱装箱是Java语言的语法糖,为了看到最终运行时的实际代码,最简单的方式就是直接反编译看编译过后的代码,我们用 jad 工具反编译上述的代码得到如下内容:

public static void main(String args[]) throws Exception {
    Integer a = Integer.valueOf(1); //自动装箱
    Integer b = Integer.valueOf(2); //自动装箱
    System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
    swap(a, b);
    System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
    Integer c = Integer.valueOf(1);
    System.out.println((new StringBuilder()).append("c = ").append(c).toString());
}

private static void swap(Integer numA, Integer numB) throws Exception {
    Field field = java/lang/Integer.getDeclaredField("value");
    field.setAccessible(true);
    int temp = numA.intValue(); //自动拆箱
    field.set(numA, numB);
    field.set(numB, Integer.valueOf(temp));
}

说明:看到反编译后的代码,验证了我们上面说的自动装箱拆箱操作 。通过我们对 IntegerCache 内部类的学习可以知道,Integer.valueOf(1) 和 Integer.valueOf(1) 实际上获取的是 Integer cache[] 里面的值,具体是 cache 数组里面的哪个值呢,我们来看 Integer.valueOf() 方法的源码:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

说明:通过这个方法我们可以算出来 Integer.valueOf(1) = IntegerCache.cache[129] ,我们通过查看 IntegerCache 的静态代码块可以知道,IntegerCache.cache[129] = new Integer(1),也就是 value 为 1 的Integer 对象,但是这个 Integer 对象是存在 cache[] 数组缓存里面的 。既然我们知道了 -128 ~ 127 是使用的缓存里面的 Integer 对象,那么我们进一步将代码中的涉及到缓存的 Integer 都替换成缓存对象,添加上注释再来看看执行的流程:

public static void main(String args[]) throws Exception {
    Integer a = Integer.valueOf(1); //自动装箱,Integer.valueOf(1) = IntegerCache.cache[129]
    Integer b = Integer.valueOf(2); //自动装箱,Integer.valueOf(2) = IntegerCache.cache[130]
    System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
    swap(a, b);
    System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
    Integer c = Integer.valueOf(1); //自动装箱,Integer.valueOf(1) = IntegerCache.cache[129] 经过 swap 方法的修改,此时 value 为 2
    System.out.println((new StringBuilder()).append("c = ").append(c).toString());
}

private static void swap(Integer numA, Integer numB) throws Exception {
    Field field = java/lang/Integer.getDeclaredField("value");
    field.setAccessible(true);
    int temp = numA.intValue();
    //numA = Integer.valueOf(1) = IntegerCache.cache[129] ,其 value 为 1 
    //numB = Integer.valueOf(2) = IntegerCache.cache[130] ,其 value 为 2 
    //这一步是将 IntegerCache.cache[129] 设置为 IntegerCache.cache[130]
    field.set(numA, numB);
    //numB = Integer.valueOf(2) = IntegerCache.cache[130],其 value 为 2 
    //经过上一步的修改,此时 Integer.valueOf(temp) = Integer.valueOf(1) = IntegerCache.cache[129],其 value 为 2 
    //这一步是将 IntegerCache.cache[130] 设置为 IntegerCache.cache[129],IntegerCache.cache[130],其 value 依旧为 2
    field.set(numB, Integer.valueOf(temp));
}

说明:也就是说,field.set(numA, numB) 这一步实际上修改的是 IntegerCache.cache[129] 的值,而 IntegerCache.cache 是全局的,所以会影响到后续使用到这个缓存值的所有代码的运行结果 。这也就解释了输出结果: a = 2  b = 2 以及 c = 2 的原因了 。但这么做风险太大,实际项目中不能这么做 。想要完成期望的结果,也就是:a = 2  b = 1,可以使用 field.setInt(numB, Integer.valueOf(temp)) 或者 field.set(numB, new Integer(1)) 这个做法,但是这么做的话,被修改成错误的 cache 值又多了一个,最终,在错误的道路上又前进了一步 。

 

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

使用方法:

(1) Integer.parseInt("100",10) 表示十进制的100,所以值为100。

(2) Integer.parseInt("100",2) 表示二进制的100,所以值为4。

(3) Integer.parseInt("666",2) 会抛出java.lang.NumberFormatException异常,因为 "666" 不符合二进制数规则。

(4) Integer.parseInt("10000000000",10) 会抛出java.lang.NumberFormatException异常,因为该数值大于Integer的最大值。

 

 

bitCount 方法

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

作用:计算二进制数中1的个数 。

核心思想:核心思想就是先每两位一组求和看有多少个1,然后在前面的基础上再算每四位一组求和看有多少个1,接着每8位一组求和看有多少个1,接着16位,32位,最终在与0x3f进行与运算,得到的数即为1的个数。依次扩大组的范围,将前面的计算结果再次合并在一起,最终将全部32位所包含的1都计算出来 。

 特殊值: 

0x55555555 = 01010101010101010101010101010101

0x33333333 = 110011001100110011001100110011

0x0f0f0f0f = 1111000011110000111100001111

0x3f  = 0011 1111 = 63 (除去符号位,最多只能有63个1)

需求:以 i = 10011111 为例,计算 10011111 中 1 的个数 。

(1) 第一步:i = i - ((i >>> 1) & 0x55555555);      

这段代码的作用是:以两位数字为一组,统计 1 的个数,结果为: 1个  1个  2个  2个

转换为二进制表示为:01  01  10  10 ,所以经过这一步后i = 01011010

(2) 第二步:i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);      

这段代码的作用是:将上面计算出来的结果 01011010 以4位数字为一组,统计 1 的个数,结果为: 2个  4个

转换为二进制表示为:0010  0100 ,所以经过这一步后:i = 00100100

(3) 第三步:i = (i + (i >>> 4)) & 0x0f0f0f0f;      

这段代码的作用是:将上面计算出来的结果 00100100 以8位数字为一组,统计 1 的个数,结果为: 6个

转换为二进制表示为:00000110 ,所以经过这一步后:i = 00000110

(4) 第四步:i = i + (i >>> 8);     

这段代码的作用是:将上面计算出来的结果 00000110 以16位数字为一组,统计 1 的个数,结果为: 6个

经过这一步后依旧为:i = 00000110

(5) 第五步:i = i + (i >>> 16);     

这段代码的作用是:将上面计算出来的结果 00000110 以32位数字为一组,统计 1 的个数,结果为: 6个

经过这一步后依旧为:i = 00000110

(6) 最后一步:i & 0x3f;

这段代码的作用是:个人理解,求绝对值并且保证数值不会越界

00000110 & 00111111 = 00000110 = 6 个

 

highestOneBit 方法

public static int highestOneBit(int n) {
    // HD, Figure 3-1
    n |= (n >>  1);
    n |= (n >>  2);
    n |= (n >>  4);
    n |= (n >>  8);
    n |= (n >> 16);
    return n - (n >>> 1);
}

作用:获取 i 数值的二进制中最高位的 1 ,其他全为0的值。(例如:输入 101011 ,返回 100000 )

核心思想:找到数值 n 的最高位的 1 ,将最高位 1 的后面的所有二进制数值都变为 1 (例如:0010 1001  变为  0011 1111),然后将获取到的数值右移一位(0011 1111  变为  0001 1111),与原来的数值相减(0011 1111 - 0001 1111),便可以得到最高位 1 后面全为 0 的二进制数。

 

假设 n = 9,其中 第3 ~ 7行的操作图解为(目的就是将最高位 1 的后面的所有二进制数值都变为 1):

Integer源码解析_第1张图片

最后一步 n - (n >>> 1) 为:

最终结果为:0000 1000 。

 

lowestOneBit 方法

public static int lowestOneBit(int n) {
    // HD, Section 2-1
    return n & -n;
}

作用:与 highestOneBit 方法相反,lowestOneBit 方法获取的是获取最低位的 1,其他全为0的值 。

核心思想:我们都知道,在计算机中,负数以其正值的补码形式表达。利用 n 的原码与补码进行 & 运算,正好能够去掉其他位置的1,留下最低位的 1 。

运算过程:

说明:假设 n = 5,那么使用二进制数表示为(int类型32位):00000000 00000000 00000000 00000101 。

原码:00000000 00000000 00000000 00000101    (一个整数,按照绝对值大小转换成的二进制数)

反码:11111111 11111111 11111111 11111010           (将二进制数按位取反所得到的二进制数)

补码:11111111 11111111 11111111 11111011         (反码加1称为补码)

最后 n & -n就是如下运算:

Integer源码解析_第2张图片

看到这里我们可能还是会有一个疑问,为什么 原码 & 补码 正好就得到最低位的 1 后面全为 0 的二进制数?

解答:如上所示,如果我们直接将 n = 5 的原码和反码做 & 运算,那么结果将为 0 ,所以关键点在于这个对反码的加 1 操作,我们都知道,反码就是将原码取反过来显示,那也就意味着原码原来是 0 的地方都会变成 1,而原来是 1 的地方都会变成 0,而这个加 1 操作正好就作用到原码的最低位 1 这个位置,我们来看一个例子:

n = 4

原码:0000 0100

反码:1111 1011

反码 + 1 :1111 1011 + 0000 0001 = 1111 1100 (补码)

我们可以看到,由于原码的 1 后面是 00,在取反码的时候 00 会变成 11,而原码的 1 取反后会变成 0,也就是 1111 1011,当再给这个 反码 + 1 的时候,这个 0 能够起到一个截止的作用,也就是正好停在了原码的最低位的 1 的地方,现在我们再来看这个补码的组成特点:

Integer源码解析_第3张图片

这样的一个数与原码做 & 运算,结果当然就是最低位的 1 左边的数全部变为 0,而只保留最低位的 1 了。

 

 

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 的二进制从头开始有多少个连续的 0 。

核心思想:这里处理其实是体现的是二分查找思想,先看高16位是否为0,是的话将高16位的0全部去掉,把低16位的数值补上,并记录下0的个数,继续往下判断,否则接着右移24位看是不是为0,这一步的目的是看高8位是否为0,是的话继续叠加上一次判断得到的 0 的个数,并且把高8位的0去掉,补上低24位的数值,否则继续往下判断,直到最后得到结果 。

运算过程:

说明: i <<= 16  等价于  i = i << 16;

假设 i = 1257084 = 0000 0000 0001 0011 0010 1110 0111 1100

(1) 第一步:if (i >>> 16 == 0) { n += 16; i <<= 16; }

0000 0000 0001 0011 0010 1110 0111 1100   无符号右移16位结果为:  0000 0000 0000 0000 0000 0000 0001 0011;  结果不为 0,所以继续往下判断。

(2) 第二步:if (i >>> 24 == 0) { n += 8; i <<= 8; }

0000 0000 0001 0011 0010 1110 0111 1100   无符号右移24位结果为:  0000 0000 0000 0000 0000 0000 0000 0000; 结果为 0。

接下来将 n 的值由 1 叠加上 8 ,i 左移 8 位,也就是将高8位的0全部去掉,把低24位的数值左移补上,左右边补 8 个 0,此时结果为 :

n = 9;

i = 0001 0011 0010 1110 0111 1100 0000 0000;

(3) 第三步:if (i >>> 28 == 0) { n += 4; i <<= 4; }

0001 0011 0010 1110 0111 1100 0000 0000   无符号右移28位结果为:  0000 0000 0000 0000 0000 0000 0000 0001; 结果不为 0,所以继续往下判断。

(4) 第四步:if (i >>> 30 == 0) { n += 2; i <<= 2; }

0001 0011 0010 1110 0111 1100 0000 0000 无符号右移30位结果为: 0000 0000 0000 0000 0000 0000 0000 0000; 结果为 0。

接下来将 n 的值由 9 叠加上 2 ,i 左移 2 位,也就是将高2位的0全部去掉,把低30位的数值左移补上,左右边补 2 个 0,此时结果为 :

n = 11;

i = 0100 1100 1011 1001 1111 0000 0000 0000;

(5) 第五步:n -= i >>> 31;

0100 1100 1011 1001 1111 0000 0000 0000 无符号右移31位结果为: 0000 0000 0000 0000 0000 0000 0000 0000; 结果为 0。

这一步是为了判断最高位是否为 1,如果为 1 则减去最开始的 n 的初始值 1,否则 n 就是最终结果。

 

 

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 的二进制从末尾开始有多少个连续的 0 。

说明:numberOfTrailingZeros 方法和 numberOfLeadingZeros 方法是完全相反的操作,不仅仅移位是相反的,就连 n 的值的处理方式也是反的,n 的初始值为 31,然后通过判断来减小,理解了上一个方法,这个方法就不做过多解释了。

 

 

toUnsignedString0 方法

//十进制转换为二进制
public static String toBinaryString(int i) {
    return toUnsignedString0(i, 1);
}

//十进制转换为八进制
public static String toOctalString(int i) {
    return toUnsignedString0(i, 3);
}

//十进制转换为十六进制
public static String toHexString(int i) {
    return toUnsignedString0(i, 4);
}

private static String toUnsignedString0(int val, int shift) {
    // assert shift > 0 && shift <=5 : "Illegal shift value";
    //获取val的有效位数
    int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
    //计算转换成字符数组所需要的长度
    int chars = Math.max(((mag + (shift - 1)) / shift), 1);
    char[] buf = new char[chars];
    formatUnsignedInt(val, shift, buf, 0, chars);
    // Use special constructor which takes over "buf".
    return new String(buf, true);
}

//解析出每个字符
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
    int charPos = len;
    int radix = 1 << shift;
    int mask = radix - 1;
    do {
        buf[offset + --charPos] = Integer.digits[val & mask];
        val >>>= shift;
    } while (val != 0 && charPos > 0);

    return charPos;
}

作用:将十进制数转换为其他进制的字符串值 。

你可能感兴趣的:(Integer源码解析)