JAVA 代码优化

1 基本类型使用优化

1.1 尽量重用对象

特别是对于String对象的使用,如需拼接字符串,使用如下例子:

//拼接字符串,不重视效率的写法
String str1 = "aaa";
str1 = str1 + "bbb";

//拼接字符串,效率高的写法
StringBuilder sb = new StringBuilder(str1);
sb.append("bbb");

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

//造成重复计算的例子
for(int i = 0; i < list.size(); i++){
...
}

//避免重复计算的例子
int length = list.size();
for(int i = 0; i < length; i++){
...
}

1.3 尽量采用懒加载策略,即在需要时候才创建

//错误的例子
String str = "aaa";
if (i == 1){
list.add(str);
}

//正确的例子
if(i == 1){
String str = "aaa";
list.add(str);
}

1.4 乘法和除法使用位移操作

//常用
for(int i = 0; i < 100; i++){
a = i * 8;
b = i / 2;
}

//使用位移操作
for(int i = 0; i < 100; i++){
a = i << 3;
b = i >> 1;
}

 1.5 把一个数字类型转为字符串

Integer id = 10;
String str = id.toString(); --最快
String str = String.valueOf(id); --最快
String str = id + “”; -- 最慢

1.6 使用最有效率的方式去遍历Map

Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"A");
Set<Map.Entry<Integer,String>> entrySet = map.entrySet();
Iterator<Map.Entry<Integer,String>> iter = entrySet.iterator();
while(iter.hasNext()){ Map.Entry<Integer,String> entry = iter.Next(); System.out.println(entry.getKey() + " -- " + entry.getValue()); }

 

 

2 类使用优化

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

2.2 及时关闭流(IO)

2.3 慎用异常: 异常对性能不利。抛出异常首先要创建一个新对象, 只要有异常被抛出,JVM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

2.4 不要在循环中使用try ... catch ...

除非不得已的情况下,否则在循环中使用try ... catch ... 会照成出现异常后依然继续执行循环的情况出现!

2.5 循环内不要不断创建对象引用

//错误的写法
for(int b = 0; b < count; b++){
Object o = new Object();
}
//正确的写法
Object o = null;
for(int b = 0; b < count; b++){
o = new Object();
}

2.6 尽量使用HashMap、ArrayList、StringBuilder。除非线程需要,否则不推荐使用HashTable、Vector、StringBuffer,后三者由于使用了同步机制而导致性能开销。

2.7 尽量在合适的场合使用单例

使用单例可以减轻加载的负担、缩短加载的时间,提高加载的效率,但并不是所有地方都适合单例,单例主要适合以下3个方面:
1)控制资源的使用,通过线程同步来控制资源的并发访问
2)控制实例的产生,以达到节约资源的目的
3)控制数据的共享,在不建立直接关联的条件下,让多个不相干的进程或线程之间实现通信
2.8 尽量避免随意使用静态变量

当某个对象被定义为static的时候,会存储在方法区中,GC对于回收方法区内的对象是需要非常严苛的条件的。这会导致该静态对象常驻于内存,直到程序终止。

2.8 及时清除不再需要的会话

当内存不足的时候,系统会把部分会话数据转移到磁盘,那么就必须先对数据进行序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,在会话不再需要时,因及时的调用HttpSession.invalidate()方法清除会话。

2.9 使用同步代码块替代同步方法

synchronized关键字给我们的代码加锁。 通常有两种写法:在方法上加锁 和 在代码块上加锁。

除非能确定一整个方法都是需要同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。

在方法上加锁:

