用源码说话:从jdk源码角度了解Ingter
本文只介绍部分源码,java源码的思想太深啦 这里我们只学习部分常见的功能的底层原理(纯属个人胡说八道)欢迎大牛看不下去前来吐槽。
本人能力有限,源码的篇幅也过于长,所以只从下面几个点去个介绍下常用的:
- Integer类的大概描述
- Integer的构造函数,及内存模型
- Integer的parseInt(String s)方法:将字符串转换成数值
- 数字反转函数:reverse(int i)
- Integer自己实现的:toString(int i)
首先从类定义来看:
public final class Integer extends Number implements Comparable<Integer>
Integer类是一个final类,说明该类是不可继承基本上所有的数据类型相关的类都是不可以继承的。并且继承Number类
public abstract class Number implements java.io.Serializable
Number类实现啦可序列化接口,所以Integer类也可以序列化,Number的子类是提供数据类型转换方法的,如:Byte、Short、Integer、
Long、Float 和 Double 等,表示将数值转化为byte、short、int、long、
float和 double 的。
Integer类还实现啦Comparable接口,这个主要是定义比较用的。
Integer可以实例化出一个对象,是int的包装类,在jdk1.5发布的新特性:自动拆装箱;使得Integer变成一个‘字面量’(常见对象都是new出来的:Integer in = new Integer(5);必须调用类的方法才能给对象赋值。字面量是可以直接赋值的数据类型:int i = 5;)一般来说只有八大基本数据类型是字面量可以直接赋值。java中新添加啦个String类也是个字面量。这个以后我们学习String源码的时候再来探究。
由于jdk6的自动拆装箱的新特性使得包装类也可以拥有字面量可以直接赋值的功能啦(Integer in =5;)自动拆装箱其实就是在代码编译时虚拟机自动帮你将:Integer in =5;解释成:Integer in = new Integer(5); 这成为解释执行。关于自动拆装箱就做一个简述,有兴趣的朋友可以查看相关博客肯定写的比我的好。
* Integer是int的包装类,所以int的大小范围就是就是它的大小
* int在内存中占四个字节 32位 首位表示正负;
* int的最小值是:1-2^31 -> -2147483648
*/
public static final int MIN_VALUE = 0x80000000;
/**
* int的最大值是:2^31-1 -> 2147483648
*/
public static final int MAX_VALUE = 0x7fffffff;
下面我们从Integer的构造方法开始:
private final int value;//当前的int值
public Integer(int value) {
this.value = value;
}
public Integer(String s) throws NumberFormatException {
//使用字符串构建Integer对象,如过s不能转化成数字则抛出 NumberFormatException
//如果可以转换则默认按十进制进行转换。
this.value = parseInt(s, 10);
}
上面有个parseInt方法可以把字符串自动转换成十进制数很神奇有没有 。下面我们来看看parseInt方法源码:
public static int parseInt(String s, int radix)throws NumberFormatException
{
//s 是传进来的字符串;radix 是基数也就是进制数(默认是10进制)
if (s == null) {//判空操作不解释
throw new NumberFormatException("null");
}
//Character.MIN_RADIX int类型常量值: 2 判断进制数不得小于最小进制数为二进制
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
//Character.MAX_RADIX int类型常量值: 32 判断进制数不得大于最大进制数为三十二进制
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
int result = 0;
//正数的边界值为1至0x7fffffff;负数的边界值为-1至0x80000000;
boolean negative = false;//记录所得值是否正负 false:正数 true:负数
int i = 0, len = s.length();//字符串偏移指针 记录字符串的长度
int limit = -Integer.MAX_VALUE;//最大边界值
int multmin;//最大边界值右移一位
int digit;
if (len > 0) {//长度大于0时 截取第一个字符
char firstChar = s.charAt(0);
if (firstChar < '0') { // 可能是 "+" or "-"
if (firstChar == '-') {
negative = true;//将符号状态标记为负数
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);
if (len == 1) //如果既不是 "+" or "-" 就抛出异常
throw NumberFormatException.forInputString(s);
i++;//移位
}
multmin = limit / radix;//防止循环中result*=radix不会发生越界
while (i < len) {
// 积累负的最大限度地避免值溢出
//用字符和进制数计算出对应的数值(实际是取字符的ascII去计算的)
//将字符转换成对应进制的整数
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;
}
上面代码看得很晕的话可以看看流程图 思维会清晰很多
我们在学习Integer时经常会遇到下面的代码
public static void main(String[] args){
Integer i1 = new Integer(5);
Integer i2 = new Integer(5);
system.out.println(i1.equals(i2));
system.out.println(i1==i2);
}
相信有点基础的朋友肯定可以一样得出答案:true false;其内存模型大概是这样的:
栈中存储的都是堆中地址的引用,equals比较内存中的值,但两个对象中的值相等equals就为true,但“==”比较的是栈中引用内存模块的地址。
上面的问题只是个小case,来看看下面这道你会不会,知道原理吗?
public static void main(String[] args){
Integer i1 = 5;
Integer i2 = 5;
system.out.println(i1.equals(i2));
system.out.println(i1==i2);
}
答案是:true true;相信不少人处于懵逼状态,说好啦的自动拆装箱呢?还是来先看看内存模型是怎样的吧:
相信很多人会问为什么呢?那我们用源码来说说吧:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];//静态缓存Integer数组
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 = h;
cache = new Integer[(high - low) + 1];//将-128到127缓存到cache中
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
很明显这是个静态内部类,基础比较好的的朋友可以看出静态类中的静态代码块会优先执行。所以当程序运行时Integer类就缓存一个字节长度的数(-128到127)。我们直接使integer类指向5 程序会优先去缓存中找到相应的值。所以两个引用会指向同一个内存块。但如果你使Integer i3=128;Integer i4=128;再比较一下答案肯定是false。有兴趣的朋友可以尝试下。再稍微提一下这种内部构建一个共享类的设计应用到啦享元设计模式:享元设计的理念就是跟名字一样主要应用与共享资源,节约资源。jdk中的包装类中基本都用到过这个设计模式来缓存一些共享的数据,有兴趣的朋友可以去了解下。
public static int reverse(int i) {
// HD, Figure 7-1
i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
i = (i << 24) | ((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) | (i >>> 24);
return i;
}
这个函数通过对数进行与 或操作得出返回值,通过将二进制位在指定值的倒转顺序中获得的值的二进制位的补数的二进制表示形式倒过来,得到所得到的值,我们可以使用这个函数对数据做简单的加密解密的功能。
toString方法相信大家都不陌生吧,所有类的基本Object类中实现啦toString方法:将类的内存地址对应的hashCode ;如果子类想实现toString出自己相应的数据,就必须重写此方法;Integer类重写啦自己的toString方法,就是将整数类型的数转换成String类型;虽然功能很简单,这种从底层实现出来的原理值得我们学习一下。
public String toString() {
return toString(value);
}
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
从上面的源码我们可以看出它是使用一个三目运算调用stringSize方法,最后调用getChars()。三目运算在jdk底层运用的比较多,大家可以多使用三目运算去代替if-else语句。下面我们看看相应方法是如何实现的:
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
static int stringSize(int x) {//主要功能是检测有没有超出长度产生越界或溢出现象
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;//返回长度,因为下标是从0开始所以返回时要+1
}
static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;
if (i < 0) {
sign = '-';
i = -i;
}
// 每次迭代生成两个数字
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];
}
// 为更小的数字而下降到快速模式
// 保持(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;
}
}
final static char[] digits = {//缓存0-9 a-z 使用下标来进行匹配取值
'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'
};
从上面源码可以看出Integer的toString方法大致是把数字切分成单个数字,然后得出相应数值去digits静态数组中匹配到相应的字符,拼接成一个字符串数组,然后调用String的构造函数来完成的。
java的设计思想博大精深,以我的能力只能领悟到这,以后有机会再来补充。