Java代码优化
- 代码优化
- 代码优化方式
- 指定类和方法的final修饰符
- 重用对象
- 使用局部变量
- 及时关闭流
- 减少对重复变量的计算
- 懒加载策略
- 谨慎使用异常
- 不要在循环中使用tryCatch
- 指定集合的初始长度
- 乘法和除法使用移位操作
- 不要在循环用创建对象的引用
- 不要将数组声明为public static final
- 合理使用单例模式
- 合理使用静态变量
- 及时清除不再需要的会话
- 使用for循环遍历实现RandomAccess接口的集合
- 使用同步代码块代替同步方法
- 将常量声明为static final并以大写命名
- 在程序的运行过程中避免使用反射
- 使用数据库连接池和线程池
- 使用带缓冲的输入和输出流进行IO操作
- 顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList
- public方法不要有太多的形式参数
- 不要对数组使用toString方法
- 不要对超出范围的基本数据类型做向下强制转型
- 全局的集合类属性中不使用的数据要及时remove掉
- 基本类型转字符串时使用toString方法
- 使用entrySet来遍历Map
- 对资源的close分开操作
代码优化
- 代码优化的目标:
- 减少代码的体积
- 提高代码的运行效率
代码优化方式
指定类和方法的final修饰符
- 类的final修饰符可以让类不可以被继承
- 方法的final修饰符可以让方法不可以被重写
- 如果指定了一个类为final, 那么该类的所有方法都是final
- Java编译器会内联所有的final方法,极大提升Java的运行效率
重用对象
- String: 出现字符串连接时应该使用StringBuilder或者StringBuffer代替
使用局部变量
- 调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,调用速度快
- 其余静态变量和实例变量都在堆中创建,调用速度慢
及时关闭流
- 数据库连接和IO操作时在使用完毕之后要及时关闭以释放资源
减少对重复变量的计算
将
for (int i = 0; i < list.size(); i++) {
...
}
替换为:
for (int i = 0, int lenth = list.size(); i < length; i++) {
...
}
- 在list.size() 很大时,可以减少调用size() 方法的损耗
懒加载策略
- 懒加载: 对象在需要的时候才进行创建
将
String init = "0";
if (i == 1) {
list.add(init);
}
替换为:
if (i == 1) {
String init = "0";
list.add(init);
}
谨慎使用异常
- 异常:
- 抛出异常首先要创建一个新的对象
- Throwable接口的构造方法调用名为fillInStackTrace() 的本地同步方法
- **fillInStackTrace()**方法检查堆栈,收集调用跟踪信息
- 异常只能用来处理错误,不能用来控制流程
不要在循环中使用tryCatch
- 不要在循环中使用try…catch…, 应该在最外层使用
指定集合的初始长度
- 如果能估计到添加内容的长度,为底层以数组方式实现的集合,工具类指定初始长度:
- ArrayList
- LinkedList
- StringBuilder
- StringBuffer
- HashMap
- HashSet
- 对于HashMap以数组+链表实现的集合,初始大小要设置为比对象大一点的2的N次幂
乘法和除法使用移位操作
- 计算机中对位的操作是最方便,最快的,可以极大地提高性能
将
for (val = 0; val < 10000; val += 5) {
a = val * 8;
b = val / 2
}
替换为:
for (val = 0; val < 10000; val +=5) {
a = val << 3;
b = val >> 1;
}
不要在循环用创建对象的引用
将
for (int i = 0; i < count; i++) {
Object obj = new Object();
}
替换为:
Object obj = null;
for (int i = 0; i < count; i++) {
obj = new Object();
}
不要将数组声明为public static final
- 这样做毫无意义,只是定义了引用为static final. 数组的内容还是可以随意改变
- 使用public意味着这个数组可以被外部类改变,这是不推荐的
合理使用单例模式
- 单例模式的应用场景:
- 控制资源的使用,通过线程同步来控制资源的并发访问
- 控制实例的产生,用来达到解决资源的目的
- 控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
合理使用静态变量
- 当某个对象被定义为static的变量所引用,那么GC通常是不会回收这个对象所占有的堆内存的
- 示例:
public class A {
private static final B b = new B();
}
静态变量b的生命周期和A类相同.如果A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止
及时清除不再需要的会话
- 当会话不再需要时,应当及时调用HttpSession的invalidate() 方法清除会话
- 为了清除不再活动的会话,许多应用服务器都会有默认的会话超时时间,通常为30分钟
- 当应用服务器需要保存更多的会话时,如果内存不足,操作系统会将部分数据转移到磁盘中
- 应用服务器可能会根据最近频繁使用MRU算法将部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常
- 如果会话需要被转储到磁盘,必须要先被序列化
- 在大规模集群中,对对象进行序列化的代价很大
使用for循环遍历实现RandomAccess接口的集合
- RandomAccess:
- 实现RandomAccess接口来支持快速随机访问
- 主要目的是允许一般的算法更改行为,从而实现将其应用到随机或连续访问列表时有良好的性能
- 随机访问: for循环
- 顺序访问: Iterator迭代器,也就是foreach
if (list instanceof RandomAccess) {
for (int i = 0; i < list.size(); i++) {}
} else {
Iterator<?> iterator = list.iterable();
while (iterator.hasNext()) {
iterator.next();
}
for (List i : list) {}
}
使用同步代码块代替同步方法
- 除了确定整个方法都是需要进行同步的,否则都使用同步代码块,避免对不需要进行同步的代码进行同步,影响代码的执行效率
将常量声明为static final并以大写命名
- static final可以在编译期间将内容放入常量池,避免在运行期间计算生成常量的值
- 将常量的名字以大写名字命名可以方便区分常量和变量
在程序的运行过程中避免使用反射
- 反射的运行效率不高,不建议在程序运行过程中频繁使用反射机制,特别是Method的invoke() 方法
- 建议将需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存中. 因为用户只关心对端交互的时候获取最快的响应速度,并不关心对端的项目启动要花多久
使用数据库连接池和线程池
- 用于重用对象:
- 数据库连接池: 避免频繁地打开和关闭连接
- 线程池: 避免频繁地创建和销毁线程
使用带缓冲的输入和输出流进行IO操作
- 带缓冲的输入和输出流可以极大地提升IO效率:
- BufferedReader
- BufferedWriter
- BufferedInputStream
- BufferedOutputStream
顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList
- 比较ArrayList和LinkedList的原理
public方法不要有太多的形式参数
- public对外提供调用方法,如果包含太多的形参会出现问题:
- 违反面向对象的编程思想.Java讲究一切皆对象,太多的形参,和面向对象的编程思想不符合
- 参数太多会导致方法调用的出错的概率增加
不要对数组使用toString方法
- 对数组的进行toString() 方法,还可能会因为数组引用为空导致空指针异常
- 对集合toString() 可以打印出集合中的内容,因为集合的父类AbstractCollections< E > 重写了Object的toString() 方法
不要对超出范围的基本数据类型做向下强制转型
- 基本数据类型的强制转换是获取高位二进制数据中能够获取到的低位位数的数据,可能无法得到预期的结果
- int = long + int. 会报错,因为long + int是一个long, 不能赋值给int
全局的集合类属性中不使用的数据要及时remove掉
- 如果一个集合类不是单个方法中的属性,那么这个集合类就是公用的
- 这个集合里面的元素不会自动释放,因为始终会有引用
- 如果公用集合里的数据不被使用后不remove掉,将会导致这个公用集合不断增大,导致内存泄漏
基本类型转字符串时使用toString方法
- 将一个基本类型转字符串,效率由高到低:
- 基本类型.toString()
- String.valueOf(基本类型)
- 基本类型 + ""
使用entrySet来遍历Map
- 使用entrySet来遍历Map:
HashMap<String, Object> map = new HashMap<>();
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Object>> it = entrySet.iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
...
}
- 如果只是想遍历Map的key值,建议使用keySet:
Set<String> keySet = map.keySet();
对资源的close分开操作
将
try {
resource1.close();
resource2.close();
} catch (Exception e) {
...
}
替换为:
try {
resource1.close();
} catch (Exception e) {
...
}
try {
resource2.close();
} catch (Exception e) {
...
}
- 如果资源的close在一起操作,如果前面的关闭操作发生异常时,会直接进入catch模块,而不会执行后面的关闭操作.资源的分开close就很好地避免了这个问题