主要属性
- 最基本的是表示包装的基本类型的值:
1
private final int value;
- 与类型相关的常量
1
2
3
4
5
6
7
8
9
10
11// int能表示的最小值
public static final int MIN_VALUE = 0x80000000;
// int能表示的最大值
public static final int MAX_VALUE = 0x7fffffff;
// 代表基本类型int的Class类示例
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
//补码形式的比特数
public static final int SIZE = 32;
//补码形式的字节数
public static final int BYTES = SIZE / Byte.SIZE; - 用来辅助而定义的常量
digits
常量中存的是所有可以用来表示数字的字符,最大包含36进制。
1 | final static char[] digits = { |
DigitTens
和DigitOnes
分别用来获取一个两位数的十位字符表示和个位字符表示。
1 | final static char [] DigitTens = { |
上面几个数组主要是用来在转化数字为字符串时通过查表快速获得对应的位。
sizeTable
主要用来快速判断一个数的位数。
1 | final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999, |
IntegerCache类
1 | private static class IntegerCache { |
Integer内部有一个static
内部类,使用数组缓存了一部分值对应的的包装对象,这部分值的默认范围是[-128
,127
],不过缓存大小可以通过-XX:AutoBoxCacheMax=<size>
参数来控制,该参数可以设置初始化缓存初始化时high
的值,但是low
是不变的,固定为-128
。
缓存中的值在public static Integer valueOf(int i)
方法中使用。
构造方法
1 | public Integer(int value) { |
int->Integer 方法
1 | public static Integer valueOf(int i) { |
首先从判断是否在初始化好的IntegerCache
的缓存范围内,如果在直接返回缓存中的值,否则new
一个返回。
String->int 方法
以下这些方法主要用来将String
类型解析为int
基本类型:
1 | //解析指定进制的字符串转化为int基本类型 |
parseInt方法
1 | public static int parseInt(String s, int radix) throws NumberFormatException{ |
主要逻辑:
- 判断字符串不为空,并且传进来进制参数在2和36之间。
- 处理首位符号
+
或者-
。 - 接下来就是将字符串转化为数字的逻辑。就是我们熟悉的每一位权重乘以权值(也就是进制数)依次相加,如:127转换成十进制
1*10*10+2*10+7*1=127
。- 但是这里的处理方式不同之处在于统一按照负数来计算而不是按照正数来计算,这是为了在计算得到结果后可以直接将符号位加在前面得到结果。而如果采用正数来计算,正数的范围是无法表示
Integer.MIN_VALUE
的相反数的,这样省去了单独处理Integer.MIN_VALUE
的逻辑,非常妙。 - 循环中的两次判断都是为了防止溢出,在相乘或者相减之前提前判断以防止相乘之后溢出导致判断大小出错。
- 但是这里的处理方式不同之处在于统一按照负数来计算而不是按照正数来计算,这是为了在计算得到结果后可以直接将符号位加在前面得到结果。而如果采用正数来计算,正数的范围是无法表示
parseUnsignedInt方法
1 | public static int parseUnsignedInt(String s, int radix) |
这个方法是将字符串按照无符号数来处理
- 如果字符串前有
-
,抛出异常。 - 对于确定在不会超过
int
最大范围内的数(根据位数判断),调用Integer.parseInt
方法处理。 - 有可能超过
int
范围的,调用Long.parseLong
方法解析为long
基本类型,如果解析后的值不超过32
位,强制转换为int
返回,如果超出了32
位,抛出异常。Long.parseLong
方法的逻辑与Integer.parseInt
方法逻辑一致。
String->Integer 方法
以下这几个方法主要用来将String
类型转化为Integer
包装类型。
1 | public static Integer valueOf(String s, int radix) throws NumberFormatException { |
这两个方法首先将String
类型解析为int
基本类型,然后调用valueOf(int i)
返回其包装类型。
int->String 方法
toString方法
以下这几个方法主要用来将int
基本类型转化为String
类型。
1 | // 处理任意进制(2-36),遇到十进制会调用下面一个方法 |
toString(int i, int radix)方法
1 | public static String toString(int i, int radix) { |
- 判断进制,不符合条件按照十进制处理,处理十进制调用
toString(int i)
方法,速度更快。 - 接下来的过程我们也很熟悉。初始化一个
char
数组,然后就是依次取余,得到其对应的char
字符,倒序填充数组,在第一位处理符号位。- 这里同样是统一转化为负数处理来避免
Integer.MIN_MAX
转化为正数时溢出 - 用提前初始化好的数组
digits
快速获查表得对应的字符
- 这里同样是统一转化为负数处理来避免
toString(int i)方法
1 | public static String toString(int i) { |
这个方法和上一个方法思想是一样的,但是用到几个技巧:
int
的高两个字节和低两个自己分开处理,低两个字节一次处理两位,高两个字节一次处理一位。- 取余数时没有用取模运算,而是先做除法,再做乘法。
- 一次处理两位时,先除以100,再乘以100,用原数相减得到余数。乘以100用位运算(
q * 100
=((q << 6) + (q << 5) + (q << 2))
)代替。 - 一次处理一位时,先除以10,再乘以10,用原数相减得到余数。除以10用乘法和位运算(
i / 10
=(i * 52429) >>> (16+3)
)代替,乘以10用位运算(q * 10
=((q << 3) + (q << 1))
)代替。
- 一次处理两位时,先除以100,再乘以100,用原数相减得到余数。乘以100用位运算(
以上位运算替换均是为了加快计算速度。
q * 100
=((q << 6) + (q << 5) + (q << 2))
q * 10
=((q << 3) + (q << 1))
i / 10
=(i * 52429) >>> (16+3)
分别解释一下这三个等式为什么成立:
100 = 2^6 + 2^5 + 2^2
->q * 100 = q * (2^6) + q * (2^5) + q * (2^2)
->(q << 6) + (q << 5) + (q << 2)
10 = 2^3 + 2^1
->q * 10 = q * (2^3) + q * (2^1)
->(q << 3) + (q << 1)
(i * 52429) >>> (16+3)
->(i * 52429) / (2^19)
->(i * 52429) / (2^19)
->(i * 52429) / 524288
, 这个结果在不考虑余数的情况下等于i / 10
解释完正确性之后,来思考几个问题:
- 为什么要用这么难理解的位运算的来代替乘除呢?显然是为了运算效率,几种运算的效率差别分别为:位运算 > 乘法 > 除法。所以用处理高位时用位运算代替乘法,处理低位时用位运算和乘法代替除法,用位运算代替乘法。
- 为什么要将高位和低位分开运算呢?答案也是计算速度上的考量,计算高两个字节时可以一次计算两位加快效率,而计算低两个字节时可以通过用乘法和移位代替除法(上面第三个等式)来加快计算效率。
toUnsignedString方法
以下两个方法将将参数中的i
作为无符号数转换为String
。例如:Integer.toString(Integer.MIN_VALUE) -> -2147483648
Integer.toUnsignedString(Integer.MIN_VALUE) -> 2147483648
1 | public static String toUnsignedString(int i, int radix) { |
这两个方法首先都将i
当成无符号数然后转换为long
型,也就是保留低32
位,高32
位填0
(转换后的long
型值一定为正数)。然后调用Long.toString
方法或者Long.toUnsignedString
方法得到结果。
特殊进制的格式化方法
1 | public static String toOctalString(int i) { |
这几个方法分别是二进制,八进制,十六进制的格式化方法。之所以这这几个进制有单独的方法,是因为他们都是2的幂次,所以可以直接处理其二进制位。例如八进制就是一次处理三位,十六进制就是一次处理四位。
注意这里都是按照无符号数来处理的。
toUnsignedString0()
方法就是通用的处理方法。
- 首先计算最后的值的长度,这里并没有考虑符号的问题,统一是按照无符号数来处理的。
((mag + (shift - 1)) / shift)
相当于(mag / shift)
向上取整。numberOfLeadingZeros
计算的是前导0的个数,后面会说。 formatUnsignedInt
这个方法比较好理解,就是一次处理shift
位,方法是与(1 << shift - 1)
相与,然后右移,处理下一个shift
位。
有关位运算的一些方法
代码中的一些位运算方法注释中的
HD
指的是《Hacker’s Delight》一书,其中有解释了大量位运算相关的技巧,中文译本《算法心得:高效算法的奥秘》
highestOneBit | lowestOneBit
1 | public static int highestOneBit(int i) { |
这两个方法返回都是2的幂(即二进制表示中至多只有一位是1),两个方法分别表示参数i
的二进制表示中只保留最高位1
和只保留最低位1
后的值。
例如:14
的二进制表示为1110
(省略高位0
),highestOneBit(14) = 8
,即1000
,lowestOneBit(i) = 2
,即0010
。
如果将参数i
看成二进制的话,就是一个返回最高位1
的权值,一个返回最低位1
的权值。将14
表示为2
的幂的和:14 = 8 + 4 + 2
, highestOneBit
可以得到第一个数,lowestOneBit
可以得到最后一个数。
但是注意两点:
- 负数的时候,
highestOneBit
始终返回-2147483648
,因为最高位始终为符号位1
。 - 参数为0时,两个方法返回都是
0
。
这两个方法有什么实际用途呢?
highestOneBit
得到的值等于将参数x
下调为小于等于x
且与之最接近的2
的幂
我们来看一下具体是怎么实现的。highestOneBit
的思想是将最高位的1
一直向右传播,直到其右方全是1
,然后将其右边的0
全部‘减掉’。示例:
1 | i 01000000 00000110 11100011 11011100 |
lowestOneBit
思想是来自于相反数的补码表示,一个数的相反数的补码表示为原数的补码表示按位取反,再加1
,加1
之后,最右边的就会进位一直到第一个0
也就是原数第一个1
的位置。最后相与,因为前面的位都是相反的,所以都为0
,而最低位1
之后都相等。看个例子就明白了。
1 | 132416 00000000 00000010 00000101 01000000 |
numberOfLeadingZeros | numberOfLeadingZeros(前导0和后导0的个数)
1 | public static int numberOfLeadingZeros(int i) { |
这两个方法是用来计算前导0
的个数和后导0
的个数。具体实现方式使用了二分的思想。numberOfLeadingZeros
方法中,首先判断前16位(i >>> 16
)是不是全为0
,如果不是则判断前8
位,如果是则计数增加16
并且左移16
位之后再判断前8
位(这个时候实际判断的是原数的17-24
位),然后依次判断前4
位,前2
位,前1
位。numberOfTrailingZeros
方法中,不同的是计数初始值为31
,首先判断后16
位(i << 16
)是不是等于0
,如果是则判断最后24
位,如果不是则计数减16
并且左移16
位后判断最后24
位(这个时候判断的实际是原数的最后8
位),依次判断后28
,30
,31
位。
我们来看下其中的细节:numberOfLeadingZeros
为什么最后一步不太一样呢,其实原本的样子应该是这样的:
1 | ...... |
可以将最后一个分支转化为n = n + 1 - (i >>> 31)
,如果将n
初始化为1
,加法也可以省去。为了计算效率,真是操碎了心啊。numberOfTrailingZeros
最后一步也不太一样,其实原本的样子是这样的:
1 | ...... |
用i >>> 31
代替了判断是否为0
和加1
操作。
bitCount(计算1的位数)
1 | public static int bitCount(int i) { |
bitCount
方法用来判断二进制表示中1
的个数。
先来说一下其原理,基本的思想是分治法,首先将相邻的两位相加结果存放到一个宽度为2
的位段,此时这个两位值表示的就是原先两位中1
的个数;然后将相邻两个位段相加结存到一个宽度为4
的位段中,此时这个四位值表示是原先四位中1
的个数;依次处理8
位,16
位,最后得到的结果就是原数中1
的个数。
可以用下面这张图来解释:
再来说下代码,首先,代码原型是这样:
1 | x = (x & 0x55555555) + ((x >>> 1) & 0x55555555); |
这个代码是如何变成上面的那个四不像代码的呢?其实进行了一些优化:
- 第一步: 这一步有点看起来像黑魔法,将加法操作变为了减法操作。
我们先只看两位操作,原操作相当于x & 0x01 + ((x >>> 1) & 0x01)
,而这个黑魔法相当于x - (x >>> 1 & 0x01)
。
如何证明这两个操作是等同的呢?
假设两个位从左到右分别为b0,b1
,也就是原先的值x
等于b0 * 2^1 + b1 * 2^0 = 2b0 + b1
,而我们最后想要的值其实是b0 + b1
,b0 + b1 = (2b0 + b1) - b0
,就等于x - (x >>> 1 & 0x01)
。32
位一起处理就是上面的结果i - ((i >>> 1) & 0x55555555)
- 第二步:原样不变,没有优化。
- 第三步:因为求和操作的结果最多为8,不会超过四位,所以可以先相加后位与
- 第四第五步:相加结果不会向相邻位段进位,所以可以先相加后位与,但是因为最多只有32个
1
占6位,所以这两步可以的消位操作可以放到最后一起做。
rotateLeft | rotateRight (循环左移和循环右移)
1 | public static int rotateLeft(int i, int distance) { |
这两个方法返回一个int值循环左移和循环右移后的值。distance
参数中除了最低的五位外都会被忽略(相当于与0x3f
相与),可以认为范围始终在0-32
之间,这与java语言规范
中<<
,>>
,>>>
指令的语义规范一致。
If the promoted type of the left-hand operand is int, then only the five lowest-order
bits of the right-hand operand are used as the shift distance. It is as if the right-hand
operand were subjected to a bitwise logical AND operator & with the
mask value 0x1f (0b11111). The shift distance actually used is therefore always in
the range 0 to 31, inclusive.
代码很好理解,左移就是左移d
位后的值与右移32-d
后的值相与,就相当于将左移溢出的部分补全到右边的位置;右移同理。那么代码中的(i >>> -distance)
和(i << -distance)
是怎么回事呢?其实-distance & 0x1f
就等于32 - distance
。所以这里可以直接用-distance
来做运算。
reverse(比特翻转)
1 | public static int reverse(int i) { |
reverse
方法将int
型值的二进制补码表示翻转顺序。思想也比较简单,还是分治思想,首先将以1bit
为单位翻转顺序然后以2bit
为单位翻转顺序,依次进行。
为什么最后一句不太一样,因为到了按照8bit
操作时,直接按照byte
来操作了。
reverseBytes(字节翻转)
1 | public static int reverseBytes(int i) { |
将int
型值的二进制补码表示以byte
为单位翻转。
signum (计算符号位)
1 | public static int signum(int i) { |
signum
方法用来计算一个int
型值的符号,正数返回1
,负数返回-1
,0
返回0
。
分别考虑|
两边的式子(i >> 31)
在i>0
时返回0
,i<0
时返回-1
,i=0
时返回0
(-i >>> 31)
在i>0
是返回1
,i<0
时返回0
,i=0
时返回0
可见,两个式子分别能满足正数和负数的返回值要求,将两者相与便都可以可以覆盖正数和负数。
无符号除法
1 | public static int divideUnsigned(int dividend, int divisor) { |
divideUnsigned
和 remainderUnsigned
分别是求两个int
数作为无符号数相除得到的商和余。原理是将两个数转换为无符号long
型(高32位填0
)相除和求余。
获取指定的int型系统变量
TODO…