• 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):

    关于字符串分割和查找:

    • 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 秒以下,在性能上得到了很大的提升,同时也具有了更好的可读性和可扩展性。

     

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

    11、乘法和除法使用移位操作

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

    13、基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList

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

    16、尽量在合适的场合使用单例

    17、尽量避免随意使用静态变量

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

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

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

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

    5、使用带缓冲的输入输出流进行IO操作

    带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率

    26、顺序插入和随机访问比较多的场景使用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() + “	” + entry.getValue());  
      
    }  
      
    }  

    避免instanceof非预期结果;

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

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

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

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

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

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

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

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

    (通过序列化方式来处理,在内存中通过字节流的拷贝来实现深拷贝。使用此方法进行对象拷贝时需注意两点:1、对象的内部属性都是可序列化的;2、注意方法和属性的特殊修饰符,比如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数组存储元素特点:1、table数组的长度永远是2的N次幂;2、table数组中的元素是Entry类型;3、table数组中的元素位置是不连续的;每个Entry都有一个next变量,它会指向下一个键值对,用来链表的方式来处理Hash冲突的问题。如果Hash码相同,则添加的元素都使用链表处理,在查找的时候这部分的性能与ArrayList性能差不多)。

    第六章  枚举和注解

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

    (在项目开发中,推荐使用枚举常量替代接口常量和类常量)(常量分为:类常量、接口常量、枚举常量;枚举常量优点:1、枚举常量更简单;2、枚举常量属于稳态性(不允许发生越界);3、枚举具有内置方法,values方法可以获取到所有枚举值;4、枚举可以自定义方法)。

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

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

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

    (1、List<T>是确定的某一个类型,编码者知道它是一个类型,只是在运行期才确定而已;2、List<T>可以进行读写操作,List<?>是只读类型,因为编译器不知道List中容纳的是什么类型的元素,无法增加、修改,但是能删除,List<Object>也可以读写操作,只是此时已经失去了泛型存在的意义了)。

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

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

    建议128:预防线程死锁;

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

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

    2、尽可能把变量、方法声明为final static类型

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

    4、频繁字符串操作使用StringBuilder或StringBuffer

  • 相关阅读:
    找“水王”
    用户体验评价
    学习进度12
    个人冲刺10
    学习进度13
    学习进度11
    个人冲刺9
    myeclipse中js报错
    安卓调用三个系统app(打电话,发短息,打开网页)
    关系图绘制详解
  • 原文地址:https://www.cnblogs.com/huojg-21442/p/7473887.html
Copyright © 2020-2023  润新知