上回说到了 字符类型 char, 作为基本类型之一, char 的底层实现对于 string 等有的关键的决定因素. 至于基本类型,难点不多,我们不在叙述了,这次我们讲 另一种类型 --- <font color=red>封装类型</font>
Java 有 8 种基本类型,每种基本类型都有一个对应的包装类型. 包装类又是啥呢?
包装类指的是 Java 的类,内部有实例变量,保存了与之相对应的基本类型的值,这些类有类方法,类变量和其他的实例方法.
基本类型 | 包装类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
因为包装类型基本相同,我们就以 Integer 和 Character 这 2 个有代表性的包装类讲下。
Integer
对于包装类来说,我们经常用到的一个就是自动装箱和拆箱,当然由于 Java 编译器的问题,不需要我们手动来操作,不过这里给大家顺便解释下
基本类型到包装类型的过程,我们一般称之为装箱 ,而将包装类型转换为基本类型的过程,我们称之为拆箱
比如
/**
* 自动装箱
*/
// 示例 1
Integer i = 100;
// 示例 2
Integer autoboxing = 12;
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
/**
* 自动拆箱
*/
// 示例 1
int a = i;
// 示例 2
int sum = 0;
for (Integer i : list) {
// Integer 不能直接进行运算符操作
// 这里属于自动拆箱
if (i % 2 != 0) {
sum += i;
}
}
在我们写了上述自动拆箱、装箱的代码后,Java 编译器会帮我们把 代码替换为valueOf 相关的代码
比如:
Integer i = 10;
// 会被替换为 Integer i = Integer.valueOf(10);
当然,我们的 Integer 也有构造方法
Integer i = new Integer(10);
当然,强烈建议用 valueOf 方法,毕竟很直观的来看,构造方法需要用到内存来新建对象,而且从 Java9 开始,已经被标记 过时了。
话不多说,我们来看源码。(JDK 版本 1.8,源码只提供了我们作为研究的代码)
public final class Integer extends Number implements Comparable<Integer> {
/**
* 最小值 -2^31
*/
@Native public static final int MIN_VALUE = 0x80000000;
/**
*
* 最大值 2^31-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
/**
* 字符串表示数字的所有字符
*/
final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
/**
* 返回一个基于16进制的 字符串
*/
public static String toHexString(int i) {
// 这里的数字 4 就是 16 进制的代指
//上篇我们的char 类型曾经研究过该函数
return toUnsignedString0(i, 4);
}
/**
* 返回一个基于 8 进制的 字符串
*/
public static String toOctalString(int i) {
// 这里的数字 3 就是 8 进制的指代
//上篇我们的char 类型曾经研究过该函数
return toUnsignedString0(i, 3);
}
/**
* 返回一个基于 2 进制的 字符串
*/
public static String toBinaryString(int i) {
//这里的数字 1 就是 2 进制的指代
//上篇我们的char 类型曾经研究过该函数
return toUnsignedString0(i, 1);
}
/**
* 转换数字为指定的进制类型字符串
*/
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
int chars = Math.max(((mag + (shift - 1)) / shift), 1);
char[] buf = new char[chars];
formatUnsignedInt(val, shift, buf, 0, chars);
// Use special constructor which takes over "buf".
return new String(buf, true);
}
/**
* 返回一个指定 int 类型的 Object 对象
*
* @param i 要转换的参数
* @return 10 进制的字符串
*/
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
// 获取输入 int 类型的 位数,比如 10 就是2 ,100 就是3
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
// 这里是上面获取 int 类型的位数方法
// 这是一个比较巧妙的地方,值得学习
static int stringSize(int x) {
for (int i=0; ; i++)
// 每次和 sizeTable 数组进行比较
if (x <= sizeTable[i])
return i+1;
}
/**
* 将 指定的 int 参数放入 char 缓冲区中
* 如果 i == Integer.MIN_VALUE,将会失败
*/
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// 每次迭代生成 2 位 数
// 这里是超出 65536 才执行的
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*10) ...
// 这里 比如 我们的 i 是 55 ,那么 q 计算得出也是 55. 所以 r = 55 - (5 * 10) = 5
// 下面用位移运算是因为在 计算机的计算中,位移运算的效率极快
r = i - ((q << 3) + (q << 1));
buf [--charPos] = digits [r];
i = q;
if (i == 0) break;
}
if (sign != 0) {
buf [--charPos] = sign;
}
}
/**
* 将字符串参数解析为第二个参数指定的基数*中的有符号整数
*
* 重点源码,必会
*
* <p>Examples: 示例如下
* <blockquote><pre>
* parseInt("0", 10) returns 0
* parseInt("473", 10) returns 473
* parseInt("+42", 10) returns 42
* parseInt("-0", 10) returns 0
* parseInt("-FF", 16) returns -255
* parseInt("1100110", 2) returns 102
* parseInt("2147483647", 10) returns 2147483647
* parseInt("-2147483648", 10) returns -2147483648
* parseInt("2147483648", 10) throws a NumberFormatException
* parseInt("99", 8) throws a NumberFormatException
* parseInt("Kona", 10) throws a NumberFormatException
* parseInt("Kona", 27) returns 411787
* </pre></blockquote>
*/
public static int parseInt(String s, int radix)
throws NumberFormatException {
/*
* 在初始化IntegerCache之前,可以在VM初始化期间*早期调用此方法。必须注意不要使用 valueOf方法
*/
if (s == null) {
throw new NumberFormatException("null");
}
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();
//-2147483647 默认取最大整数的取反值
// 在 result 返回之前一直和该 limit 值进行比较,判断是否会溢出
int limit = -Integer.MAX_VALUE;
int multmin;
int digit;
if (len > 0) {
// 比如 我们现在的参数是55,那么 s.charAt(0) 的 char 就是 50.这里是 ASCII
char firstChar = s.charAt(0);
// 判断字符串的起始位置是否有符号操作符
// 如果是 + ,那么 char 是43 ,而 '0'是 48 自然会进入
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
// 如果有符号,那么溢出的值 limit 判断 就变成了 整数的最小负数了 是 -2^31
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 初始值为最大负整数 / 进制数
multmin = limit / radix;
while (i < len) {
// 根据 Character 获取当前字符对应的数字
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
// 判断是否会溢出, 由于计算结果是 带负号的,统一用小于号表示
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
// 这里 判断结果是不是小于 limit 加上 计算得到的Character 数字
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
// result 进行赋值
result -= digit;
/**
* 模拟计算逻辑
* 如果 输入的字符参数是 55,我们一般 paseInt 都是 10进制.
* 第一次 digit = 55 / 10 = 5, result *= radix; ==> result 为0, 所以 0 *= 10 = 0, result 进行赋值 为 -5
* 第二次 digit = 55 / 10 = 5 , result *= radix;==> result 为-5, 所以 -5 *= 10 = -50, result 进行赋值 为 -55
*/
}
} else {
throw NumberFormatException.forInputString(s);
}
// 最后进行判断 是否使用本地方法,如果没有使用 则返回 -result
return negative ? result : -result;
}
/**
* 我们一般使用的 parseInt 方法
*/
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
/**
* 这是一个无符号的转换,使用不是很多 核心还是调用上面的 parseInt
*/
public static int parseUnsignedInt(String s, int radix)
throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
int len = s.length();
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar == '-') {
throw new
NumberFormatException(String.format("Illegal leading minus sign " +
"on unsigned string %s.", s));
} else {
if (len <= 5 || // Integer.MAX_VALUE in Character.MAX_RADIX is 6 digits
(radix == 10 && len <= 9) ) { // Integer.MAX_VALUE in base 10 is 10 digits
return parseInt(s, radix);
} else {
long ell = Long.parseLong(s, radix);
if ((ell & 0xffff_ffff_0000_0000L) == 0) {
return (int) ell;
} else {
throw new
NumberFormatException(String.format("String value %s exceeds " +
"range of unsigned int.", s));
}
}
}
} else {
throw NumberFormatException.forInputString(s);
}
}
/**
* 无符号调用
*/
public static int parseUnsignedInt(String s) throws NumberFormatException {
return parseUnsignedInt(s, 10);
}
/**
* 重点源码,必会
* 这是我们的 常用 valueOf 方法.
* 内部的调用还是 我们上面提到的 parseInt
* 注意 这里的 返回是 Integer 方法,这里涉及到了 Integer 的内部缓存, 就是 Integer.valueOf()
*/
public static Integer valueOf(String s, int radix) throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
/**
* 重点源码,必会
* 内部的调用还是 我们上面提到的 parseInt
* 注意 这里的 返回是 Integer 方法,这里涉及到了 Integer 的内部缓存, 就是 Integer.valueOf()
*/
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
/**
* 重点源码,必会
* <===============================================================================>
* 根据JLS的要求,缓存以支持自动装箱的对象标识语义,以获取* -128和127(含)之间的值
*
* 缓存在首次使用时初始化。缓存*的大小可以由 -XX:AutoBoxCacheMax = <size>选项控制
*/
private static class IntegerCache {
//最低位为 -128
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// 最高位可以由属性配置
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
// 属性赋值
if (integerCacheHighPropValue != null) {
try {
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);
} catch( NumberFormatException nfe) {
// 如果参数不能解析,忽略
}
}
high = h;
//设置缓存数组 size
cache = new Integer[(high - low) + 1];
int j = low;
//给缓存数组进行初始化赋值
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
/**
* valueOf 先在缓存中进行判断,如果有,进行 return
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
/**
* 构造函数
*/
public Integer(int value) {
this.value = value;
}
/**
* 字符串类型的构造函数
*/
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
/**
* 返回一个 byte 类型的值
*/
public byte byteValue() {
return (byte)value;
}
/**
* 返回一个 short 类型的值
*/
public short shortValue() {
return (short)value;
}
/**
* 返回一个拆箱类型的值
*/
public int intValue() {
return value;
}
/**
* 返回一个 long 类型的值
*/
public long longValue() {
return (long)value;
}
/**
* 返回一个 float 类型的值
*/
public float floatValue() {
return (float)value;
}
/**
* 返回一个 double 类型的值
*/
public double doubleValue() {
return (double)value;
}
/**
* 返回一个 基于 value 类型的 10 进制的 字符串
*/
public String toString() {
return toString(value);
}
/**
* hash Code
*/
@Override
public int hashCode() {
return Integer.hashCode(value);
}
/**
* int 类型的 hash code,
*/
public static int hashCode(int value) {
return value;
}
/**
* Integer 的 equals 方法 比较value ,如果 value 相同,则相同
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
/**
* 排序方法
*/
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
/**
* 排序方法
*/
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
由于篇幅有限,不能把所有的源码都写上,不过上面写的都是核心方法,只要了解上面的即可.
下面我们继续来点猛料,看看 Integer 的二进制算法
Integer 的二进制算法
integer 有 2 个 二进制方法进行按位翻转
按位翻转就是左边的位与右边的位进行互换,比如 10 换成 01。
// 16 进制表示
int i = 0x1234321;
// 2 进制输出
System.out.println("2 进制表示 ======>" + Integer.toBinaryString(i));
// 10 进制输出
System.out.println("10 进制表示 ======>" + i);
//--------------------------------------------------------------------
//按位翻转-以字节
int rb = Integer.reverseBytes(i);
// 2 进制输出
System.out.println("以字节翻转后 2 进制表示 ======>" + Integer.toBinaryString(rb));
// 10 进制输出
System.out.println("以字节翻转后 10 进制表示 ======>" + rb);
//按位翻转-以字节
int r = Integer.reverse(i);
// 2 进制输出
System.out.println("以位翻转后 2 进制表示 ======>" + Integer.toBinaryString(r));
// 10 进制输出
System.out.println("以位翻转后 10 进制表示 ======>" + r);
output
2 进制表示 ======> 1001000110100001100100001
10 进制表示 ======> 19088161
以字节翻转后 2 进制表示 ======> 100001010000110010001100000001
以字节翻转后 10 进制表示 ======>558048001
以位翻转后 2 进制表示 ======> 10000100110000101100010010000000
以位翻转后 10 进制表示 ======> -2067610496
/**
* 进行按字节翻转
*/
public static int reverseBytes(int i) {
// 以 int i = 0x1234321 (10 进制 19088161)为例
// i >>> 24 无符号右移,最高字节移动到最低位置
return ((i >>> 24) ) |
// (i >> 8) & 0xFF00 左边第二个字节移动到右边第二个
((i >> 8) & 0xFF00) |
((i << 8) & 0xFF0000) |
((i << 24));
}
/**
* 进行按位翻转
*/
public static int reverse(int i) {
// HD, Figure 7-1
// HD 代表一本书 《Hacker's Delight》(中文版本:《算法心得:高效算法的奥秘》)
// Figure 7-1 代表书中的 7-1 图
// 交换第一位
i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
//交换第 2 位
i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
//交换第 4位
i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
i = (i << 24) | ((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) | (i >>> 24);
return i;
}
上面 reverse 代码看的很晕。其实现原理就是
- 交换相邻的单一位。
- 以 2 位一组进行交换相邻的位置
- 以 4 位一组进行交换相邻的位置
- 。。。。 然后就是 8 位 16位
这里我们用数字进行解释(为避免太深入 二进制转换,不容易理解,我们以 10进制为例)
比如 i = 12345678
- 单一位相邻交换 12 34 56 78 ======> 交换后结果 21 43 65 87
- 以 2 位相邻交换 21 43 65 87 ======> 交换后结果 43 21 87 65
- 以 4 位相邻交换 43 21 87 65 ======> 交换后结果 8765 4321
如上可以看出,10 进制的翻转 效率很一般,当然对于 2 进制来说,效率就杠杠的了,因为 二进制可以在一条指令中交换多个相邻位
reverse 函数为啥写的这么恶心呢?虽然说我们目前使用的不说很多。我们能不能换一种翻转方式?比如第一个和最后一个交换,第二个和倒数第二个交换这种? 因为我们考虑的是10 进制,这样的操作方式是完全 ok 的,但是对于二进制来说,这样是低效的,毕竟没有充分利用 CPU 的性能,reverse 方法就是充分利用 CPU 的性能而做的方法。
循环移位 (按位操作的补充)
Integr 中提供了 2 个循环移位的方法
// 循环左移
// @param i 需要移动的参数
// @param distance 移动的位数
public static int rotateLeft(int i, int distance) {
return (i << distance) | (i >>> -distance);
}
// 循环右移
// @param i 需要移动的参数
// @param distance 移动的位数
public static int rotateRight(int i, int distance) {
return (i >>> distance) | (i << -distance);
}
这里之所以说是 按位操作的补充,是因为我们的普通按位操作,比如 << 向左移动,右边全部以 0 补齐, >> 向右移动,左边 以 0 补齐。
而这里的 循环移动就是补充的位不再是 0 了,比如 左移 1位,右边不再是 0 补齐,而是 原来 向左 移动的位数会到右边。
我们来举个 例子
1010 0001 << 1 就变成了
<font color = red>1</font>0100 001<font color = #3386FF >0</font> 左边的 1 被舍弃了,右边 补充了0
而 我们的循环移动则是
1010 0001 循环左移 1位 变成了
<font color = red>1</font>0100 001<font color = #B8FF33 >1</font> 左边的 1 被放到 右边
在循环移位时,distance 可以是负数 <font color = red>
rotateLeft(val, -distance) == rotateRight(val, distance) </font>这里是等价的
这里我们解释下关于 distance 是负数的问题。如果我们在移位的时候,传入的 distance 是负数。那么就会根据被移位数的长度进行转换,比如 我现在被移位数是 int 类型的值 rotateRight(10, -8).那么就会转换为 rotateRight(10, 32 +(-8)) = (10, 24),然后计算出左移 的值,再计算出右移的值。两者 进行 或 运算即可。
如果有写的不对的地方,请大家留言。