深入理解,源码解析 Integer 神奇的老哥
咱们对这个Integer封装类并不陌生,就连初学者都了解 ,下面咱们就解剖一下这个神奇的老哥
JDK源码 (在java.lang包下面)
- 首先这老哥继承一个Number 类 ,这个类的总体作用就是对数字的处理, 这个实现了Serializable(序列化接口),内置抽象方法(intValue,longValue,floatValue,doubleValue)以及byteValue,shortValue方法。
- 其次实现比较接口 Comparable ,其泛型是老哥,此接口对实现它的每个类的对象强加一个整体排序。 这个排序被称为类的自然排序 ,类的compareTo方法被称为其自然比较方法,相当于java的
Arrays.sort方法,同时
不需要指定一个comparator 有兴趣可以看一下此接口和comparator 的区别,老哥就有了自己和自己本身类型比较能力。 - 老哥是个final类 不能被继承,所以你见过老哥的儿子和孙子吗?
肯定会有些大兄弟看到这玩意会懵逼的 其实这两个玩意就是2个补码,不要那么紧张
这里的话 我就不解释了 给大兄弟们一个链接(因为我懒的看 )https://blog.csdn.net/u011531425/article/details/80712160
最后得出来 的结论 MIN_VALUE = -231-1 MAX_VALUE = 231-1 这是老哥的范围
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()); // trueisPrimitive方法 确定指定
类
对象表示一个基本类型 ,有九个预定类
对象代表八个基本数据类型和void。 这些是由Java虚拟机创建,并且具有相同的名称为他们所代表的基本类型,即boolean
,byte
,char
,short
,int
,long
,float
和double
并且是该方法返回
true
的唯一类
对象结论:
c1和c2的虚拟机类对象是一样的,Integer在虚拟机中还是拆箱为int。
老哥.toString(int i, int radix)
View Code
- 此方法的第一个int i 是大兄弟要转换的参数 。 第二个参数是该参数是以几进制来显示 该方法返回值为String老朋友。
实现分析
- 首先判断了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 |
|
如果正好等于10的话 那么直接调用toString(i)方法。
View Code
首先判断是否等于Integer.MIN_VALUE 如果等于的return -2147483648 说明老哥的最小值就是这个
其次判断是否小于0
1 |
|
View Code
1
stringSize方法主要判断大兄弟当前传的值 是几位数字 你可以试一下 如果当i等于
2
的时候 最后size为 当大于等于
10
的时候 size为
2
然后就是看比较关键的部分了 getChars(i, size, buf); i为大兄弟你们传的值 size是值的位数 buf是之后的存储的地方
View Code
View Code
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];
}
疑问:为什么要和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;
}
这里将代码计算一下
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)
View Code
此方法 监听数字转化异常
throws NumberFormatException
- 第一个参数 是大兄弟们要转化的字符串 那如果说 字符串中没有数字的话 是不是就要报异常了 这个很好理解
- 第二个参数 应该有点熟悉把 这个就是咱们的base 字符串需要以几进制的形式显示。
实现分析
// 首先判断字符串是否为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");
}
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);
}
那咱们来举个例子(走debug)
public static void main(String[] args) { Integer a = 101; System.out.println(a.parseInt("600", 10)); }
我就那郁闷 600的第一个字符应该是6的啊 怎么会是54呢 然后我想了一下 我恍然大悟
之后的话 就很好理解了 ,最后返回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