代码优化的目标是:
-
减小代码的体积
-
提高代码运行的效率,提高程序性能
1 复杂的逻辑条件,是否可以调整顺序,让程序更高效呢
假设业务需求是这样:会员,第一次登陆时,需要发一条感谢短信。如果没有经过思考,代码直接这样写了
if(isUserVip && isFirstLogin){
sendMsg();
}
假设总共有 5 个请求,isUserVip 通过的有 3 个请求,isFirstLogin 通过的有 1 个请求。 那么以上代码,isUserVip 执行的次数为 5 次,isFirstLogin 执行的次数也是 3 次,如下:
如果调整一下 isUserVip 和 isFirstLogin 的顺序呢?
if(isFirstLogin && isUserVip ){
sendMsg();
}
isFirstLogin 执行的次数是 5 次,isUserVip 执行的次数是 1 次,如下:
2 你的程序是否不经意间创建了不必要的对象
举个粟子吧,判断用户会员是否处于有效期,通常有以下类似代码:
//判断用户会员是否在有效期
public boolean isUserVIPValid() {
Date now = new Date();
Calendar gmtCal = Calendar.getInstance();
gmtCal.set(2019, Calendar.JANUARY, 1, 0, 0, 0);
Date beginTime = gmtCal.getTime();
gmtCal.set(2020, Calendar.JANUARY, 1, 0, 0, 0);
Date endTime= gmtCal.getTime();
return now.compareTo(beginTime) >= 0 && now.compareTo(endTime) <= 0;
}
但是呢,每次调用 isUserVIPValid 方法,都会创建 Calendar 和 Date 对象。其实吧,除了 New Date,其他对象都是不变的,我们可以抽出全局变量,避免创建了不必要的对象,从而提高程序效率,如下:
public class Test {
private static final Date BEGIN_TIME;
private static final Date END_TIME;
static {
Calendar gmtCal = Calendar.getInstance();
gmtCal.set(2019, Calendar.JANUARY, 1, 0, 0, 0);
BEGIN_TIME = gmtCal.getTime();
gmtCal.set(2020, Calendar.JANUARY, 1, 0, 0, 0);
END_TIME = gmtCal.getTime();
}
//判断用户会员是否在有效期
public boolean isUserVIPValid() {
Date now = new Date();
return now.compareTo(BEGIN_TIME) >= 0 && now.compareTo(END_TIME) <= 0;
}
}
3 查询数据库时,你有没有查多了数据?
大家都知道,查库是比较耗时的操作,尤其数据量大的时候。所以,查询 DB 时,我们取所需就好,没有必要大包大揽。
假设业务场景是这样:查询某个用户是否是会员。曾经看过实现代码是这样。。。
List<Long> userIds = sqlMap.queryList("select userId from user where vip=1");
boolean isVip = userIds.contains(userId);
为什么先把所有会有查出来,再判断是否包含这个 useId,来确定 useId 是否是会员呢?直接把 userId 传进 sql,它不香吗?如下:
Long userId = sqlMap.queryObject("select userId from user where userId='userId' and vip='1' ")
boolean isVip = userId!=null;
实际上,我们除了把查询条件都传过去,避免数据库查多余的数据回来,还可以通过select 具体字段代替 select*,从而使程序更高效。
4 对空指针保持嗅觉,如使用 equals 比较时,常量或确定值放左边
NullPointException 在 Java 世界早已司空见惯,我们在写代码时,可以三思而后写,尽量避免低级的空指针问题。
比如有以下业务场景,判断用户是否是会员,经常可见如下代码:
boolean isVip = user.getUserFlag().equals("1");
如果让这个行代码上生产环境,待君蓦然回首,可能那空指针 bug,就在灯火阑珊处。显然,这样可能会产生空指针异常,因为 user.getUserFlag()可能是 null。
怎样避免空指针问题呢?把常量 1 放到左边就可以啦,如下:
boolean isVip = "1".equals(user.getUserFlag());
5 你的关键业务代码是否有日志保驾护航?
关键业务代码无论身处何地,都应该有足够的日志保驾护航。
比如:你实现转账业务,转个几百万,然后转失败了,接着客户投诉,然后你还没有打印到日志,想想那种水深火热的困境下,你却毫无办法。。。
那么,你的转账业务都需要那些日志信息呢?至少,方法调用前,入参需要打印需要吧,接口调用后,需要捕获一下异常吧,同时打印异常相关日志吧,如下:
public void transfer(TransferDTO transferDTO){
log.info("invoke tranfer begin");
//打印入参
log.info("invoke tranfer,paramters:{}",transferDTO);
try {
res= transferService.transfer(transferDTO);
}catch(Exception e){
log.error("transfer fail,cifno:{},account:{}",transferDTO.getCifno(),
transferDTO.getaccount())
log.error("transfer fail,exception:{}",e);
}
log.info("invoke tranfer end");
}
除了打印足够的日志,我们还需要注意一点是,日志级别别混淆使用,别本该打印 info 的日志,你却打印成 error 级别,告警半夜三更催你起来排查问题就不好了。
6 某些可变因素,如红包皮肤等等,做成配置化是否会更好呢。
假如产品提了个红包需求,圣诞节的时候,红包皮肤为圣诞节相关的,春节的时候,红包皮肤等。
如果在代码写死控制,可有类似以下代码:
if(duringChristmas){
img = redPacketChristmasSkin;
}else if(duringSpringFestival){
img = redSpringFestivalSkin;
}
......
如果到了元宵节的时候,运营小姐姐突然又有想法,红包皮肤换成灯笼相关的,这时候,是不是要去修改代码了,重新发布了?
从一开始,实现一张红包皮肤的配置表,将红包皮肤做成配置化呢?更换红包皮肤,只需修改一下表数据就好了。
7 多余的 import 类,局部变量,没引用是不是应该删除
如果看到代码存在没使用的 import 类,没被使用到的局部变量等,就删掉吧,如下这些:
这些没被引用的局部变量,如果没被使用到,就删掉吧,它又不是陈年的女儿红,留着会越发醇香。它还是会一起被编译的,就是说它还是耗着资源的呢。
8 初始化集合时尽量指定其大小
阿里开发手册推荐了这一点!
假设你的 map 要存储的元素个数是 15 个左右,最优写法如下
//initialCapacity = (15/0.75)+1=21
注释:这样可以避免hashmap扩容,从而减少资源的消耗,提高性能,如果不确定初始值大小就写16
Map map = new HashMap(21);
又因为hashMap的容量跟2的幂有关,所以可以取32的容量
Map map = new HashMap(32);
9 尽量指定类、方法的final修饰符
带有 final 修饰符的类是不可派生的。在 Java 核心 API 中,有许多应用 final 的例子,例如 java.lang.String,整个类都是 final 的。为类指定 final 修饰符可以让类不可以被继承,为方法指定 final 修饰符可以让方法不可以被重写。如果指定了一个类为 final,则该类所有的方法都是 final 的。Java 编译器会寻找机会内联所有的 final 方法,内联对于提升 Java 运行效率作用重大,具体参见 Java运行期优化。此举能够使性能平均提高 50%。
10 尽量重用对象(同第2点原理一样)
特别是 String 对象的使用,出现字符串连接时应该使用 StringBuilder/StringBuffer 代替。由于 Java 虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。
11 尽可能使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。
12 及时关闭流
Java 编程过程中,进行数据库连接、I/O 流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。
13 尽量减少不必要的代码,能一步到位就一步到位
例如下面代码:
List<Object> list = InterfaceUtil.getUserList();
for (int i = 0; i < list.size(); i++) {
}
建议换成:
for (int i = 0; i < InterfaceUtil.getUserList().size(); i++) {
}
14 尽量采用懒加载的策略,即在需要的时候才创建
例如:
String str = "abc";
if (i == 1){
list.add(str);
}
建议替换为:
if (i == 1){
String str = "abc";
list.add(str);
}
原因:如果i !=1 str对象都不需要用,这样就会创建一个对于对象,如果你代码里有很多这情况就会影响程序的运行效率。
15 乘法和除法使用移位操作
用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的。
具体移位操作如下:
乘:用左移运算符 <<
除:用右移运算符 >>
移多少位:1.看除数是2的几次方就移几次。
2.看第二个因数是2的几次方就移几次。
案例如下:
int a = 120;
int b = 4;
//右移
System.out.println( a/b );
System.out.println(a>>2);
//左移
System.out.println( a * b);
System.out.println( a<<2);
注释: 因数 * 因数 = 积
被除数 / 除数 = 商
16 循环内不要不断创建对象引用
例如:
for (int i = 1; i <= count; i++)
{
Object obj = new Object();
}
这种做法会导致内存中有多个Object 对象引用存在,量很大的话,就耗费内存了,建议为改为:
Object obj = null;
for (int i = 0; i <= count; i++)
{
obj = new Object();
}
这样的话,内存中只有一份 Object 对象引用,每次 new Object() 的时候,Object 对象引用指向不同的 Object 罢了,但是内存中只有一份,这样就大大节省了内存空间了。
17 尽量使用 HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用 Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销
18 循环体内不要使用"+"进行字符串拼接,而直接使用 StringBuilder (或StringBuffer)不断 append **
原因如下:
- String类本身是final类型,字符串拼接时,每一次拼接都会重新创建一个字符串对象,这无疑对内存是一种恶意消耗。
- 每次虚拟机碰到"+"这个操作符对字符串进行拼接的时候,会new出一个StringBuilder,然后调用append方法,最后调用 toString() 方法转换字符串,即拼接多少次,就会 new 出多少个 StringBuilder()来,这对于内存是一种浪费。
- StringBuilder的出现就是用来替换StringBuffer的,但不适宜于多线程编程。从这点儿上来说,StringBuilder 在单线程编程情况下应优先于StringBuffer使用,而在多线程编程时则应使用StringBuffer,不宜使用StringBuilder 。
总结: StringBuilder效率(线程不安全) > StringBuffer效率(线程安全)
19 long或者Long初始赋值时,使用大写的L而不是小写的l,因为字母l极易与数字1混淆,这个点非常细节,值得注意
20 使用最有效率的方式去遍历 Map
Map<String, Object> map = new HashMap<>();
map.put("1", "哈哈");
//1.转换成set集合
Set<Entry<String,Object>> entrySet = map.entrySet();
//2.获取set集合的迭代器
Iterator<Entry<String, Object>> iterator = entrySet.iterator();
//3.用迭代器遍历set集合
while(iterator.hasNext()) {//当迭代器有值时就遍历
Entry<String, Object> next = iterator.next();//获取值
System.out.println(next.getKey()+" "+next.getValue());
}