程序代码优化要点:java
字符串优化:程序员
String对象特色:正则表达式
String a="123";String b=new String("123");
的时候,编译器虽然会建立一个新的String实例,可是实际值依然是指向常量池中的已有的123。咱们可使用a.intern(),String的intern方法返回常量池中的引用,intern是一个native本地方法。String a="123";a="456";
只是改变了对象的引用所指向的位置,实际的”123”是不变的。关于内存泄漏:算法
存在内存泄漏的方法:
String:数据库
关于字符串分割和查找:apache
StringTokenizer的使用:
StringTokenizer是jdk自带的字符串分割工具,因为没有使用正则匹配,因此速度更快,编程
StringBuffer和StringBuilder:设计模式
new StringBuilder().append()
,提升拼接效率,可是在大量循环拼接时,编译器不够智能,每次都生成新的StringBuilder,产生大量gc,因此性能不高,最好在循环中使用conact或本身构建StringBuffer或StringBuilder。List接口: 因为篇幅过长,故拆分,请参考《Java性能优化笔记-List接口分析》//TODO
衡量一个程序是否优质,能够从多个角度进行分析。其中,最多见的衡量标准是程序的时间复杂度、空间复杂度,以及代码的可读性、可扩展性。针对程序的时间复杂度和空间复杂度,想要优化程序代码,须要对数据结构与算法有深刻的理解,而且熟悉计算机系统的基本概念和原理;而针对代码的可读性和可扩展性,想要优化程序代码,须要深刻理解软件架构设计,熟知并会应用合适的设计模式数组
首先,现在计算机系统的存储空间已经足够大了,达到了 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