这篇文章继续java.lang包下的源码学习,笔者也是找了几个比较常用的来阅读。下面针对Integer、Long、Double这样的基本类型的封装类,记录一些比较经典、常用的方法的学习心得,如toString()、parseInt()等。


java.lang.Integer

1. public static String toString(int i)

  说起toString(),这是从Object类中继承过来的,当然,如果我们不重写,那么返回的值为ClassName + "@" + hashCode的16进制。那么,如果是我们自己,要怎么实现呢。笔者这里想到的办法是循环对10求余,得到对应的char型数组后就得到了字符串。那么我们来看看JDK中高手是怎么写的,以下是10进制的【10进制的与其它的是不一样的】。

 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(0, size, buf);
}
 static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0; if (i < 0) {
sign = '-';
i = -i;
} // Generate two digits per iteration
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 << 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 [] DigitTens = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
'4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
'6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
'8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
};
//笔者注:取出个位数的数字。
final static char [] DigitOnes = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
};
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'
};

上面的代码用到的静态常量数组

  • 先来对getChars()进行分析:

这里行与23行的两处循环,分别对int型的高位的两个字节、低位的两个字节进行遍历。【为什么呢,且看下文

  首先对于15行的代码,如果没有上面的注释笔者可能想不出来是什么意思。q*100=q*(26+25+22),乘法原来可以这样的【但笔者写了main方法测试了一下,两种方式在计算时间上没什么差别,可能是我的测试方式有问题,具体效率会高多少,目前还不清楚】。13-15行的意义便是进行求余的思想,不过这里是对100进行求余,每次找出两位数,这样有效的减少了乘除法的次数【高手就是高手】。

  对于低位的循环,同样也是求余,但这里连除法都用是另一种形式。第24行的意思是:q=i*(52429/216)/23≈≈i*0.1【好吧,笔者也没测出效率高多少】。因为这里要用i*52429>>16更精确的表示乘以十分之八的作用,而高位的两个字节的数再乘会溢出,所以源码里进行了高位与低位用两种方式分开循环。

最后是符号的判断,这里就不多说了。

  • 再来对toString(int i)进行分析:

  这里有一处对Integer.MIN_VALUE的判断,只有读完了getChars的代码才会知道,原来第8行的i=-i,对于i=Integer.MIN_VALUE是会益处的。源码中也有相关注释【Will fail if i == Integer.MIN_VALUE】。

2. public static String toString(int i, int radix)

  对于toString(int i, int radix):以传入的基数radix转换成字符串,这里是用真正的求余运算i % radix来实现的【笔者暗爽,居然有了高手想法】,但需要注意的是:为防止溢出,这里是用负数来进行运算的。源码很简单,这里不再赘述,请自行阅读。

3.public static int parseInt(String s, int radix)

这个方法当时看得笔者头大,尤其是char类型转成int型那段代码。以下是源码:

 public static int parseInt(String s, int radix) throws NumberFormatException {
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, max = s.length();
int limit;
int multmin;
int digit; if (max > 0) {
if (s.charAt(0) == '-') {
negative = true;
limit = Integer.MIN_VALUE;
i++;
} else {
limit = -Integer.MAX_VALUE;
}
multmin = limit / radix;
if (i < max) {
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
} else {
result = -digit;
}
}
while (i < max) {
// 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);
}
if (negative) {
if (i > 1) {
return result;
} else { /* Only got "-" */
throw NumberFormatException.forInputString(s);
}
} else {
return -result;
}
}

  如果放弃第33行的digit = Character.digit(s.charAt(i++),radix) 而单纯的理解为:得到char型代表的真实的数字【如'3'就是代表3、'A'在更高的进制里表示10】。那么理解parseInt会容易得多。大致思路就是:由左至右遍历String的每个char,乘以对应的radix加上后面的数即可。如对于十进制:1234 = ((1 × 10 + 2) × 10 + 3) × 10 + 4。

  • 先来总结一些笔者理解到的重点:

首先所有的运算都是基于负数的。在toString也提到过,因为将Integer.MIN_VALUE直接变换符号会导致数值溢出。

