深入理解,源码解析 Integer 神奇的老哥

咱们对这个Integer封装类并不陌生,就连初学者都了解  ,下面咱们就解剖一下这个神奇的老哥

JDK源码 (在java.lang包下面)

深入理解,源码解析 Integer 神奇的老哥

 

  1. 首先这老哥继承一个Number 类 ,这个类的总体作用就是对数字的处理, 这个实现了Serializable(序列化接口),内置抽象方法(intValue,longValue,floatValue,doubleValue)以及byteValue,shortValue方法。
  2. 其次实现比较接口 Comparable ,其泛型是老哥,此接口对实现它的每个类的对象强加一个整体排序。 这个排序被称为类的自然排序 ,类的compareTo方法被称为其自然比较方法,相当于java的Arrays.sort方法,同时
    不需要指定一个comparator  有兴趣可以看一下此接口和comparator  的区别,老哥就有了自己和自己本身类型比较能力。
  3. 老哥是个final类  不能被继承,所以你见过老哥的儿子和孙子吗?

 

深入理解,源码解析 Integer 神奇的老哥

肯定会有些大兄弟看到这玩意会懵逼的 其实这两个玩意就是2个补码,不要那么紧张

这里的话 我就不解释了   给大兄弟们一个链接(因为我懒的看 )https://blog.csdn.net/u011531425/article/details/80712160

 

最后得出来 的结论 MIN_VALUE =  -231-1   MAX_VALUE = 231-1    这是老哥的范围

深入理解,源码解析 Integer 神奇的老哥

Class.getPrimitiveClass("int")  为指定的对象返回虚拟机的类对象   实现 :static native Class getPrimitiveClass(String name);   这个类的话  是通过其他语言(好像是C写的)实现的 (native ),咱们不用管这哥们。 

这个地方涉及到老哥.class 和 老哥.TYPE  以及 int.class的区别 

我这里稍微的解释一下 

  • 老哥.class  这个很好理解就是返回老哥的类型类,
  • 老哥.TYPE 这个就是虚拟机的类对象, 其实也就是int 
  • int.class 返回的是int

  做个测试:通过反射机制,看老哥.TYPE的类型类是不是原始类型

  Class<Integer> c1 = Integer.TYPE; 
  Class<Integer> c2 = int.class; 
  System.out.println(c1 == c2);  // true
  System.out.println(c1.isPrimitive()); // true 
  System.out.println(c2.isPrimitive()); // true 

  isPrimitive方法 确定指定对象表示一个基本类型 ,有九个预定对象代表八个基本数据类型和void。 这些是由Java虚拟机创建,并且具有相同的名称为他们所代表的基本类型,即boolean , byte , char , short , int , long , floatdouble

       并且是该方法返回true的唯一对象 

结论:

  c1和c2的虚拟机类对象是一样的,Integer在虚拟机中还是拆箱为int。

老哥.toString(int i, int radix)

深入理解,源码解析 Integer 神奇的老哥 View Code

  •   此方法的第一个int i 是大兄弟要转换的参数 。 第二个参数是该参数是以几进制来显示  该方法返回值为String老朋友。

实现分析

  1. 首先判断了radix

if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
   radix = 10;

Character.MIN_RADIX  ==  (public static final int MIN_RADIX = 2;) // Character是字符的类

Character.MAX_RADIX ==  (public static final int MAX_RADIX = 36;)

这里解释一下为什么最小值是2 ?   如果不是2的话,输入汉字的话  ,这家伙就直接塌盘了。

如果radix <  2  或者 radix > 36  那么将会默认以10进制的方式显示。

1

2

3

if (radix == 10) {

    return toString(i);

}

如果正好等于10的话  那么直接调用toString(i)方法。

深入理解,源码解析 Integer 神奇的老哥 View Code

首先判断是否等于Integer.MIN_VALUE    如果等于的return -2147483648 说明老哥的最小值就是这个

其次判断是否小于0

1

int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);

深入理解,源码解析 Integer 神奇的老哥 View Code

1

stringSize方法主要判断大兄弟当前传的值 是几位数字  你可以试一下  如果当i等于2的时候  最后size为  当大于等于10的时候 size为2

 然后就是看比较关键的部分了 getChars(i, size, buf);   i为大兄弟你们传的值  size是值的位数 buf是之后的存储的地方 

深入理解,源码解析 Integer 神奇的老哥 View Code

深入理解,源码解析 Integer 神奇的老哥 View Code

深入理解,源码解析 Integer 神奇的老哥

while (i >= 65536) {
        q = i / 100;
        //
        r = i - ((q << 6) + (q << 5) + (q << 2)); //等价于r = i - (q * 100);
     i = q;
    // 取DigitOnes[r]的目的其实取数字r%10的结果        buf [--charPos] = DigitOnes[r];
     // 取DigitTens[r]的目的其实是取数字r/10的结果
        buf [--charPos] = DigitTens[r];
}

深入理解,源码解析 Integer 神奇的老哥

疑问:为什么要和65536比较呢  ?

结论 :移位要比乘法的效率高  程序要尽量用移位代替乘法。

 

深入理解,源码解析 Integer 神奇的老哥

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

深入理解,源码解析 Integer 神奇的老哥

 

这里将代码计算一下  

   q = (i * 52429) >>> (16+3);

==> q = (i * 52429) / 524288;

==> q = i * 0.1 

==> q = q/10

