java程序优化

程序代码优化要点:java

  • 字符串优化:分析String源码,了解String经常使用方法,使用StringBuffer、StringBuilder。
  • List、Map、Set优化:分析经常使用ArrayList、LinkedList、HashMap、TreeMap、LinkedHashMap、Set接口、集合经常使用方法优化。
  • 使用NIO:Buffered、Channel操做和原理,使用零拷贝。
  • 引用优化:强引用、弱引用、软引用、虚引用、WeekHashMap。
  • 优化技巧:经常使用代码优化技巧。这里不一一罗列,请参考下面的详解。
  • 字符串优化:程序员

    • String对象特色:正则表达式

      • 终态:String类被声明为final,不可被继承重写,保护了String类和对象的安全。在jdk1.5以前final声明会被inline编译,性能大幅度提升,jdk1.5以后性能提高不大。
      • 常量池:String在编译期间会直接分配在方法区的常量池中,当咱们写了多个相同值的String对象时,它们实际是指向了同一空间的不一样引用罢了。这样对于String这样常用的对象访问代价和建立代价是十分低的。须要注意的是当使用String a="123";String b=new String("123");的时候,编译器虽然会建立一个新的String实例,可是实际值依然是指向常量池中的已有的123。咱们可使用a.intern(),String的intern方法返回常量池中的引用,intern是一个native本地方法。
      • 不变性:String对象生成后内存空间永久不会变化,好处是在多线程的状况下不用加锁同步操做。须要注意以下代码:String a="123";a="456";只是改变了对象的引用所指向的位置,实际的”123”是不变的。
    • 关于内存泄漏:算法

      • 存在内存泄漏的方法: 
        String:数据库

        • substring(int,int):

关于字符串分割和查找:apache

  • String的split: 
    split实现中使用了正则表达式,在大量字符串分割时正则表达式会贪婪匹配,效率会下降,不推荐使用。
    • StringTokenizer的使用: 
      StringTokenizer是jdk自带的字符串分割工具,因为没有使用正则匹配,因此速度更快,编程

      • StringBuffer和StringBuilder:设计模式

        • 区别:StringBuffer是线程安全的,全部操做字符串的方法都作了synchronized操做,而StringBuilder没有,是线程不安全的,因此StringBuffer性能低于StringBuilder。
        • 注意事项:StringBuffer和StringBuilder都提供了带有capacity参数的构造函数,主要做用是指定初始化容量(保存字符串缓冲区)的大小,当容量超过capacity时,会进行扩容,扩容为原来大小的2倍,建立新内存空间,同时把原来空间的内存拷贝到新内存空间,而后释放原内存空间。因为内存拷贝很耗时,因此最好指定适当的capacity。
        • 与String的+号对比:当使用+号拼接字符串时,编译器会把+号替换成new StringBuilder().append(),提升拼接效率,可是在大量循环拼接时,编译器不够智能,每次都生成新的StringBuilder,产生大量gc,因此性能不高,最好在循环中使用conact或本身构建StringBuffer或StringBuilder。List接口: 因为篇幅过长,故拆分,请参考《Java性能优化笔记-List接口分析》//TODO
          Map接口: 因为篇幅过长,故拆分,请参考《java性能优化笔记-Mapt接口分析》//TODO
          Set接口: 因为篇幅过长,故拆分,请参考《java性能优化笔记-Set接口分析》//TODO
          RadnomAccess接口: 因为篇幅过长,故拆分,请参考《java性能优化笔记-RadnomAccess接口分析》//TODO
            • 使用缓冲区:BufferedInput和BufferedOutput在上面的文章中已经介绍过了,一样BufferedWrtier和BufferedReader效率也很是高。优先使用缓冲区。
            • 使用静态方法:静态方法不须要构建实例就能够直接使用,而且因为方法区gc不多回收,且jvm会缓存经常使用的类,因此一些经常使用工具类封装成static的性能会更高。并且要比函数重载更具备表达意义。
            • 使用设计模式:在对象比较大时可使用原型模式替换new操做,尤为对象构造函数比较耗时时,能够直接使用原型模式clone对象,也可使用apache的commons下的BeanUtil中的clone方法。一样在一些业务下,可使用单例模式、享元模式、代理模式、工厂模式等经常使用的设计模式优化对象生成过程,提高性能。

 

 

衡量一个程序是否优质,能够从多个角度进行分析。其中,最多见的衡量标准是程序的时间复杂度、空间复杂度,以及代码的可读性、可扩展性。针对程序的时间复杂度和空间复杂度,想要优化程序代码,须要对数据结构算法有深刻的理解,而且熟悉计算机系统的基本概念和原理;而针对代码的可读性和可扩展性,想要优化程序代码,须要深刻理解软件架构设计,熟知并会应用合适的设计模式数组

