常用代码优化方法

在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身。养成良好的编码习惯非常重要,能够显著地提升程序性能。

1. 尽量重用对象,不要循环创建对象,比如:for 循环字符串拼接(不在 for中使用String类型 + 拼接,因为其底层是先new 一个StringBuilder再在 for 里 append最后调用StringBuilder的toString()方法  ). 由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响 , 出现字符串连接情况时应使用StringBuffer代替

StringBuilder s=new StringBuilder();
for(int i=0;i<10;i++){
	s.append("C");
}

2. 容器类初始化的地时候指定大小(长度)

List<String> collection = new ArrayLIst<String>(5);

Map<String, String> map = new HashMap<String, String>(32);

StringBuffer buffer = new StringBuffer(16);

3. ArrayList(底层数组)随机遍历快,LinkedList(底层双向链表)添加删除快

4. 集合遍历尽量减少重复计算,改成    

for (int i = 0,size=list.size();i<size; i++) { 

}

5. 使用键值对Entry 遍历 Map

for(Map.Entry<String,String> entry:map.entrySet()){
    String key =entry.getkey();
    String value=entry.getValue();
}

6. 大数组复制使用System.arraycopy

int[] is = new int[]{1,2,3,4,5}; 
int[] is2 = new int[is.length];
System.arraycopy(is, 0, is2, 0, is.length);
System.out.println(Arrays.toString(is2));

7. 尽量使用基本类型而不是包装类型,避免自动拆装箱 空指针问题

尽量使用基本数据类型作为方法参数类型 , 尽量使用基本数据类型作为方法返回值类型

Integer i = 10;
Integer j = 20;
System.out.println(i+j);


//反编译后代码如下:
Integer i = Integer.valueOf(10);
Integer j = Integer.valueOf(20);
System.out.println(i.intValue() + j.intValue());

8. 不要手动调用 System.gc()

9. 及时消除过期对象的引用,防止内存泄漏 

10. 尽量使用局部变量,调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。

11. 尽量使用尽量使用非同步的容器HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销

StringBuffer,StringBuilder的区别在于:java.lang.StringBuffer 线程安全的可变字符序列。一个类似于String的字符串缓冲区,但不能修改。StringBuilder与该类相比,通常应该优先使用StringBuilder类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。为了获得更好的性能,在构造StringBuffer或StringBuilder时应尽量指定她的容量。当然如果不超过16个字符时就不用了。 相同情况下,使用StringBuilder比使用StringBuffer仅能获得10%~15%的性能提升,但却要冒多线程不安全的风险。综合考虑还是建议使用StringBuffer

对于常量字符串,用'String' 代替 'StringBuffer'   常量字符串并不需要动态改变长度。对于字符串拼接append方法最快、concat次之、加号最慢 大多数情况,我们使用“+”,符合编码习惯和我们的阅读 当在频繁进行字符串的运算(如拼接、替换、删除等),或者在系统性能临界的时候,我们可以考虑使用concat或append方法

12. 尽量减小同步作用范围,  应尽量使用方法同步代替代码块同步  synchronized 方法 vs. 代码块 

13. 用ThreadLocal 缓存线程不安全的对象,SimpleDateFormat

public class SimpleDateFormUtils {

    private static ThreadLocal<SimpleDateFormat> d=new ThreadLocal<SimpleDateFormat>(){

        @Override
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static void main(String[] args){
        d.get().format(new Date());
    }

}

public class SimpleDateFormUtils {

    private static final String MESSAGE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final ThreadLocal<DateFormat> messageFormat = new ThreadLocal< >();

    private static final DateFormat getDateFormat() {
        DateFormat format = messageFormat.get();
        if (format == null) {
            format = new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());
            messageFormat.set(format);
        }

        return format;
    }

    public static void main(String[] args){
        System.err.println(SimpleDateFormUtils.getDateFormat().format(new Date()));
    }

}

14. 尽量使用延迟加载( 懒汉加载或者饿汉加载 )

String str = "aaa";
if (i == 1){
  list.add(str);
}

 建议改成:

if (i == 1){
  String str = "aaa";
  list.add(str);
}

15. 尽量减少使用反射,必须用时加缓存,用反射赋值对象,主要优点是节省了代码量,主要缺点却是性能有所下降

List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
    UserVO userVO = new UserVO();
    BeanUtils.copyProperties(userDO, userVO);
    userVOList.add(userVO);
}

正例:

List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
    UserVO userVO = new UserVO();
    userVO.setId(userDO.getId());
    ...
    userVOList.add(userVO);
}

16. 尽量使用连接池、线程池、对象池、缓存

17. 及时释放资源, I/O 流、Socket、数据库连接

