啃碎JDK源码(二):Integer

上次我们已经对String类源码做了一个简单的总结,感兴趣的小伙伴可以去看一下啃碎JDK源码(一):String,今天来看看Java的Integer包装类。

先来看看Integer实现了哪些接口

public final class Integer extends Number implements Comparable<Integer> {

可以看到Integerfinal修饰,代表不可被继承,继承Number类实现Comparable接口。

private final int value;
public static final int MIN_VALUE = 0x80000000;//最大值
public static final int MAX_VALUE = 0x7fffffff;//最小值
public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

可以看出使用了int类型的value来存储值,并且定义了Integer的最大值为2^31-1,最小值为-2^31Integer的基本数据类型为int

先来看一下Integer的两个构造函数:

public Integer(int value) {
    this.value = value;
}
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}

那么这个parseInt方法是干啥用的呢?

方法parseInt(String s,int radix)的目的是输出一个十进制数。

比如:parseInt(1010,2)
意思就是:输出2进制数1010在十进制下的数。

我们平时用到Integer.parseInt("123");其实默认是调用了int i =Integer.parseInt("123",10);

下面是源码:

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");
        }
        //判断基数是否在 2~36之间
        if (radix < Character.MIN_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " less than Character.MIN_RADIX");
        }

        if (radix > Character.MAX_RADIX) {//36
            throw new NumberFormatException("radix " + radix +
                                            " greater than Character.MAX_RADIX");
        }

        int result = 0;
        boolean negative = false;//是否为负数,默认为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,限制变为int的最小值
                    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防止数据溢出 
            *如果是正数就是-2,147,483,64 
            *如果是负数就是-2,147,483,64 **/
            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;
    }

valueOf

接下来来看一个比较经典的问题,看下面代码:

Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;
System.out.println(a == b);
System.out.println(c == d);

你认为这里是四个不同的对象,那么输出应该都是flase?

不,输出结果为:

true
false

通过javap -c/javap -verbose 命令可以查看字节码;红色圈圈里就是我们jdk5之后的基本类型的自动包装的字节码实现,可以看出,此处是调用了Integer.valueOf(..)方法的:说白了就是Integer a = 100 等价于Integer a = Integer.valueOf(100)

image.png

JDK1.5之后,java提供了自动装箱和自动拆箱的功能。自动装箱也就是调用了Integer类的一个静态方法valueOf方法,那我们来看看源码是如何实现的:

image.png

看看IntegerCache是个什么东西:

image.png

可以看到IntegerCache是一个静态内部类,low的值已经写死-128,而high的值由你的虚拟机决定sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"),既然是一个参数也就意味着你可以动态设置。然后在循环中将low - high之间数字的装箱后方法cache[]这个Integer类型的数组中。这样就完成了缓存。

因为当我们调用valueOf方法时传入-128到127之间的数字时,Integer会给我们返回同样的对象,记住这一点以后面试的时候也可以和面试官吹吹牛逼了!

image.png

接下来我们来看看Integer实现的equalshashcodetoString等方法:

equals 和 hashcode

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

可以看到它会先判断类型是否符合,然后进行拆箱比较操作。

看下hashcode方法:

@Override
public int hashCode() {
    return Integer.hashCode(value);
}

public static int hashCode(int value) {
    return value;
}

可以看到hashcode直接返回本身的int值。

toString

IntegertoString方法 是我认为一个比较有趣的地方:

image.png

看下toString(int i)源码:

image.png

在上面用到了stringSize方法,就是求这个Integer数的长度,我们来看看他是如何实现的:

image.png

可以看到这段代码在计算Integer数长度时,构建了一个一维数组,然后拿x与数组每个值进行比较。

还有一个getChars方法是获取数值对应的字符串,其中有两个地方使用了非常巧妙的方式来进行除法运算和取余运算。在计算机中,a/ba%b相比较位运算,都是比较费时的计算的。下面来看看jdk中是如何优化计算的:

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

其中有一行代码:

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

上面这行公式约等于 q * 0.1,也就是说使用乘法计算比使用除法高效。

思路是这样:

当 i >= 65536时,是每两位取出数字,i /= 100,例如 i = 567235474,
(1)先取最后两位 7 和 4 放入buf数组中,i = 5672354,buf = { , , , , , , , '7', '4'};
(2)再取最后两位 5 和 4 放入buf数组中,i = 56723,buf = { , , , , , '5', '4', '7', '4'};
当 i < 65536 时,跳出循环,采用每一次取出一位数字,也就是 i /= 10
(3)取最后一位 3 放入buf数组中,i = 5672,buf = { , , , , '3', '5', '4', '7', '4'};
(4)取最后一位 2 放入buf数组中,i = 567,buf = { , , , '2', '3', '5', '4', '7', '4'};
(5)取最后一位 7 放入buf数组中,i = 56,buf = { , , '7', '2', '3', '5', '4', '7', '4'};
(6)取最后一位 6 放入buf数组中,i = 5,buf = { , '6', '7', '2', '3', '5', '4', '7', '4'};
(7)取最后一位 5 放入buf数组中,i = 0,buf = { '5', '6', '7', '2', '3', '5', '4', '7', '4'},结束。

总结

关于Integer类暂时介绍到这里,有关其它的包装类Long等源码部分也是类似的,后续就不再介绍,有兴趣的小伙伴可以去研究一下。

image

区区码农

879 声望
1.4k 粉丝
0 条评论
推荐阅读
数据库与缓存双写一致性
先做一个说明,从理论上来说,有两种处理思维,一种需保证数据强一致性,这样性能肯定大打折扣;另外我们可以采用最终一致性,保证性能的基础上,允许一定时间内的数据不一致,但最终数据是一致的。

超大只乌龟15阅读 3.7k评论 1

Java12的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft63阅读 11.9k

Java8的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft32阅读 24.6k评论 1

一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。知识点详解秒杀系统架构图秒杀流程图秒杀系统设计这篇文章一万多...

王中阳Go33阅读 2.5k评论 1

封面图
Java11的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft28阅读 15.4k评论 3

Java5的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft13阅读 20.4k

Java9的新特性
Java语言特性系列Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java...

codecraft20阅读 14.5k

区区码农

879 声望
1.4k 粉丝
宣传栏