JAVA源码之JDK(二)——Integer、Long、Double - 韩某
来源:互联网 发布:ubuntu vmdk镜像下载 编辑:程序博客网 时间:2024/06/08 06:27
转载自:http://www.tuicool.com/articles/VNrqEr
这篇文章继续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进制的与其它的是不一样的 】。
1 public static String toString(int i) {2 if (i == Integer.MIN_VALUE)3 return "-2147483648";4 int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);5 char[] buf = new char[size];6 getChars(i, size, buf);7 return new String(0, size, buf);8 }
1 static void getChars(int i, int index, char[] buf) { 2 int q, r; 3 int charPos = index; 4 char sign = 0; 5 6 if (i < 0) { 7 sign = '-'; 8 i = -i; 9 }10 11 // Generate two digits per iteration12 while (i >= 65536) {13 q = i / 100;14 // really: r = i - (q * 100);15 r = i - ((q << 6) + (q << 5) + (q << 2));16 i = q;17 buf [--charPos] = DigitOnes[r];18 buf [--charPos] = DigitTens[r];19 }20 21 // Fall thru to fast mode for smaller numbers22 // assert(i <= 65536, i);23 for (;;) { 24 q = (i * 52429) >>> (16+3);25 r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...26 buf [--charPos] = digits [r];27 i = q;28 if (i == 0) break;29 }30 if (sign != 0) {31 buf [--charPos] = sign;32 }33 }
//笔者注:取出十位数的数字。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()进行分析:
这里 12 行 与 23行 的两处循环,分别对int型的高位的两个字节、低位的两个字节进行遍历。【 为什么呢,且看下文 】
首先对于 15行 的代码,如果没有上面的注释笔者可能想不出来是什么意思。q*100=q*(2 6 +2 5 +2 2 ),乘法原来可以这样的【 但笔者写了main方法测试了一下,两种方式在计算时间上没什么差别,可能是我的测试方式有问题,具体效率会高多少,目前还不清楚 】。13-15行的意义便是进行求余的思想,不过这里是对100进行求余,每次找出两位数,这样有效的减少了乘除法的次数【 高手就是高手 】。
对于低位的循环,同样也是求余,但这里连除法都用是另一种形式。第 24行 的意思是:q=i*(52429/2 16 )/2 3≈ ≈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型那段代码。以下是源码:
1 public static int parseInt(String s, int radix) throws NumberFormatException { 2 if (s == null) { 3 throw new NumberFormatException("null"); 4 } 5 6 if (radix < Character.MIN_RADIX) { 7 throw new NumberFormatException("radix " + radix + 8 " less than Character.MIN_RADIX"); 9 }10 11 if (radix > Character.MAX_RADIX) {12 throw new NumberFormatException("radix " + radix +13 " greater than Character.MAX_RADIX");14 }15 16 int result = 0;17 boolean negative = false;18 int i = 0, max = s.length();19 int limit;20 int multmin;21 int digit;22 23 if (max > 0) {24 if (s.charAt(0) == '-') {25 negative = true;26 limit = Integer.MIN_VALUE;27 i++;28 } else {29 limit = -Integer.MAX_VALUE;30 }31 multmin = limit / radix;32 if (i < max) {33 digit = Character.digit(s.charAt(i++),radix);34 if (digit < 0) {35 throw NumberFormatException.forInputString(s);36 } else {37 result = -digit;38 }39 }40 while (i < max) {41 // Accumulating negatively avoids surprises near MAX_VALUE42 digit = Character.digit(s.charAt(i++),radix);43 if (digit < 0) {44 throw NumberFormatException.forInputString(s);45 }46 if (result < multmin) {47 throw NumberFormatException.forInputString(s);48 }49 result *= radix;50 if (result < limit + digit) {51 throw NumberFormatException.forInputString(s);52 }53 result -= digit;54 }55 } else {56 throw NumberFormatException.forInputString(s);57 }58 if (negative) {59 if (i > 1) {60 return result;61 } else { /* Only got "-" */62 throw NumberFormatException.forInputString(s);63 }64 } else {65 return -result;66 }67 }
如果放弃第 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对象进行了缓存。这是通过一个静态内部类来实现的。代码如下:
1 private static class IntegerCache { 2 private IntegerCache(){} 3 4 static final Integer cache[] = new Integer[-(-128) + 127 + 1]; 5 6 static { 7 for(int i = 0; i < cache.length; i++) 8 cache[i] = new Integer(i - 128); 9 }10 }
这也就解释了为什么会有如下的结果:
1 Integer a1 = Integer.valueOf(13);2 Integer a2 = Integer.valueOf(13);3 Integer a3 = Integer.valueOf(133);4 Integer a4 = Integer.valueOf(133);5 System.out.println(a1 == a2);6 System.out.println(a3 == a4);
truefalse
java.lang.Long
好吧,我承认,这与Integer如出一辙。cache值也是[-128-127]。写这个是多余的。
java.lang.Double
很惭愧,源码看不懂,跟到里面有些甚至是native方法,那只好这里记录一下double类型的存储原理了。
总所周知,JAVA中double与long都占8个字节,但double的值域却比long大得多得多。Double.MAX_VALUE = 0x1.fffffffffffffP+1023,接近于2 1023 。那如此庞大的数是怎么存的呢。这里会用到指数,即在64位二进制码中,一部分表示数字的值,一部分表示指数数值。就像十进制中的3.14,可以表示为:314×10 -2 。
单精度浮点型用8位表示指数数值,其中一位是符号位。其余23位表示数字,1位表示符号。
双精度浮点型用11位表示指数数值,其中一位是符号位。其余52位表示数字,1为表示符号。
十进制数0.5等于2 -1 ,它的存储形式是,数字部分符号位为0,数字部分为10,指数符号位为1,指数数值部分为1。
十进制数-3.125等于2 1 +2 0 +2 -1 +2 -3 ,整数部分为11,小数部分为101,所以它的存储形式是,数字部分符号位为1,数字部分为11101,指数符号位为1,指数数值部分为11。即11101×2 -3 。
如果遇到无限个小数位的数值时,就会截掉可表示的数字的后面的部分,由此可见,指数数值部分越多,表示的浮点型精度就越大。
OK,本次的学习记录就到这里。
学习是件快乐而又有成就感的事。
- JAVA源码之JDK(二)——Integer、Long、Double - 韩某
- java 类型转换 Long double String Integer
- Integer, Long, Double expressions
- [Java]JDK源码学习(2)Integer
- java中数据类型转换 Integer String Long Float Double Date
- java中数据类型转换 Integer String Long Float Double Date
- java中数据类型转换 Integer String Long Float Double Date
- java类型转换 Integer String Long Float Double Date
- java类型转换 Integer String Long Float Double Date
- java类型转换 Integer String Long Float Double Date
- Java中Integer/Long/Double/Float大小比较
- JDK源码分析之Integer
- JDK 源码解析 —— Integer
- JDK 源码解析 —— Integer
- Java源码学习之Integer类(二)——1.8新增的几个函数和变量
- JDK(二)java源码分析之ArrayList
- java jdk缓存-128~127的Long与Integer
- java long ,double
- 【opencv/mfc】PICTURE控件显示图像
- java中replace()和replaceAll()总结
- php上传不了文件记得修改权限
- HTTP协议详解(真的很经典)
- Android IntentService详解
- JAVA源码之JDK(二)——Integer、Long、Double - 韩某
- TranslateAnimation从上往下平移动画
- 第二章 创建炫酷的动画效果
- Python3:collections.deque的用法简介
- 网络最后一层input及相关loss func
- Genymotion下载安装及Android模拟器下载安装
- cocos 使用extensions 库需要注意的事
- 四种方案解决ScrollView嵌套ListView问题 (转)
- 分布式架构的演进