public synchronized doSave(String fileUrl) {
    mkdir();
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

这里加锁的目的是为了防止并发的情况下,创建了相同的目录,第二次会创建失败,影响业务功能。

在代码块上加锁:

public void doSave(String path,String fileUrl) {
    synchronized(this) {
      if(!exists(path)) {
          mkdir(path);
       }
    }
    uploadFile(fileUrl);
    sendMessage(fileUrl);
}

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

这两个池都能很好的用于对象的重用,前者可以避免频繁的打开和关闭连接,后者可以避免频繁的创建和销毁线程。

2.11 字符串变量和字符串常量equals的时候将字符串常量写在前面,这么 做主要是避免出现空指针异常。

2.12 减少for循环次数

如果循环层级比较深,循环中套循环,可能会影响代码的执行效率。

如果有两层循环,如果userList和roleList数据比较多的话,需要循环遍历很多次,才能获取我们所需要的数据,非常消耗cpu资源

低效率:

//正常逻辑2层for循环处理
for(User user: userList) {
   for(Role role: roleList) {
      if(user.getRoleId().equals(role.getId())) {
         user.setRoleName(role.getName());
      }
   }
}

优化:

Map<Long, List<Role>> roleMap = roleList.stream().collect(Collectors.groupingBy(Role::getId));
for (User user : userList) {
    List<Role> roles = roleMap.get(user.getRoleId());
    if(CollectionUtils.isNotEmpty(roles)) {
        user.setRoleName(roles.get(0).getName());
    }
}

优化思想就是减少循环次数,最简单的办法是,把第二层循环的集合变成map,这样可以直接通过key,获取想要的value数据。

虽说map的key存在hash冲突的情况,但遍历存放数据的链表或者红黑树时间复杂度,比遍历整个list集合要小很多。

2.13 消除if...else的锦囊妙计,反射时添加缓存

优化前:

publicinterface IPay {  
    void pay();  
}  
 
@Service
publicclass AliaPay implements IPay {  
     @Override
     public void pay() {  
        System.out.println("===发起支付宝支付===");  
     }  
}  
 
@Service
publicclass WeixinPay implements IPay {  
     @Override
     public void pay() {  
         System.out.println("===发起微信支付===");  
     }  
}  
  
@Service
publicclass JingDongPay implements IPay {  
     @Override
     public void pay() {  
        System.out.println("===发起京东支付===");  
     }  
}  
 
@Service
publicclass PayService {  
     @Autowired
     private AliaPay aliaPay;  
     @Autowired
     private WeixinPay weixinPay;  
     @Autowired
     private JingDongPay jingDongPay;  
    
   
     public void toPay(String code) {  
         if ("alia".equals(code)) {  
             aliaPay.pay();  
         } elseif ("weixin".equals(code)) {  
              weixinPay.pay();  
         } elseif ("jingdong".equals(code)) {  
              jingDongPay.pay();  
         } else {  
              System.out.println("找不到支付方式");  
         }  
     }  
}
View Code

优化后:

/**
 * @Author: Ywh
 * @Date: 2022/7/25 14:50
 * @Description
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayCode {
    String value();
    String name();
}
 
 
@PayCode(value = "alia", name = "支付宝支付")
@Component("alia")
public class AliaPay implements IPay {
    @Override
    public void pay() {
        System.out.println("===发起支付宝支付===");
    }
}
 
@PayCode(value = "jingdong", name = "京东支付")
@Component("jingdong")
public class JingDongPay implements IPay {
    @Override
    public void pay() {
        System.out.println("===发起京东支付===");
    }
}
 
@PayCode(value = "weixin", name = "微信支付")
@Component("weixin")
public class WeixinPay implements IPay {
    @Override
    public void pay() {
        System.out.println("===发起微信支付===");
    }
}
 
@Service
@Slf4j
public class PayService2 implements ApplicationListener<ContextRefreshedEvent> {
    private static Map<String, IPay> payMap = null;
 
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //在初始化或刷新ApplicationContext时发布
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        //获取所有拥有特定payCode注解的Bean(AliPay、WeiXinPay、JindDongPay)
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);
 
        if (beansWithAnnotation != null) {
            payMap = new HashMap<>();
            beansWithAnnotation.forEach((key, value) -> {
                String bizType = value.getClass().getAnnotation(PayCode.class).value();
                payMap.put(bizType, (IPay) value);
            });
        }
    }
 
    public void pay(String code) {
        payMap.get(code).pay();
    }
}
 
 
    @GetMapping("/pay")
    @ApiOperation("测试支付")
    public void pay(String code){
        payService2.pay(code);
    }
View Code

2.14 不要满屏try...catch异常

可以使用全局异常处理:RestControllerAdvice

@RestControllerAdvice
public class GlobalExceptionHandler {
 
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        if (e instanceof ArithmeticException) {
            return "数据异常";
        }
        if (e instanceof Exception) {
            return "服务器内部异常";
        }
        retur nnull;
    }
}

2.15 状态用枚举

public enum OrderStatusEnum {  
     CREATE(1, "下单"),  
     PAY(2, "支付"),  
     DONE(3, "完成"),  
     CANCEL(4, "撤销");  
 
     private int code;  
     private String message;  
 
     OrderStatusEnum(int code, String message) {  
         this.code = code;  
         this.message = message;  
     }  
   
     public int getCode() {  
        return this.code;  
     }  
 
     public String getMessage() {  
        return this.message;  
     }  
  
     public static OrderStatusEnum getOrderStatusEnum(int code) {  
        return Arrays.stream(OrderStatusEnum.values()).filter(x -> x.code == code).findFirst().orElse(null);  
     }  
}
View Code
  • 代码的可读性变强了,不同的状态,有不同的枚举进行统一管理和维护。

  • 枚举是天然单例的,可以直接使用==号进行比较。

  • code和message可以成对出现,比较容易相关转换。

  • 枚举可以消除if...else过多问题。

2.16 少用@Transactional注解,避免大事务

可以使用编程式事务,在spring项目中使用TransactionTemplate类的对象,手动执行事务。

@Autowired
   private TransactionTemplate transactionTemplate;
   ...
   public void save(final User user) {
         transactionTemplate.execute((status) => {
            doSameThing...
            return Boolean.TRUE;
         })
   }

2.17 SimpleDateFormat线程不安全,使用java8的DateTimeFormatter类。

2.18 少用Executors创建线程池

优先推荐使用ThreadPoolExecutor类,我们自定义线程池。

ExecutorService threadPool = new ThreadPoolExecutor(
    8, //corePoolSize线程池中核心线程数
    10, //maximumPoolSize 线程池中最大线程数
    60, //线程池中线程的最大空闲时间,超过这个时间空闲线程将被回收
    TimeUnit.SECONDS,//时间单位
    new ArrayBlockingQueue(500), //队列
    new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略

 Java通过Executors提供四种线程池,分别为:
1、newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制)
2、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3、newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
4、newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

posted @ 2022-11-20 23:02  NingShare  阅读(106)  评论(0编辑  收藏  举报