关于程序优化那些事

关于程序优化那些事(持续更新)

前言

干程序员这么多年了,零零碎碎也接触过关于优化这方面的东西,类似于创建索引、计算前置、异步编排等等。但是一直都没有过一个系统的总结,最近公司里由于有些接口响应实在太慢,于是借着这个解决问题的机会,好好谈谈 关于程序优化那些事。

首先,项目给你需求,让优化接口,你也不知道到底是因为什么原因导致的响应慢,是sql的问题,还是代码嵌套多层循环的问题,还是前端调用了多个接口导致的性能问题,还是并发问题,或者是架构问题。

所以在这种情况之下,只能一个一个排查:

数据库层面:

  1. 优化索引

    不单单局限于简单的索引,复合索引、覆盖索引等都可以去考虑,这个往往成本很低但是收效很大;

  2. 优化sql

    • 劲量避免子查询语句

    • 用小表驱动大表

    • where语句后面先接最容易筛选的条件

    • sql尽量简单,计算移步到应用程序中

    • 排序和分组很消耗数据库性能,如果可以尽可能放在代码里

  3. 考虑是否优化表结构

    • 适当的冗余字段其实可以避免一些关联查询,效果还是不错的

    • 数据量大的时候(千万级),考虑是否进行水平拆分

    • 当字段个数太多,可以垂直拆分出经常使用的字段

  4. 数据库是否返回了不必要的字段,比如仅仅需要10个字段,一次性返回了100个

  5. 查询中避免造成索引失效

  6. 其他待补充

代码层面:

  1. 考虑代码逻辑执行顺序对性能的影响

    比如接口代码分三块,ABC顺序执行,有时候调整顺序能够收效显著。

  2. 考虑代码细节

    • 是否在循环里执行了数据库查询语句,能不能放到循环外面一次查出来

    • 多次的数据库插入语句能否合并,修改语句合并

    • 不必要的循环语句

    • 禁止超过两次的循环嵌套

    • 不必要的实时计算(可以考虑计算前置)

    • 是否依赖的下游方法响应慢,影响这边性能

  3. 考虑是否需要缓存中间件

    • 会被并发访问的数据库资源,得加缓存,防止大量请求直接打到数据库

    • 本地缓存+redis双重控制,设计缓存策略

    • 缓存更新三大类:自动失效、定时更新、主动通知更新(变更极少,通过MQ通知更新)

    • 当然一切是有利有弊的,当使用缓存时,得避免缓存雪崩、击穿和穿透等问题。另外,数据库缓存一致性也是个很值得去探讨的问题

  4. 使用异步编排

    • 对于顺序执行的几块代码,如果是互相之间没有什么相关性,可以考虑异步的方式

    • 对于遍历一个大list,可以考虑去将list切分,异步分别遍历

    • 对主要性能影响不大的代码,例如注册成功发送短信,也可以异步

需求层面:

考虑是否需求合理,是不是必要的?有时候修改稍微修改产品设计可以显著减少代码复杂度

前端层面:

考虑是否请求了不必要的接口,有时候不必要整个页面接口刷新

总结

总之数据库资源是宝贵的,尽量让数据库只进行简单的查询插入修改删除操作,计算都移步到内存中。

如果以上方法都无法起效,可以考虑是不是机器性能问题,对于财大气粗的公司可以直接选择堆机器,

当然也可能是大的架构方面的问题,这个需要提出来共同商讨。

代码优化细节:

  • Map和List在声明的时候,设置容量

  • 尽量指定类、方法的final修饰符

    如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,

  • 当进行大量字符串拼接的时候,用StringBuilder代替String

  • 尽可能使用局部变量

    调用方法时传递的参数和方法中使用的变量,都存储在栈中,速度较快,其他变量,如静态变量,实例变量都在堆中创建,速度较慢,而且栈随着方法结束就关闭了,不需要额外的垃圾回收

  • 及时关闭流数据库连接,IO流等

  • 尽量减少对变量的重复计算

    明确一个概念,即使是只有一行代码的方法,调用也是有消耗的,包括创建栈帧,维护栈内存安全,调用结束弹栈等,都是消耗一定性能的,所以尽量减少重复不必要的调用,比如

    for(int i = 0;i<list.size();i++){}
    // 替换成
    int length = list.size();
    for(int i = 0;i<length;i++){}
  • 尽量采用懒加载,用的时候才创建对象

  • 异常处理

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

  • 当复制大量数据时,建议使用System.arraycopy()命令

  • 当乘法和除法是2的整次幂的时候,建议采用移位操作

  • 循环内不要重复创建对象

    for (int i = 1; i <= count; i++)
    {
        Object obj = new Object();    
    }
    // 替换为
    Object obj = null;
    for (int i = 0; i <= count; i++)
    {
        obj = new Object();
    }
  • 基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList

  • 尽量使用HashMap,ArrayList,StringBuilder,除非考虑到线程安全才去考虑HashTable,Vector,StringBuffer。后三者使用同步机制而导致性能开销

  • 尽量避免使用静态变量,静态变量常驻内存。

  • 对于锁的合理使用

    • 避免发生死锁

    • 建议使用同步代码块的替代同步方法

  • 常量声明为static final

  • 不要创建不使用的对象和不需要引用的类

  • 避免使用反射

  • 使用数据库连接池和线程池

  • 使用缓冲流

  • 顺序插入和随机访问次数多用ArrayList,随机插入和元素删除多用LinkedList

  • if语句多个条件时,将容易判断的的条件放最左面

  • equal()时,常量放左边

  • 数组禁止toString()

  • 禁止对超过范围的long进行下转型

  • 基本数据类型转换成String 最快是包装类.toString(),String.value() 次之,+"" 最次

  • 最快遍历map的方法是使用Iterator

  • 对多个资源的close() 应该分别close()

  • 对于ThreadLocal使用前或者使用后一定要先remove

    当前一般会使用线程池技术,在使用结束后线程不会销毁而是重新放入线程池,在下次使用时可能get上次set的数据,这一般很难发现

  • 重写方法加@Override

    • 清楚知道这个方法由父类继承而来

    • getObj() 和get0bj() 能立马确定是否继承成功

    • 在抽象类或者接口中修改方法名,实现类中会报错

  • 用Objects代替equals

  • 多线程获取随机数时,用ThreadLocalRandom而非Random

  • 待补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值