文章目录
- 一、类、方法、变量尽量指定final修饰
- 二、字符串拼接优先StringBuilder、StringBuffer
- 三、尽量重用对象、不要循环创建对象
- 四、容器类初始化的时候指定长度
- 五、使用Entry遍历Map
- 六、ArrayList随机遍历快,linkedList添加删除快
- 七、尽量使用基本类型而不是包装类型
- 八、及时消除过期像的引用,防止内存泄漏
- 九、ThreadLocal缓存线程不安全的对象,SimpleDateFormat
- 十、尽量减少使用反射,如果使用反射,第一次用反射,然后加入缓存
- 十一、尽量使用连接池、线程池、对象池、缓存等
- 十二、及时释放资源、I/O流、Socket、数据库连接、redis连接等
- 十三、日志中参数拼接使用占位符
- 十四、字符串变量和字符串常量equals的时候将字符串常量写在前面
- 十五、不要将数组声明为public static final
- 十六、减少使用new关键字创建对象
- 十七、尽量使用HashMap、ArrayList、StringBuilder
- 十八、尽量采用懒加载的策略,就是在需要的时候才创建
- 十九、在数值运算时,遇到偶数乘、除偶数倍时,尽量使用位移运算
- 二十、对象引用的优化
- 二十一、慎重使用static静态
- 二十二、将变量转换成字符串时尽量使用to.string()方法
- 二十三、尽可能使用局部变量
- 二十四、不要创建一些不使用的对象,不要导入一些不使用的类
- 二十五、String尽量少用正则表达式
- 二十六、对资源的close()建议分开操作
- 二十七、尽量在合适的场合使用单例
- 二十八、慎用synchronized,尽量减小synchronize的方法
- 二十九、尽量合理的创建HashMap
- 三十、尽量减少对变量的重复计算
- 三十一、尽量早释放无用对象的引用
- 三十二、尽量避免使用二维数组
- 三十三、尽量使用System.arraycopy ()代替通过来循环复制数组
- 三十四、尽量缓存经常使用的对象
- 三十五、尽量避免非常大的内存分配
- 三十六、在使用同步机制时,应尽量使用方法同步代替代码块同步
- 三十七、不要在循环中使用Try/Catch语句,应把Try/Catch放在循环最外层
作者 | 邮箱 | 时间 |
---|---|---|
潘顾昌 | guchang.pan@hand-china.com | 2020/05/17 |
一、类、方法、变量尽量指定final修饰
public static void test(){
final String x="1";
final String y=x+"2";
String z=x+y;
System.out.println(z);
}
二、字符串拼接优先StringBuilder、StringBuffer
这个涉及到jvm的内存分配,举个例子
String str = “abc”;
String str2 = str + “ccd”;
jvm会在堆内存中开辟3个空间,1为“abc”,2为“ccd”,3为“abcccd”,最终str2指向3,1和2因为没有被引用,会在GC回收机制内被回收,而GC的开销是相当大的,所以应尽量避免
那么使用StringBuffer是什么情况呢
StringBuffer str =“abc”;
str.append(“ccd”);
jvm只会在堆空间中开辟一个空间“abc”,执行append时只会在“abc”的空间上+“ccd”
因此避免了GC的回收,也避免了内存的浪费
同样是为了获取到“abcccd”,但第二种方式明显更有效率
那怎么判断是使用StringBuffer还是StringBuilder的呢?
如果有线程安全的考虑使用StringBuffer,无则使用StringBuilder,线程安全也是一个比较高的开销
三、尽量重用对象、不要循环创建对象
能使用单例尽量使用单例。
四、容器类初始化的时候指定长度
原理:可变数组都有一个扩容政策,当数据量超过它的加载因子时,就会执行扩容操作
当指定长度时,只有在超过指定的长度时,才会执行扩容操作,所以我们使用的时候应尽量预估它的大小,尽量指定大小
new ArrayList<String>(5);
new HashMap<String,String>(32);
五、使用Entry遍历Map
for(Map.Entry<String,String> entry:map.entrySet()){
String key =entry.getkey();
String value=entry.getValue();
}
六、ArrayList随机遍历快,linkedList添加删除快
七、尽量使用基本类型而不是包装类型
String str = "hello";
上面这种方式会创建一个“hello”字符串,而且JVM的字符缓存池还会缓存这个字符串;
String str = new String("hello");
此时程序除创建字符串外,str所引用的String对象底层还包含一个char[]数组,这个char[]数组依次存放了h,e,l,l,o
八、及时消除过期像的引用,防止内存泄漏
九、ThreadLocal缓存线程不安全的对象,SimpleDateFormat
public class SimpleDateFormUtils(){
private static ThreadLocal<SimpleDateFormat> d=new ThreadLocal<SimpleDateFormat>(){
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static void main(String[] args){
d.get().format(new Date());
}
}
十、尽量减少使用反射,如果使用反射,第一次用反射,然后加入缓存
十一、尽量使用连接池、线程池、对象池、缓存等
但是commons-pool提供了一套很好用的对象池组件,使用也很简单。
org.apache.commons.pool.ObjectPool定义了一个简单的池化接口,有三个对应实现,与我们的 Vector 这个集合不同的是,commons-pool提供了多样的集合,包括先进先出(FIFO),后进先出(LIFO)
StackObjectPool :实现了后进先出(LIFO)行为。
SoftReferenceObjectPool: 实现了后进先出(LIFO)行为。另外,对象池还在SoftReference 中保存了每个对象引用,允许垃圾收集器针对内存需要回收对象。
KeyedObjectPool定义了一个以任意的key访问对象的接口(可以池化对种对象),有两种对应实现。
GenericKeyedObjectPool :实现了先进先出(FIFO)行为。
StackKeyedObjectPool : 实现了后进先出(LIFO)行为。
PoolableObjectFactory 定义了池化对象的生命周期方法,我们可以使用它分离被池化的不同对象和管理对象的创建,持久,销毁。
BasePoolableObjectFactory这个实现PoolableObjectFactory接口的一个抽象类,我们可用扩展它实现自己的池化工厂。
十二、及时释放资源、I/O流、Socket、数据库连接、redis连接等
十三、日志中参数拼接使用占位符
log.info("o:"+o);不推荐
log.info("o:{}",o);推荐
十四、字符串变量和字符串常量equals的时候将字符串常量写在前面
避免空指针
String str = "123";
if ("123".equals(str))
{
...
}
十五、不要将数组声明为public static final
十六、减少使用new关键字创建对象
十七、尽量使用HashMap、ArrayList、StringBuilder
尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销
十八、尽量采用懒加载的策略,就是在需要的时候才创建
String str = "HFanss";
if ("2".equals(status))
{
list.add(str);
}
应改为
if ("2".equals(status))
{
String str = "HFanss";
list.add(str);
}
原理:这个应该很容易看懂的,当if不成立时,创建的字符串就没用了,面临GC回收,应该有效避免,写在if内部
十九、在数值运算时,遇到偶数乘、除偶数倍时,尽量使用位移运算
int a = 2;
int b = 16;
System.err.println(a<<5);//等同于 2*2*2*2*2*2 即2*32
System.err.println(b>>2);//等同于 16/2/2 即16/4
原理:即使使用 2*32、16/4的方式,最终在底层的算法还是位移,因为位移是基于2进制的算法,任何运算都会转换成二进制再运算,那我们直接使用二进制就会提升一部分效率
二十、对象引用的优化
List<Object> list = new ArrayList<>();
for (int i = 0; i < 1000; i++ )
{
Object obj = new Object();
list.add(obj);
}
应改为
List<Object> list = new ArrayList<>();
Object obj = null;
for (int i = 0; i < 1000; i++)
{
obj = new Object();
list.add(obj);
}
原理:我们的目的只是list.add(obj)这一步,
前者obj引用会在栈空间中有1000个,但是最终只会用到1个,
后者obj引用在栈空间只有1个,提升效果不言而喻!
二十一、慎重使用static静态
使用静态后,编译时会直接创建,而且直到程序结束,一般只会用在常量,公共方法上,因为需要保证随时随地使用,基于这一需求,它不太使用于对象的创建上,会浪费内存
二十二、将变量转换成字符串时尽量使用to.string()方法
Integer s = 5;
s.toString();
String.valueOf(s);//源码显示调用了Integer.toString()方法,而且会在调用前做空判断
s+“”;//源码显示使用StringBuilder实现,先用append方法拼接,再用toString()方法对比而言,直接调用最底层的toString()方法无疑是效率最高的
二十三、尽可能使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。
二十四、不要创建一些不使用的对象,不要导入一些不使用的类
这毫无意义,如果代码中出现"The value of the local variable i is not used"、“The import java.util is never used”,那么请删除这些无用的内容
二十五、String尽量少用正则表达式
正则表达式虽然功能强大,但是其效率较低,除非是有需要,否则尽可能少用。
replace() 不支持正则
replaceAll() 支持正则
如果仅仅是字符的替换建议使用replace()。
二十六、对资源的close()建议分开操作
try{
XXX.close();
YYY.close();
}
catch (Exception e){
...
}
// 建议改为
try{
XXX.close();
}
catch (Exception e){
...
}
try{
YYY.close();
}
catch (Exception e){
...
}
二十七、尽量在合适的场合使用单例
使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:
第一,控制资源的使用,通过线程同步来控制资源的并发访问;
第二,控制实例的产生,以达到节约资源的目的;
第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。
二十八、慎用synchronized,尽量减小synchronize的方法
都知道,实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。synchronize方法被调用时,直接会把当前对象锁了,在方法执行完之前其他线程无法调用当前对象的其他方法。
二十九、尽量合理的创建HashMap
当你要创建一个比较大的hashMap时,充分利用这个构造函数
public HashMap(int initialCapacity, float loadFactor);
避免HashMap多次进行了hash重构,扩容是一件很耗费性能的事,在默认中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能准确的估计你所需要的最佳大小,同样的Hashtable,Vectors也是一样的道理。
三十、尽量减少对变量的重复计算
for(int i=0;i<list.size();i++)
应该改为
for(int i=0,len=list.size();i<len;i++)
三十一、尽量早释放无用对象的引用
大部分时,方法局部引用变量所引用的对象会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部,引用变量显式设为null。
例如:
Public void test(){
Object obj = new Object();
……
Obj=null;
}
上面这个就没必要了,随着方法test()的执行完成,程序中obj引用变量的作用域就结束了。但是如果是改成下面:
Public void test(){
Object obj = new Object();
……
Obj=null;
//执行耗时,耗内存操作;或调用耗时,耗内存的方法
……
}
这时候就有必要将obj赋值为null,可以尽早的释放对Object对象的引用。
三十二、尽量避免使用二维数组
二维数据占用的内存空间比一维数组多得多,大概10倍以上。
三十三、尽量使用System.arraycopy ()代替通过来循环复制数组
System.arraycopy() 要比通过循环来复制数组快的多。
三十四、尽量缓存经常使用的对象
尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap、的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降,推荐可以使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。也可以使用ConcurrentReferenceHashMap弱引用构建缓存,当内存不足时可以及时清理。
三十五、尽量避免非常大的内存分配
有时候问题不是由当时的堆状态造成的,而是因为分配失败造成的。分配的内存块都必须是连续的,而随着堆越来越满,找到较大的连续块越来越困难。
三十六、在使用同步机制时,应尽量使用方法同步代替代码块同步
比通过循环来复制数组快的多。