18. 慎用异常,不要用抛异常来表示正常的业务逻辑   目前业务都手动抛异常处理 应该是个伪定论

异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程

19. String 操作尽量少用正则表达式

replace VS replaceAll
split

除非是必须的,否则应该避免使用split,split由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资源,如果确实需要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char),频繁split的可以缓存结果。

20. 日志输出注意使用不同的级别

21. 日志中参数拼接使用占位符

log.info("orderId:" + orderId); 不推荐, 会增加无畏的字符串拼接

log.info("orderId:{}", orderId); 推荐

log.debug( "Load student(name: {}" ,student.getName() );不推荐,student可能为null空指针

log.debug( "Load student(student: {}" , student );推荐

//判断当前配置下debug级别是否可以输出, 等同于下面的先判断,后打印避免无用的字符串拼接
logger.debug("入参报文:{}",() -> JSON.toJSONString(policyDTO));

if(logger.isDebugEnabled()){ 
    logger.debug("入参报文:{}",JSON.toJSONString(policyDTO));
}

最牛逼 Java 日志框架 — Log4j2,性能无敌,横扫对手.....

22. 把一个基本数据类型转为字符串,包装数据类型(eg Integer).toString()是最快的方式、String.valueOf(数据)次之、数据+""最慢

 for (int i = 0; i < A.length; i++) {
         //int tmp=A[i];
         //ints[i]=tmp*tmp;
         ints[i]=A[i]*A[i]; //这种效率高
 }

23. 尽量在合适的场合使用单例

使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:

  1. 控制资源的使用,通过线程同步来控制资源的并发访问;

  2. 控制实例的产生,以达到节约资源的目的;

  3. 控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。

24. 尽量避免随意使用静态变量

当某个对象被定义为static变量所引用,那么GC通常是不会回收这个对象所占有的内存,此时静态变量b的生命周期与A类同步,如果A类不会卸载,那么b对象会常驻内存,直到程序终止。

有的同学习惯使用对象访问静态变量,这种方式多了一步寻址操作,需要先找到变量对应的类,再找到类对应的变量,如下面的代码:

public class A{

private static B b = new B();

}

24(2)  使用静态代码块实现赋值静态成员变量

反例:

private static Map<String, Integer> map = new HashMap<String, Integer>(){
    {
        map.put("Leo",1);
        map.put("Family-loving",2);
        map.put("Cold on the out side passionate on the inside",3);
    }
};

正例:

private static Map<String, Integer> map = newHashMap<String, Integer>();
static{ map.put("Leo",1);
  map.put("Family-loving",2);
  map.put("Cold on the out side passionate on the inside",3);
}

25.尽量避免过多过常地创建Java对象

尽量避免在经常调用的方法,循环中new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理,在我们可以控制的范围内,最大限度地重用对象,最好能用基本的数据类型或数组来替代对象。

   Long i = new Long(1L);
   String s = new String("abc");
改成:
   Long i = 1L;
   String s = "abc";

26.尽量使用局部变量

在函数内,基本类型的参数和临时变量都保存在栈(Stack)中,访问速度较快;对象类型的参数和临时变量的引用都保存在栈(Stack)中,内容都保存在堆(Heap)中,访问速度较慢。在类中,任何类型的成员变量都保存在堆(Heap)中,访问速度较慢。

27. 尽量处理好包装类型和基本类型两者的使用场所

虽然包装类型和基本类型在使用过程中是可以相互转换,但它们两者所产生的内存区域是完全不同的,基本类型数据产生和处理都在栈中处理,包装类型是对象,是在堆中产生实例。在集合类对象,有对象方面需要的处理适用包装类型,其他的处理提倡使用基本类型。

28. 尽量使用移位来代替'a/b' 'a*b'的操作

"/" 和 '*' 是一个代价很高的操作,使用移位的操作将会更快和更有效   但注意的是使用移位应添加注释,因为移位操作不直观,比较难理解。

如:

int num = a / 4;

int num = a / 8;

应改为: 
int num = a >> 2;

int num = a >> 3;



int num = a * 4;

int num = a * 8;

应改为: 
int num = a << 2;

int num = a << 3;

29. 尽量避免使用二维数组  

 二维数据占用的内存空间比一维数组多得多,大概10倍以上。

30. 尽量缓存经常使用的对象

尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降,推荐可以使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。

31. 在java+Oracle的应用系统开发中,java中内嵌的SQL语言应尽量使用大写形式,以减少Oracle解析器的解析负担。

32. 在java编程过程中,进行数据库连接,I/O流操作,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销。

33. 不用new关键字创建对象的实例

用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。

下面是Factory模式的一个典型实现:

public static Credit getNewCredit(){
     return new Credit();
}

改进后的代码使用clone()方法:

private static Credit BaseCredit = new Credit();

