前言
今天我们来看一下JDK里一些好玩的代码。我们来分析下。
在Integer源码里,我们可以看到这样一段代码:
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,99999999, 999999999, Integer.MAX_VALUE };
// Requires positive x
static int stringSize1(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
这段代码是在干啥???
我们仔细读下,其实就会发现他其实是计算传入的int型x的位数,要求x为正数。
嗯,不难理解。
他为什么要这么写呢?
思考
对于我们来讲,当拿到一个int型正整数,如何用程序算出它的位数呢?
一般不假思索的可能想到String的length方法计算。如下:
static int myStringSize1(int x){
return String.valueOf(x).length();
}
嗯,代码量很少。。。。。。
还有吗?
对,还可以用除法,除以10,计算位数。
static int myStringSize2(int x){
int num=1;
while(x>10){
x=x/10;
num++;
}
return num;
}
嗯。。。。不错不错~
当然能用除法解决的基本上也可以用乘法解决,int的最大位数为10位,所以可以乘以10,判断大小。
// Requires positive x
static int stringSize2(int x) {
int p = 10;
for (int i=1; i<11; i++) {
if (x < p)
return i;
p = 10*p;
}
return 10;
}
好吧,目前我就想到了除JDK以外的这三种方法。
我们测试一下我们的代码正确性。
public static void main(String[] args) {
int [] x=new int[]{5,10,333,6666,77777,123456,1234567,87654321,999999999,1111111111,Integer.MAX_VALUE};
for(int i=0;i<x.length;i++){
int a1=Test.stringSize1(x[i]);
int a2=Test.stringSize2(x[i]);
int a3=Test.myStringSize1(x[i]);
int a4=Test.myStringSize2(x[i]);
System.out.println(a1+"---"+a2+"---"+a3+"---"+a4);
}
}
运行结果:
结果还是木有问题哒。
提升
JDK的这种写法有什么好处呢???
可以看到,JDK的方法把一部分数据计算变成了数据比较,相当于优化吧。
那它的执行效率怎么样呢?
我们写的方法的效率又如何呢?
我们来测试下吧!
实践
我们可以制造一个随机的定长int数组,看看它们执行耗时,同时统计若干组数据,进行比较。
话不多说,直接写代码并记录到Excel里供分析。
public static void main(String[] args) throws Exception{
List<List<Long>> rowList= Lists.newArrayList();
List<String> titleList=Lists.newArrayList();
titleList.add("JDK方法");
titleList.add("乘法");
titleList.add("String方法");
titleList.add("除法");
for(int s=0;s<50;s++){
List<Long> cellList=Lists.newArrayList();
int [] xArrays=new int [10000];
for(int i=0;i<xArrays.length;i++){
xArrays[i]=1 + (int)(Math.random()*Integer.MAX_VALUE);
}
//System.out.println("当前S值为"+s);
long start1=System.nanoTime();
for(int i=0;i<xArrays.length;i++) {
stringSize1(xArrays[i]);
}
long end1=System.nanoTime();
long time1=(end1-start1)/1000;
System.out.println("JDK方法耗时---》"+time1+"ms");
cellList.add(time1);
long start4=System.nanoTime();
for(int i=0;i<xArrays.length;i++) {
stringSize2(xArrays[i]);
}
long end4=System.nanoTime();
long time4=(end4-start4)/1000;
System.out.println("乘法耗时---》"+time4+"ms");
cellList.add(time4);
long start2=System.nanoTime();
for(int i=0;i<xArrays.length;i++) {
myStringSize1(xArrays[i]);
}
long end2=System.nanoTime();
long time2=(end2-start2)/1000;
System.out.println("String方法耗时---》"+time2+"ms");
cellList.add(time2);
long start3=System.nanoTime();
for(int i=0;i<xArrays.length;i++) {
myStringSize2(xArrays[i]);
}
long end3=System.nanoTime();
long time3=(end3-start3)/1000;
System.out.println("除法耗时---》"+time3+"ms");
cellList.add(time3);
rowList.add(cellList);
}
WriteExcelUtil.writeExecl(titleList,rowList,"/Users/zhangwentong/Desktop/workbook.xlsx");
}
我记录了50组数据,每组里面每个计算位数的方法执行10000次,得到如下结果。
把它绘制成折线图。
可以看到,String的length方法效率是最差的。。。。
我们其实看一下String.valueOf(x).length()这个源码,就知道为什么这么慢了。
然后除法也稍微逊色一点。。。
乘法和JDK的效率都可以说不错。。。。。但JDK方法其实要好一点。。。。
而且开始创建的sizeTable是占据较少空间,但却降低了CPU的计算次数(乘法需要每次乘以十在比较计算)。
当然,因为int的位数只有10位,可以写一个sizeTable,当数据量大时,比如long,最大19位,写一个19个数的sizeTable? 一堆9???
哈哈,我们可以看看Long里面的计算位数的方法。
// Requires positive x
static int stringSize(long x) {
long p = 10;
for (int i=1; i<19; i++) {
if (x < p)
return i;
p = 10*p;
}
return 19;
}
人家当然用的乘法啦。。。。。
结论
我们可以看到,我们最容易想到的String.length方法确是效率最低的。。。。
JDK源码里一些方法会被大量调用,当然要做到最好的优化啦。。。
多读些源码,深入思考,多加练习,有助于提升自己。
今天就到这儿吧。