然后就是第31行的multmin = limit / radix这个数的控制,可以在乘法计算之前可判断计算之后是否溢出。同理,第50行可在减法之前判断计算后是否溢出。

  • 再来简单说说Character.digit(s.charAt(i++),radix):

  对于≥0且≤255的char型,是由一个int A[] = int[256]的数字数组来对应的【对于>255的我也没看懂】。数组中每个int都是有两个2字节字符组成的,前面2个字节表示参与计算的值,后面2个字节表示这个字符属于什么种类,这个种类也是经过取二进制最后5位数得到的。如表示数字的char型'0'~'9',ascII是48~57,那么int数组中的A[48~57]位的每个int数的后2个字节存的是\u3609,与0x1F做按位与,即二进制最后5位数,得到9,这个就表示的就是数字,是由Character.DECIMAL_DIGIT_NUMBER这个静态常量定义的,除此之外,还有Character.LETTER_NUMBER表示字符数字等等。并且前面2个字节参与计算的公式为:

value = ch + ((val & 0x3E0) >> 5) & 0x1F;

  这个value就是最终得到的数值,val是int数组中对应的数。

  总之其目的就是通过char类型'3'或'A',得到其表示的数值3和10。如果这里没看懂的可以忽略,其实笔者也只是看到的一个表面现象,至于为什么要怎么做,还得请大神来解答。

并且在获取A[]中的数时,中间还有这样的强转,笔者这里也是完全不明白这么做的意义。代码如下:

static int getProperties(int ch) {
char offset = (char)ch;
int props = A[offset];
return props;
}

4.Integer的缓存

最后来说说cache,为提高效率,JDK将[-128,127]之间的这些常用的int值的Integer对象进行了缓存。这是通过一个静态内部类来实现的。代码如下:

 private static class IntegerCache {
private IntegerCache(){} static final Integer cache[] = new Integer[-(-128) + 127 + 1]; static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Integer(i - 128);
}
}

想一想这里为什么会用静态内部类,想明白了以后是不是觉得自己又提升了一点呢。

这也就解释了为什么会有如下的结果:

 Integer a1 = Integer.valueOf(13);
Integer a2 = Integer.valueOf(13);
Integer a3 = Integer.valueOf(133);
Integer a4 = Integer.valueOf(133);
System.out.println(a1 == a2);
System.out.println(a3 == a4);
true
false

猜一猜输出结果是什么


java.lang.Long

好吧,我承认,这与Integer如出一辙。cache值也是[-128-127]。写这个是多余的。


java.lang.Double

  很惭愧,源码看不懂,跟到里面有些甚至是native方法,那只好这里记录一下double类型的存储原理了。

  总所周知,JAVA中double与long都占8个字节,但double的值域却比long大得多得多。Double.MAX_VALUE = 0x1.fffffffffffffP+1023,接近于21023。那如此庞大的数是怎么存的呢。这里会用到指数,即在64位二进制码中,一部分表示数字的值,一部分表示指数数值。就像十进制中的3.14,可以表示为:314×10-2

单精度浮点型用8位表示指数数值,其中一位是符号位。其余23位表示数字,1位表示符号。

双精度浮点型用11位表示指数数值,其中一位是符号位。其余52位表示数字,1为表示符号。

例如:

十进制数0.5等于2-1,它的存储形式是,数字部分符号位为0,数字部分为10,指数符号位为1,指数数值部分为1。

十进制数-3.125等于21+20+2-1+2-3,整数部分为11,小数部分为101,所以它的存储形式是,数字部分符号位为1,数字部分为11101,指数符号位为1,指数数值部分为11。即11101×2-3

如果遇到无限个小数位的数值时,就会截掉可表示的数字的后面的部分,由此可见,指数数值部分越多,表示的浮点型精度就越大。


OK,本次的学习记录就到这里。

学习是件快乐而又有成就感的事。