public static Credit getNewCredit(){

    return (Credit)BaseCredit.clone();
}

成员变量: 存在默认值(系统给定)
局部变量: 不存在默认值,必须先赋值,然后在使用

34:如果变量的初值会被覆盖,就没有必要给变量赋初值

    List<UserDO> userList = new ArrayList<>();
    if (isAll) {
        userList = userDAO.queryAll();
    } else {
        userList = userDAO.queryActive();
    }

改成: 
    List<UserDO> userList;
    if (isAll) {
        userList = userDAO.queryAll();
    } else {
        userList = userDAO.queryActive();
    }

35. 对于多常量选择分支,尽量使用switch语句而不是if-else语句

if-else语句,每个if条件语句都要加装计算,直到if条件语句为true为止。switch语句进行了跳转优化,Java中采用tableswitch或lookupswitch指令实现,对于多常量选择分支处理效率更高。经过试验证明:在每个分支出现概率相同的情况下,低于5个分支时if-else语句效率更高,高于5个分支时switch语句效率更高。

反例:

if (i == 1) {
    ...; // 分支1
} else if (i == 2) {
    ...; // 分支2
} else if (i == ...) {
    ...; // 分支n
} else {
    ...; // 分支n+1
}
正例:

switch (i) {
    case 1 :
        ... // 分支1
        break;
    case 2 :
        ... // 分支2
        break;
    case ... :
        ... // 分支n
        break;
    default :
        ... // 分支n+1
        break;
}
备注:如果业务复杂,可以采用Map实现策略模式。

36.  MyBatis 不要为了多个查询条件而写 1 = 1

当遇到多个查询条件,使用where 1=1 可以很方便的解决我们的问题,但是这样很可能会造成非常大的性能损失,因为添加了 “where 1=1 ”的过滤条件之后,数据库系统就无法使用索引等查询优化策略,数据库系统将会被迫对每行数据进行扫描(即全表扫描) 以比较此行是否满足过滤条件,当表中的数据量较大时查询速度会非常慢;此外,还会存在SQL 注入的风险。

37.避免使用BigDecimal(double) , 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。

    

反例:BigDecimal bigDecimal = newBigDecimal(0.11D);

正例:BigDecimal bigDecimal1 = BigDecimal.valueOf(0.11D);

38.  不要在多线程下使用同一个 Random
Random 类的 seed 会在并发访问的情况下发生竞争,造成性能降低,建议在多线程环境下使用 ThreadLocalRandom 类。 文章:  还在用 Random生成随机数了?试试 ThreadLocalRandom,好用!

使用:  ThreadLocalRandom.current().nextInt(0, 100); 生成0-100随机数

在 Linux 上,通过加入 JVM 配置 -Djava.security.egd=file:/dev/./urandom,使用 urandom 随机生成器,在进行随机数获取时,速度会更快。 

cat /dev/urandom | LC_ALL=C tr -dc "[:alnum:]" | fold -w 16 |head -3  
                               说明: fold -w 16  指定密码的位数。 head -3 产成多少个密码。
生成特别复杂的密码: cat /dev/urandom | LC_ALL=C tr -dc "[:graph:]" | fold -w 16 |head -3

39. 自增推荐使用 LongAddr
自增运算可以通过 synchronized 和 volatile 的组合,或者也可以使用原子类(比如 AtomicLong)。 后者的速度比前者要高一些,AtomicLong 使用 CAS 进行比较替换,在线程多的情况下会造成过多无效自旋,所以可以使用 LongAdder 替换 AtomicLong 进行进一步的性能提升

参考文章: JAVA代码优化_点点的博客-CSDN博客_java代码优化

        https://mp.weixin.qq.com/s?__biz=MzI3MjUxNzkxMw==&mid=2247484163&idx=1&sn=11fcabe47313b3667a0f1afecd7b5508&chksm=eb301d75dc479463c451da85e278fc54a48bf3cd3842e265a6d4f05f30de90c5736c349830cf&mpshare=1&scene=1&srcid=&sharer_sharetime=1573005727121&sharer_shareid=e1d4a35ff3308c63451b2ff386b1e2bd&key=a00a174091495fb128c4068a3c3df4ad72f86b092bd3d4be020beecd1237ac9df538f91c573811540f2e9c3573072f193f0b6da77d1bef6ffd8c42726f5ac79920753b7d2978683d5df49ae8c515d0f0&ascene=1&uin=NjU1MzgyNTYw&devicetype=Windows+10&version=62070152&lang=zh_CN&pass_ticket=2YQWFJfhCrMenkrdY%2BWoK0ETaGGgUV8LMfBj95U63Lb5n8qb3NBoZ7X6mFrYQ5Ps

Java编码技巧之高效代码50例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值