首先,现在计算机系统的存储空间已经足够大了,达到了 TB 级别,所以相比于空间复杂度,时间复杂度是程序员首要考虑的因素。为了追求高性能,在某些频繁操做执行时,甚至能够考虑用空间换取时间。缓存

 

 1. 针对日志记录的优化

 2. 针对数据库链接的优化

共享数据库链接。共有 5 次数据库链接操做,每次都需从新创建数据库链接,数据库插入操做完成以后又当即释放了,数据库链接没有被复用。为了作到共享数据库链接,能够经过单例模式 (Singleton Pattern)得到一个相同的数据库链接,每次数据库链接操做都共享这个数据库链接。这里没有使用数据库链接池(Database Connection Pool)是由于在程序只有少许的数据库链接操做,只有在大量并发数据库链接的时候才须要链接池。

共享数据库链接而获得的性能提高的缘由是,数据库链接是一个耗时耗资源的操做,须要同远程计算机进行网络通讯,创建 TCP 链接,还须要维护链接状态表,创建数据缓冲区。若是共享数据库链接,则只须要进行一次数据库链接操做,省去了屡次从新创建数据库链接的时间。

3. 针对插入数据库记录的优化

4. 针对多线程的优化

使用多线程实现并发 / 并行。清空数据库表的操做,使用多线程而获得的性能提高的缘由是,系统部署所在的服务器是多核多处理器的,使用多线程,给每一个任务分配一个线程执行,能够充分地利用 CPU 计算资源。

6. 针对设计模式的优化,

 

回顾以上代码优化过程:关闭日志记录、共享数据库链接、使用预编译 SQL、使用 SQL 批处理、使用多线程实现并发 / 并行、使用 DAO 模式抽象出数据访问层,程序运行时间从最初的 100 秒左右下降到 15 秒如下,在性能上获得了很大的提高,同时也具备了更好的可读性和可扩展性。

 

十、当复制大量数据时,使用System.arraycopy()命令

十一、乘法和除法使用移位操做

十二、循环内不要不断建立对象引用

1三、基于效率和类型检查的考虑,应该尽量使用array,没法肯定数组大小时才使用ArrayList

1四、尽可能使用HashMap、ArrayList、StringBuilder,除非线程安全须要,不然不推荐使用Hashtable、Vector、StringBuffer,后三者因为使用同步机制而致使了性能开销

1六、尽可能在合适的场合使用单例

1七、尽可能避免随意使用静态变量

要知道,当某个对象被定义为static的变量所引用,那么gc一般是不会回收这个对象所占有的堆内存的

foreach循环的底层实现原理就是迭代器Iterator,

2一、将常量声明为static final,并以大写命名

这样在编译期间就能够把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也能够方便区分出常量与变量

五、使用带缓冲的输入输出流进行IO操做

带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这能够极大地提高IO效率

2六、顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList

这个,理解ArrayList和LinkedList的原理就知道了

字符串变量和字符串常量equals的时候将字符串常量写在前面

30、不要对数组使用toString()方法

31.把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+””最慢

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

public static void main(String[] args)  
  
{  
  
HashMap<String, String> hm = new HashMap<String, String>();  
  
hm.put(“111”, “222”);  
  
Set<Map.Entry<String, String>> entrySet = hm.entrySet();  
  
Iterator<Map.Entry<String, String>> iter = entrySet.iterator(); while (iter.hasNext())  
  
{  
  
Map.Entry<String, String> entry = iter.next();  
  
System.out.println(entry.getKey() + “\t” + entry.getValue());  
  
}  
  
}  

避免instanceof非预期结果;

(instanceof用来判断一个对象是不是一个类的实例,只能用于对象的判断,不能用于基本类型的判断(编译不经过),instanceof操做符的左右操做数必须有继承或实现关系,不然编译会失败。例:null instanceof String返回值是false,instanceof特有规则,若左操做数是null,结果就直接返回false,再也不运算右操做数是什么类)

建议19:断言绝对不是鸡肋;

(防护式编程中常用断言(Assertion)对参数和环境作出判断。断言是为调试程序服务的。两个特性:一、默认assert不启用;二、assert抛出的异常AssertionError是继承自Error的)。

建议21:用偶判断,不用奇判断;

