第1条:了解字符串连接的性能
字符串连接操作符(+)是把多个字符串合并为一个字符串的便利方法;对于产生一行输出,或者构造一个字符串来表示一个小的、大小固定的对象,使用此连接操作符是非常合适的,但是它不适合规模比较大的情形;为连接n个字符串而重复地使用字符串连接操作符,要消耗n的平方级的时间; 这是由于字符串是非可变的,当两个字符串被连接的时候,它们的内容都要被拷贝;连接操作符示例代码如下:
public String statement(){
String s = “”;
for(int i=0; i<numItems(); i++)
s += lineForItem(i);
return s;
}
如果项目数量巨大,那么这个方法的执行时间难以估算;为了获得可接受的性能,请使用StringBuffer替代String,修改后代码如下:
public String statement(){
StringBuffer s = new StringBuffer(numItems()*LINE_WIDTH);
for(int i=0; i<numItems(); i++)
s.append(lineForItem(i));
return s.toString();
}
进测试发现,第一种做法的开销随项目数量呈平方级增加,而第二种做法是线性增加,当项目数越大,性能的差别会越显著.
使用原则:不要使用字符串连接操作符来合并多个字符串,除非性能无关紧要.相反,应该使用StringBuffer的append方法,或者采用其他的方案,比如使用字符数组,或者每次只处理一个字符串,而不是将它们组合起来.
第2条:如果需要精确的数据,请避免使用float和double
float和double类型的主要设计目标是为了科学计算和工程计算,它们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的,然而它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合,对于货币计算尤为不合适,因为要让它们精确地表达0.1是不可能的,例如:
System.out.println(1.03-0.42);
结果: 0.6100000000000001
System.out.println(1.00-9*0.10);
结果: 0.09999999999999995
常规解决办法是在展现或使用数据时,对数据进行处理,如四舍五入.但是当经过多次计算过程时,很可能经过处理的数据也得不到所要答案,造成比较大误差,例如,假设你的口袋里有1元,你看到货架上有一排糖果,标价分别为1角、2角、3角、4角等等,一直到1元;你打算从标价为1角的开始,每种买1个,一直到不能支付下一种价格的糖果为止,那么你可以买多少个糖果?还会找回多少零钱?程序如下:
double funds = 1.00;
int itemsBought = 0;
for(double price=0.10;funds>=price;price+=0.10){
funds - = price;
itemsBought++;
}
System.out.println(itemsBought+” items bought.”);
System.out.println(“Change: ”+funds);
运行结果:3个糖果,剩余0.3999999999999999
实际结果:4个糖果,剩余0.00
显然结果错误!
解决这个问题的正确办法是使用BigDecimal、int或者long进行货币计算.上面程序修改如下:
final BigDecimal TEN_CENTS = new BigDecimal(“ 0.10” );
int itemsBought = 0;
BigDecimal funds = new BigDecimal(“ 1.00” );
for(BigDecimal price=TEN_CENTS;funds.compareTo(price)>=0;price=price.add(TEN_CENTS)){
itemsBought++;
funds = funds.subtract(price);
}
System.out.println(itemsBought+” items bought.”);
System.out.println(“Money left over: ”+funds);
运行结果:4个糖果,剩余0.00
实际结果:4个糖果,剩余0.00
显然结果正确!
不过,使用BigDecimal有两个缺点:与使用原语运算类型(float、double等)相比,这样做很不方便,而且更慢;好处就是允许完全控制舍入,让你从8种舍入模式中选择其一.
还有一种解决方案是先将数据放大成整数(或者使用更小单位),使用int或者long,得到结果处理小数点.
总而言之,对于有些要求精确答案的计算任务,请不要使用float和double.如果希望系统来处理十进制小数点,并不介意使用的不便,那么请使用BigDecimal;如果进行商务计算,并且要求特别的舍入行为,那么请使用BigDecimal;如果性能要求高,并且又不介意自己处理十进制小数点,而且涉及的数值又不太大,那么可以使用int或者long;如果数值范围没有超过9位十进制数字,则可以使用int;如果不超过18位数字,则可以使用long;如果数值范围超过了18位数字,就必须使用BigDecimal.
第3条:将局部变量的作用域最小化
将局部变量的作用域最小化,可以增加代码的可读性和可维护性,并降低出错的可能性.其中最有力的技术是在第一次使用它的地方声明;几乎每一个局部变量的声明都应该包含一个初始化表达式;如果还没有足够的信息来对一个变量进行有意义的初始化,那么应该推迟这个声明,直到可以初始化为止,但也有个例外情况与try-catch语句有关,如果变量的值必须在try块的外部被使用到,那么它必须在try块之前被声明,但是在try块之前,它还不能被”有意义地初始化”.
在循环中经常要用到最小化变量作用域这一规则.for循环使你可以声明循环变量,它们的作用域被限定在正好需要的范围之内.因此,如果在循环终止之后循环变量的内容不再被需要的话,则for循环优先于while循环.
例如,一个对集合进行迭代的首选做法:
for(Iterator i = c. iterator(); i.hasNext();){
doSomething(i.next());
}
对比while代码片断,包含两个循环,一个错误(Bug):
Iterator i = c. iterator();
while(i.hasNext()){
doSomething(i.next());
}
…
Iterator i2 = c2. iterator();
while(i.hasNext()){ //Bug
doSomething(i2.next());
}
第二个循环包含一个”剪切-粘贴”错误:它本来是要初始化一个新的循环变量i2,但是却使用了老的循环变量i,不幸的是,这是i仍然还在有效范围之内.结果代码通过编译,运行也不会抛出异常,但是所做的事情却是错误的.如果类似的”剪切-粘贴”错误发生在for循环的用法中,则代码根本不能通过编译.循环是完全独立的,所以重用循环变量名字不会有任何危害.另外,使用for循环比while少一行代码.
另一种对列表进行迭代的循环做法,它也使局部变量的作用域最小化:
for(int i = 0, n = list.size(); i < n; i++){
doSomething(list.get(i));
}
对于随机访问的List实现,比如ArrayList,这种用法是非常有用的,因为它可能比前面我们推荐的方法运行得更快.这种用法有两个循环变量: i和n,两者的作用域完全相同;变量n的使用避免了每次循环时都必须调用size方法,从而提高了性能;如果循环中涉及的方法,在每次迭代中都返回同样的结果,那么应该使用这种用法.当确定列表对象真的提供了随机访问能力时,这种用法是可以接受的;否则,它将会导致平方级的性能开销.
最后一项”最小化局部变量的作用域”的技术是使方法小而集中,每个操作一个方法.
后面会陆续更新,谢谢您的关注!!!