结论:这里可以看出来 移位要比 乘法除法 效率高  乘法比除法的效率高  这些都是以后在高效代码中需要注意的。

注意:这里的乘以  是   0.1

刚才那个疑问 :为什么要和65536比较呢  ?

这个主要因为基本数据类型int有关,最大不能超过(2^31-1)。如果使用一个太大的数字进行乘法加移位运算很容易导致溢出 ,那么为什么是65536这个数字呢?

我们还用 q = (i * 52429) >>> (16+3)来个解析

使用num1,num2,num3三个变量代替源代码中的数字,便于后面分析使用num1=65536, num2=52429 ,num3=19

既然我们要使用q = (i * num2) >>> (num3);的形式使用乘法和移位代替除法,那么n和m就要有这样的关系:

num2= (2^num3 /10 )

只有这样才能保证(i * num2) >>> (num3)结果接近于0.1。  

2^13=8192, 820/8192=0.10009765625

2^14=16384, 1639/16384=0.10003662109375

2^15=32768, 3277/32768=0.100006103515625

2^16=65536, 6554/65536=0.100006103515625

2^17=131072, 13108/131072=0.100006103515625

2^18=262144, 26215/262144=0.10000228881835938

2^19=524288, 52429/524288=0.10000038146972656

2^20=1048576, 104858/1048576=0.1000003815

2^21=2097152, 209716/2097152 = 0.1000003815

2^22= 4194304, 419431/4194304= 0.1000001431

因为如果num3越大,就会要求i比较小,因为必须保证(i * num2) >>> (num3)的过程不会因为溢出而导致数据不准确。那么是怎么敲定num1=65536,num2= 524288, num3=19的呢? 这三个数字之间是有这样一个操作的:

(num1*num2) >>> num3

因为要保证该操作不能因为溢出导致数据不准确,所以num1和num2就相互约束。两个数的乘积是有一定范围的,不成超过这个范围,所以,num1增大,num2就要随之减小。相对以上次幂的计算   19的时候  最接近于0.1  所以就选择了这3个数字。

 

接下来的代码  就i不是那么难分析了 只要使用代入法就可以了   最后会将数据存储到buf数组里面。


老哥.parseInt(String s, int radix)

深入理解,源码解析 Integer 神奇的老哥 View Code

此方法 监听数字转化异常

    throws NumberFormatException

  • 第一个参数  是大兄弟们要转化的字符串   那如果说  字符串中没有数字的话  是不是就要报异常了   这个很好理解   
  • 第二个参数  应该有点熟悉把   这个就是咱们的base 字符串需要以几进制的形式显示。

实现分析

深入理解,源码解析 Integer 神奇的老哥

// 首先判断字符串是否为null
if (s == null) {
    throw new NumberFormatException("null");
}
// 判断是几进制形式显示 范围为 2 - 36   不然的话 ,会报异常
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");
}

深入理解,源码解析 Integer 神奇的老哥

深入理解,源码解析 Integer 神奇的老哥

 

int result = 0;
boolean negative = false;
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;

// 判断s字符串的长度

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

深入理解,源码解析 Integer 神奇的老哥

那咱们来举个例子(走debug)

  

public static void main(String[] args) {
    Integer a = 101;
    System.out.println(a.parseInt("600", 10));
}

 深入理解,源码解析 Integer 神奇的老哥

我就那郁闷  600的第一个字符应该是6的啊  怎么会是54呢   然后我想了一下  我恍然大悟

深入理解,源码解析 Integer 神奇的老哥

  之后的话  就很好理解了 ,最后返回int类型的。


 

 老哥.valueOf(String s, int radix)

此方法 监听数字转化异常

    throws NumberFormatException

  • 第一个参数  是大兄弟们要转化的字符串   那如果说  字符串中没有数字的话  是不是就要报异常了   这个很好理解   
  • 第二个参数  应该有点熟悉把   这个就是咱们的base 字符串需要以几进制的形式显示。

实现分析

public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
}

首先将字符串通过parseInt转为了int类型的

public static Integer valueOf(int i) {

  // assert 关键字断言   断言  assert关键字需要在运行时候显式开启才能生效,否则断言就没有任何意义  同时  如果断言失败的 那么整个系统都会崩溃  不懂的话  可以通过链接去看看

  https://blog.csdn.net/yangjiachang1203/article/details/52351880

  assert IntegerCache.high >= 127;
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

 // 老哥自带的缓存

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
  // static 静态代码块可知缓存的初始化是在第一次使用的时候。 通过 VM 参数 -XX:AutoBoxCacheMax=可以配置缓存的最大值。 在 VM 初始化期间, 缓存最大值 high, 可能被保存在 sun.misc.VM class 的私有系统属性里。
    static {
        // high value may be configured by property
        int h = 127;
   
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            int i = parseInt(integerCacheHighPropValue);
            i = Math.max(i, 127);
            // Maximum array size is Integer.MAX_VALUE
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        }
     // 初始化的时候  最大的值为high 127
        high = h;
     // 这个时候  缓存帮我们new一个对象  范围大小 -128 ~ 127  直接说结论把 : 当数据为 -128 ~ 127 的时候  提供了缓存服务  可以直接在缓存中拿数据 
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
    }

    private IntegerCache() {}
}

 


 

关于有些老哥和int的区别可以看一下这个 (个人推荐)

https://blog.csdn.net/wangyang1354/article/details/52623703