JAVA源码之JDK(二)——Integer、Long、Double的更多相关文章

  1. java源码学习(二)Integer

    Integer类包含了一个原始基本类型int.Integer属性中就一个属性,它的类型就是int. 此外,这个类还提供了几个把int转成String和把String转成int的方法,同样也提供了其它跟 ...

  2. JAVA源码之JDK(三)——String、StringBuffer、StrinBuilder

    Java中,除了8种基本类型,最长用的应该就是String类了.那么我们来看看JDK中的源码是怎么建造String.StringBuffer.StrinBuilder一系列类的. java.lang. ...

  3. JAVA源码之JDK(一)——java.lang.Object

    想要深入学习JAVA,还需追本溯源,从源码学起.这是我目前的想法.如今JAVA各种开源框架涌出,很多JAVA程序员都只停留在如何熟练使用的层次.身为其中一员的我深感惭愧,所以要加快学习的脚步,开始研究 ...

  4. JAVA源码走读(二)二分查找与Arrays类

    给数组赋值:通过fill方法. 对数组排序:通过sort方法,按升序.比较数组:通过equals方法比较数组中元素值是否相等.查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找 ...

  5. Java源码赏析(二)Java常见接口

    一.Comparable接口 package java.lang; import java.util.*; public interface Comparable<T> { /** * i ...

  6. 解密随机数生成器(二)——从java源码看线性同余算法

    Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...

  7. Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境

    Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Andr ...

  8. Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库

    http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...

  9. 【JDK命令行 一】手动编译Java源码与执行字节码命令合集(含外部依赖引用)

    写作目标 记录常见的使用javac手动编译Java源码和java手动执行字节码的命令,一方面用于应对 Maven 和 Gradle 暂时无法使用的情况,临时生成class文件(使用自己的jar包):另 ...

随机推荐

  1. 了解entity framework其他query方式之Entity SQL,Raw Sql分析

    一:linq 对ef来说不是唯一性的query... 二:Entity Sql 1. esql => entity sql... [类sql的语言] 和sql差不多,但是呢,不是sql... u ...

  2. Arduino I2C + 温湿度传感器Si7021

      Si7021是Silicon Labs生产的温湿度传感器芯片.其主要特性: 湿度精度:误差典型值+/-2%RH,最大值+/-3%RH(0~80%RH),出厂已校正 温度精度:误差典型值+/-0.3 ...

  3. GRPC .netcore

    GRPC是Google发布的一个开源.高性能.通用RPC(Remote Procedure Call)框架.提供跨语言.跨平台支持.以下以一个.NET Core Console项目演示如何使用GRPC ...

  4. 小程序:如何在wxml页面中调用JavaScript函数

    早上过来遇到一个这样的bug: 在计算百分比的时候没有保留小数点后2位,从而导致一些无法整除的结果显示太长 一开始,我以为这是一个很普通的bug,既然wxml在页面{{}}内支持简单的运算,我想也应该 ...

  5. TCP协议中URG和PSH位

    URG(紧急位):设置为1时,首部中的紧急指针有效:为0时,紧急指针没有意义. PSH(推位):当设置为1时,要求把数据尽快的交给应用层,不做处理 通常的数据中都会带有PSH但URG只在紧急数据的时设 ...

  6. Python 中当前位置以及目录文件遍历操作

    Python 中当前位置以及目录文件遍历操作 当前位置 print(os.path.dirname(__file__)) 其中 dirname 会选择目录(文件夹),"__file__&qu ...

  7. shell__常用命令__grep

    grep | zgrep (不用解压zip就能直接搜索) -i 不区分大小写 -I 忽略二进制文件 -R或r 递归文件目录 -c 计算找到的总数量 -n 显示行号 -v 显示不包含匹配文本的所有行 - ...

  8. Squid代理服务器(三)——ACL访问控制

    一.ACL概念 Squid提供了强大的代理控制机制,通过合理设置ACL(Access Control List,访问控制列表)并进行限制,可以针对源地址.目标地址.访问的URL路径.访问的时间等各种条 ...

  9. Android 中 DrawerLayout + ViewPager 怎么解决滑动冲突?

    DrawerLayout 是 Android 官方的侧滑菜单控件,而 ViewPager 相信大家都很熟悉了.今天这里就讲一下当在 DrawerLayout 中嵌套 ViewPager 时,要如何解决 ...

  10. selenium上传图片

    在我们使用selenium的时候碰到上传图片.文件时一般都可以先定位然后直接send_keys,但是有的却不行,selenium也没有提供其它的办法,只能靠第三方软件来解决 我们要借助一个叫AutoI ...