java代码优化笔记

本文列举java开发中代码优化的一些方法。

尽量重用对象,不要循坏创建对象,比如:for循环字符串拼接

这篇文章里面介绍了为什么不能循环拼接字符串对象。

容器类初始化的时候指定长度

例如map和list,如果我们知道我们的数据量有多大,我们最好一开始的时候就指定长度,因为这些list和map在扩容的时候,会做一些例如数据复制的动作或者重新hash的复杂动作,影响效率。
先说arraylist,它内部存储的结构其实是数组,扩容的时候需要复制一遍原来的数据,影响效率;而hashmap在resize的时候,也是很复杂,会影响效率。另外hashmap有一点不同的,传入值不一定会作为初始化容量值,例如传入3,最终tableSizeFor得到的初始化容量值是4,总是2的n次方,是为了hash的时候key分布得更加均匀。

ArrayList随机遍历快,LnkedLiset添加删除快

集合遍历尽量减少重复计算

例如:for(int i = 0,len=collection.size;i<len;i++){},也就是collection.size()尽量先计算出来,以后就不用再计算了。

使用Entry遍历Map

for(Map.entry<String,String> entry:map.entrySet()){
String key = entry.getKey();
String value = entry.getValue();
}
使用entry一次性把key和value取出来了,如果只取keySet,那么如果要value就要再get一遍,所以最好一起取出来。

大数组复制使用System.arraycopy

因为System.arraycopy使用的是原生的方法,所以效率比较高,速度比较快

尽量使用基本类型而不是包装类型

为什么这么说呢?先看一段代码:


    public static void main(String[] args) {
        int i = 100;
        System.out.println(i);

        Integer i2 = 100;
        System.out.println(i2);
    }

它编译生产的字节码是:

         0: bipush        100
         2: istore_1
         3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         6: iload_1
         7: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        10: bipush        100
        12: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        15: astore_2
        16: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        19: aload_2
        20: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V

我们可以看到,调用包装类型的时候,多了一步:

invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

也就是这个基本类型会通过valueOf被包装成Integer类型,所以使用包装类型的时候,都会有一个类似valueOf的操作,把基本类型变成包装类型,这里就会有一点性能上的影响了。
关于valueOf,我们再看下面一段代码:

    public static void main(String[] args) {
        Integer i1=100;
        Integer i2 = 100;
        System.out.println(i1==i2);

        Integer i3=1000;
        Integer i4=1000;
        System.out.println(i3==i4);
    }

输出true和false
看了Integer的valueOf源码:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

low默认是-128,hign默认是127,所以如果之前有缓存,小于127的都可以从缓存里面读取,所以是相等的。大于127就自己创建了,所以 就不相等。

不要手动调用System.gc()

System.gc()通知垃圾收集器进行fullGC,但是什么时候fullgc是不确定的。而且生产环境可能会加+DisableExplicitGC来禁用System.gc(),所以开发过程中最好不要有依赖System.gc()的代码。

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

这里有一个模拟stack的类,表面上很难看出问题。

public class Stack {
	private Object[] elements;
	private int size = 0;
	private static final int DEFAULT_INITIAL_CAPACITY = 16;

	public Stack() {
		elements = new Object[DEFAULT_INITIAL_CAPACITY];
	}

	public void push(Object e) {
		ensureCapacity();
		elements[size++] = e;
	}
	public Object pop() {
		if (size == 0)
			throw new EmptyStackException();
		return elements[--size];
	}

	/**
	 * Ensure space for at least one more element, roughly doubling the capacity
	 * each time the array needs to grow.
	 */
	private void ensureCapacity() {
		if (elements.length == size)
			elements = Arrays.copyOf(elements, 2 * size + 1);
	}
}

问题出在这里:

	public Object pop() {
		if (size == 0)
			throw new EmptyStackException();
		return elements[--size];
	}

我们返回了elements的最后一个元素,那么现在有elements[size-1]指向的对象有两个引用,一个是elements[size-1],一个是外面引用它的引用,如果外面引用释放了这个对象,elements[size-1]这个指针一直还在,对象也不会释放,这里一直被引用着,所以存在内存泄漏的风险,正确的做法是修改为:

	public Object pop() {
		if (size == 0)
			throw new EmptyStackException();
		Object object = elements[size-1];
		elements[size-1] = null;
		size--;
		return object;
	}

