构造函数
1 | private final int value; |
从构造方法中我们可以知道,初始化一个 Integer 对象的时候只能创建一个十进制的整数。
Integer 转 String
转化为 16 进制/8 进制/2 进制方法如下:
1 | public static String toHexString(int i) { |
可以看到其实都是调用toUnsignedString0
方法,并传入自己的位数,例如16 = 2^4
所以传入 4,8 = 2^3
传入 3,2 = 2^1
,传 1。
toUnsignedString0(int val, int shift)
1 | private static String toUnsignedString0(int val, int shift) { |
numberOfLeadingZeros(int i)
1 | public static int numberOfLeadingZeros(int i) { |
这个函数会返回 i 在二进制中第一个 1 前 0 的长度。如果 i 为负数,返回 1。如果 i == 0 ,返回 32。
具体算法其实就是参考二分的思想:
- 通过右移 16 位判断高十六位是否为 0,如果为 0,n+=16,再将低十六位前移。
- 右移 24 位,如果上一步执行了,那么这一步其实是判断高 24 位是否为 0,如果上一步未执行,那么这里是判断高 8 位是为 0。
- 通过这样不断二分,逐渐确认了 i 为正数时,0 的数量
n -= i >>>31
:如果 i 为正数,右移 31 位的结果为 0,无影响;如果 i 为负数,前面的判断就不会执行,n=1,而 i 右移 31 位的结果就是 1。所以最后返回1-1 = 0
.
formatUnsignedInt(int val, int shift, char[] buf, int offset, int len)
1 | static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) { |
通过这个方法,将 val 转化为目标进制数组。模拟的就是我们进行进制转化的方法。例如 10 转化为 2 进制的计算过程:
1 | 10 % 2 = 0,10 / 2 = 5 即最后一位是0,下一步用5计算 |
通过很精妙的二分法和位运算从而得到了目标进制的结果。
toString()
Integer 中有一个默认的十进制转化和一个其余进制转化方法。
1 | // 默认转化方法 |
一下内容来自Java 源码学习系列(三)——Integer
分析两个问题:
- 为什么在 getChars 方法中,将整型数字写入到字符数组的过程中为什么按照数字 65536 分成了两部分呢?这个 65535 是怎么来的?
- 在下面两段代码的方法二中,在对 i 进行除十操作的过程中为什么选择先乘以 52429 在向右移位 19 位。其中 52429 和 19 是怎么来的?
1 | 方法一: |
1 | 方法二 |
使用 num1,num2,num3 三个变量代替 65536,52429,19 三个数字,便于后面分析使用。
先明确两点:
移位的效率比直接乘除的效率要高
乘法的效率比除法的效率要高
r = i - ((q << 6) + (q << 5) + (q << 2));
表示的其实是
1 | r = i - (q * 100) |
q = (i * num2) >>> (num3);
中,>>>表示无符号向右移位。代表的意义就是除以2^num3(524288)
。 所以q = (i * 52429) >>> (16+3);
可以理解为:q = (i * 52429) / 524288;
。那么就相当于q = i * 0.1
也就是q = i/10
,这样通过乘法和向右移位的组合的形式代替了除法,提高效率。
再来回答上面两个问题中,部分一和部分二中最大的区别就是部分一代码使用了除法,第二部分只使用了乘法和移位。因为乘法和移位的效率都要比除法高,所以第二部分单独使用了乘法加移位的方式来提高效率。那么为什么不都使用乘法加移位的形式呢?为什么大于 num1(65536)的数字要使用除法呢?原因是 int 型变量最大不能超过(2^31-1)。如果使用一个太大的数字进行乘法加移位运算很容易导致溢出。那么为什么是 65536 这个数字呢?第二阶段用到的乘法的数字和移位的位数又是怎么来的呢?
我们再回答第二个问题。
既然我们要使用 q = (i * num2) >>> (num3);的形式使用乘法和移位代替除法,那么 n 和 m 就要有这样的关系:
1 | num2= (2^num3 / 10 +1) |
只有这样才能保证(i * num2) >>> (num3)结果接近于 0.1。
那么 52429 这个数是怎么来的呢?来看以下数据:
1 | 2^10=1024, 103/1024=0.1005859375 |
超过 22 的数字就不列举了,因为如果 num3 越大,就会要求 i 比较小,因为必须保证(i * num2) >>> (num3)的过程不会因为溢出而导致数据不准确。那么是怎么敲定 num1=65536,num2= 524288, num3=19 的呢? 这三个数字之间是有这样一个操作的:
1 | (num1* num2)>>> num3 |
因为要保证该操作不能因为溢出导致数据不准确,所以 num1 和 num2 就相互约束。两个数的乘积是有一定范围的,不成超过这个范围,所以,num1 增大,num2 就要随之减小。
我觉得有以下几个原因:
1.52429/524288=0.10000038146972656 精度足够高。 2.下一个精度较高的 num2 和 num3 的组合是 419431 和 22。2^31/2^22 = 2^9 = 512。512 这个数字实在是太小了。65536 正好是 2^16,一个整数占 4 个字节。65536 正好占了 2 个字节,选定这样一个数字有利于 CPU 访问数据。
不知道有没有人发现,其实65536* 52429
是超过了 int 的最大值的,一旦超过就要溢出,那么为什么还能保证(num1* num2)>>> num3
能得到正确的结果呢?
这和>>>有关,因为>>>表示无符号右移,他会在忽略符号位,空位都以 0 补齐。
一个有符号的整数能表示的范围是-2147483648 至 2147483647,但是无符号的整数能表示的范围就是 0-4,294,967,296(2^32),所以,只要保证 num2*num3 的值不超过 2^32 次方就可以了。65536 是 2^16,52429 正好小于 2^16,所以,他们的乘积在无符号向右移位就能保证数字的准确性。
String 转 Integer
1 | // 默认使用十进制转化 |
valueOf
1 | // 默认使用10进制转换 |
缓冲区代码如下:
1 | private static class IntegerCache { |
缓冲区中含有一个静态代码块,会在 Integer 加载时执行。
也就是说,默认情况下:当 Integer 被加载时,就新建了-128 到 127 的所有 Integer 对象并存放在 Integer 数组 cache 中。
而在 valueOf 代码中,当调用 valueOf 方法时,如果参数的值在-127 到 128 之间,则直接从缓存中返回一个已经存在的对象。如果参数的值不在这个范围内,则 new 一个 Integer 对象返回。
所以,当把一个 int 变量转成 Integer 的时候(或者新建一个 Integer 的时候),建议使用 valueOf 方法来代替构造函数。或者直接使用Integer i = 100
;编译器会转成Integer s = Integer.valueOf(10000);
。
hashCode()和 equals()
1 |
|
如上,原来 Integer 类型的 hashCode 就是这个数的值。这是我没想到的。
1 | public boolean equals(Object obj) { |
转化为基础类型直接比较。