(不要使用奇判断(i%2 == 1 ? "奇数" : "偶数"),使用偶判断(i%2 == 0 ? "偶数" : "奇数")。缘由Java中的取余(%标识符)算法测试数据输入1 2 0 -1 -2,奇判断的时候,当输入-1时,也会返回偶数。

建议22:用整数类型处理货币;

(不要使用float或者double计算货币,由于在计算机中浮点数“有可能”是不许确的,它只能无限接近准确值,而不能彻底精确。不能使用计算机中的二进制位来表示如0.4等的浮点数。解决方案:一、使用BigDecimal(优先使用);二、使用整型)。

建议44:推荐使用序列化实现对象的拷贝;

(经过序列化方式来处理,在内存中经过字节流的拷贝来实现深拷贝。使用此方法进行对象拷贝时需注意两点:一、对象的内部属性都是可序列化的;二、注意方法和属性的特殊修饰符,好比final、static、transient变量的序列化问题都会影响拷贝效果。一个简单办法,使用Apache下的commons工具包中的SerializationUtils类,直接使用更加简洁方便)。

建议56:自由选择字符串拼接方式;

(字符串拼接有三种方法:加号、concat方法及StringBuilder(或StringBuffer)的append方法。字符串拼接性能中,StringBuilder的append方法最快,concat方法次之,加号最慢。

第五章  数组和集合

建议60:性能考虑,数组是首选;

(性能要求较高的场景中使用数组替代集合)(基本类型在栈内存中操做,对象在堆内存中操做。数组中使用基本类型是效率最高的,使用集合类会伴随着自动装箱与自动拆箱动做,因此性能相对差一些)

建议64:多种最值算法,适时选择;

(最值计算时使用集合最简单,使用数组性能最优,利用Set集合去重,使用TreeSet集合自动排序)。

 

 

建议79:集合中的哈希码不要重复;

(列表查找无论是遍历查找、链表查找或者是二分查找都不够快。最快的是Hash开头的集合(如HashMap、HashSet等类)查找,原理:根据hashCode定位元素在数组中的位置。HashMap的table数组存储元素特色:一、table数组的长度永远是2的N次幂;二、table数组中的元素是Entry类型;三、table数组中的元素位置是不连续的;每一个Entry都有一个next变量,它会指向下一个键值对,用来链表的方式来处理Hash冲突的问题。若是Hash码相同,则添加的元素都使用链表处理,在查找的时候这部分的性能与ArrayList性能差很少)。

第六章  枚举和注解

建议83:推荐使用枚举定义常量;

(在项目开发中,推荐使用枚举常量替代接口常量和类常量)(常量分为:类常量、接口常量、枚举常量;枚举常量优势:一、枚举常量更简单;二、枚举常量属于稳态性(不容许发生越界);三、枚举具备内置方法,values方法能够获取到全部枚举值;四、枚举能够自定义方法)。

建议85:当心switch带来的空值异常;

(使用枚举值做为switch(枚举类);语句的条件值时,须要对枚举类进行判断是否为null值。由于Java中的switch语句只能判断byte、short、char、int类型,JDK7能够判断String类型,使用switch语句判断枚举类型时,会根据枚举的排序值匹配。若是传入的只是null的话,获取排序值须要调用如season.ordinal()方法时会抛出NullPointerException异常)。

建议98:建议采用的顺序是List<T>,List<?>,List<Object>;

(一、List<T>是肯定的某一个类型,编码者知道它是一个类型,只是在运行期才肯定而已;二、List<T>能够进行读写操做,List<?>是只读类型,由于编译器不知道List中容纳的是什么类型的元素,没法增长、修改,可是能删除,List<Object>也能够读写操做,只是此时已经失去了泛型存在的意义了)。

适时选择不一样的线程池来实现;

(Java的线程池实现从根本上来讲只有两个:ThreadPoolExecutor类和ScheduledThreadPoolExecutor类,仍是父子关系。为了简化并行计算,Java还提供了一个Executors的静态类,它能够直接生成多种不一样的线程池执行器,好比单线程执行器、带缓冲功能的执行器等,归根结底仍是以上两个类的封装类)。

建议128:预防线程死锁;

线程死锁(DeadLock)是多线程编码中最头疼问题,也是最难重现的问题,由于Java是单进程多线程语言。要达到线程死锁须要四个条件:一、互斥条件;二、资源独占条件;三、不剥夺条件;四、循环等待条件;按照如下两种方式来解决:一、避免或减小资源贡献;二、使用自旋锁,若是在获取自旋锁时锁已经有保持者,那么获取锁操做将“自旋”在那里,直到该自旋锁的保持者释放了锁为止)。

 

一、不要在循环条件中计算

二、尽量把变量、方法声明为final static类型

三、缩小变量的做用范围,目的是加快GC的回收;

四、频繁字符串操做使用StringBuilder或StringBuffer

相关文章
相关标签/搜索