或者:

	public Object pop() {
		if (size == 0)
			throw new EmptyStackException();
		Object object = elements[--size];
		elements[size] = null;
		return object;
	}

来自《effective java》中文版 第二版page21

尽量使用局部变量,减小变量的作用域

因为作用域越小,出了作用域,对象就可以被回收了,可以尽快地做垃圾回收。

尽量使用非同步容器

例如,优先考虑使用ArrayList而不是Vector,因为Vector的几乎每个方法都是synchronized,影响效率。

尽量减小同步作用范围,synchronized方法vs同步代码块

在非static方法上加锁,其实就相当于在this上加锁,例如下面两个方法就是效率就是一样的:

	public synchronized void f1() {//在this對象上加锁
		System.out.println("f1");
	}
	public  void f2() {
		synchronized(this) {
			System.out.println("f2");
		}
	}

但是如果下面,明显f2的效率比f1的要高:

	public synchronized void f1() {//在this對象上加锁
	       System.out.println("f");
		System.out.println("f1");
		System.out.println("f");
	}
	public  void f2() {
	     System.out.println("f");
		synchronized(this) {
			System.out.println("f2");
		}
		System.out.println("f");
	}

所以,从这个角度上来说,同步代码块比同步方法更能减小同步的范围,一般来说更加倾向使用同步代码块。
如果是在static方法上加锁,那么就相当于在类上加锁,下面这两个方法是相同的:

	public static synchronized void f3() {
		System.out.println("f3");
	}
	public static void f4() {
		synchronized(SynchronizedTest.class) {
			System.out.println("f4");
		}
	}

用ThreadLocal缓存线程不安全对象,例如SimpleDateFormat

例如:

public class SimpleDateFormatUtil {
	private static ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {  
        protected SimpleDateFormat initialValue() {  
        	return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }  
	};
	public static void main(String[] args) {
		dateFormatHolder.get().format(new Date());
	}
}

这不仅保证了线程安全,也可以提高效率

尽量使用延时加载

例如:

public class Singleton {
	private Singleton() {}
	private static class SingletonHolder{
		private static Singleton instance = new Singleton();
	}
	public static Singleton getInstance() {
		return SingletonHolder.instance;
	}
}

为什么说是延时加载呢?SingletonHolder是一个类,在不对这个类引用的时候,是不对这个类加载的;当引用SingletonHolder这个类的时候,会把里面的成员Singleton加载出来。SingletonHolder在初始化的时候,由jvm类加载机制保证,所以不会出现线程安全的问题。所以它是线程安全的,并且只有引用这个类的时候才加载,所以是延时加载的。

尽量减少使用反射,加缓存

如果真的每次都要反射,试着把反射加缓存里面,下次就不用反射了。框架里面经常会使用这种机制。

尽量使用连接池,线程池,对象池,缓存

我们知道,跟数据库建立连接是比较耗内存的,所以建立完连接,就放回去,下次可以使用

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

jdk1.7开始使用了自动关闭资源的try语句

                 try (

                   // 声明、初始化两个可关闭的资源

                   BufferedReader br = new BufferedReader(new FileReader(

                                     "AutoCloseTest.java"));

                                     PrintStream ps = new PrintStream(new FileOutputStream(

                                                        "readme.txt"))) {

                            // 使用两个资源

                            System.out.println(br.readLine());

                            ps.println("test");

                   }

慎用异常,不要用异常来表示正常业务逻辑

因为异常也是比较重的对象,发生异常的时候,会把整个堆栈记录下来,再往外抛,比较耗时间,会影响正常业务逻辑的时间

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

因为正则表达式虽然好用,却也做了复杂的操作,所以影响效率。
例如String的replaceAll使用的就是正则,replace也是但是比较轻量(只是普通字符串替换),所以能用replace的尽量不要用replaceAll。

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

有的日志在生产环境打的,有的日志在测试环境打的,生产环境打太多日志会影响效率,生产环境只打关键的日志。

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

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

因为我们都知道,字符串的拼接实际上每次都要new String Builder,效率很低,这里讲过
另外即使我们设置了日志级别,info不输出,但是还是会把(“orderId:”+orderId这种字符串拼接拼出来,一样会影响效率。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值