前言
程式碼最佳化,一個很重要的課題。或許有些人覺得沒用,一些細小的地方有什麼好修改的,改與不改對於程式碼的運作效率有什麼影響呢?這個問題我是這麼考慮的,就像大海裡的鯨魚一樣,牠吃一條小蝦米有用嗎?沒用,但是,吃的小蝦米一多之後,鯨魚就被餵飽了。程式碼最佳化也是一樣,如果專案著眼於盡快無BUG上線,那麼此時可以抓大放小,程式碼的細節可以不精打細磨;但是如果有足夠的時間開發、維護程式碼,這時候就必須考慮每個可以優化的細節了,一個一個細小的最佳化點累積起來,對於程式碼的運作效率絕對是有提升的。
程式碼最佳化的目標是:
1、減少程式碼的體積
#2、提高程式碼運行的效率
程式碼最佳化細節
1、盡量指定類別、方法的final修飾符
帶有final修飾符的類別是不可衍生的。在Java核心API中,有許多應用final的例子,例如java.lang.String,整個類別都是final的。為類別指定final修飾符可以讓類別不可以被繼承,為方法指定final修飾符可以讓方法不可以被重寫。如果指定了一個類別為final,則該類別所有的方法都是final的。 Java編譯器會尋找機會內聯所有的final方法,內嵌對於提升Java運作效率作用重大,具體參考Java執行期最佳化。此舉能夠使性能平均提高50%。
2、盡量重複使用物件
特別是String物件的使用,出現字串連接時應該使用StringBuilder/StringBuffer取代。由於Java虛擬機器不僅要花時間產生對象,以後可能還需要花時間對這些對象進行垃圾回收和處理,因此,產生過多的對象將會對程式的效能帶來很大的影響。
3、盡可能使用局部變數
呼叫方法時傳遞的參數以及在呼叫中建立的臨時變數都保存在堆疊中速度較快,其他變量,如靜態變數、實例變數等,都在堆中創建,速度較慢。另外,棧中創建的變量,隨著方法的運行結束,這些內容就沒了,不需要額外的垃圾回收。
4、及時關閉流程
Java程式設計過程中,進行資料庫連線、I/O串流操作時務必小心,在使用完畢後,及時關閉以釋放資源。因為這些大物件的操作會造成系統大的開銷,稍有不慎,將會導致嚴重的後果。
5、盡量減少對變數的重複計算
明確一個概念,對方法的調用,即使方法中只有一句語句,也是有消耗的,包括建立堆疊幀、調用方法時保護現場、呼叫方法完畢時恢復現場等。所以例如下面的操作:
for (int i = 0; i <p>建議替換為:</p><pre class='brush:php;toolbar:false;'>for (int i = 0, int length = list.size(); i < length; i++) {...}
這樣,在list.size()很大的時候,就減少了很多的消耗
6 、盡量採用懶加載的策略,即在需要的時候才創建
例如:
String str = "aaa"; if (i == 1){ list.add(str); }
建議替換為:
if (i == 1){ String str = "aaa"; list.add(str); }
7、慎用異常
異常對效能不利。拋出例外首先要建立一個新的對象,Throwable介面的建構子呼叫名為fillInStackTrace()的本地同步方法,fillInStackTrace()方法檢查堆疊,收集呼叫追蹤資訊。只要有異常被拋出,Java虛擬機器就必須調整呼叫堆疊,因為在處理過程中建立了一個新的物件。異常只能用於錯誤處理,不應該用來控製程式流程。
8、不要在循環中使用try…catch…,應該把其放在最外層
#除非不得已。如果毫無理由地這麼寫了,只要你的領導資深一點、有強迫症一點,八成就要罵你為什麼寫出這種垃圾代碼來了
9、如果能估計到待添加的內容長度,為底層以陣列方式實作的集合、工具類別指定初始長度
例如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder為例:
( 1)StringBuilder() // 預設分配16個字元的空間
(2)StringBuilder(int size) // 預設分配size個字元的空間
(3)StringBuilder(String str ) // 預設分配16個字元+str.length()個字元空間
可以透過類別(這裡指的不僅僅是上面的StringBuilder)的來設定它的初始化容量,這樣可以明顯地提升性能。比如StringBuilder吧,length表示目前的StringBuilder能保持的字元數量。因為當StringBuilder達到最大容量的時候,它會將自身容量增加到當前的2倍再加2,無論何時只要StringBuilder達到它的最大容量,它就不得不創建一個新的字符數組然後將舊的字符數組內容拷貝到新字元數組中—-這是十分耗費性能的一個操作。試想,如果能預估到字元數組中大概要存放5000個字元而不指定長度,最接近5000的2次冪是4096,每次擴容加的2不管,那麼:
(1)在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间
(2)把原来的4096个字符拷贝到新的的字符数组中去
这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。
10、当复制大量数据时,使用System.arraycopy()命令
11、乘法和除法使用移位操作
例如:
for (val = 0; val < 100000; val += 5){ a = val * 8; b = val / 2; }
用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,因此建议修改为:
for (val = 0; val < 100000; val += 5){ a = val << 3; b = val >> 1; }
移位操作虽然快,但是可能会使代码不太好理解,因此最好加上相应的注释。
12、循环内不要不断创建对象引用
例如:
for (int i = 1; i <= count; i++){ Object obj = new Object(); }
这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:
Object obj = null; for (int i = 0; i <= count; i++) { obj = new Object(); }
这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。
13、基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList
14、尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销
15、不要将数组声明为public static final
因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变
16、尽量在合适的场合使用单例
使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:
(1)控制资源的使用,通过线程同步来控制资源的并发访问
(2)控制实例的产生,以达到节约资源的目的
(3)控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
17、尽量避免随意使用静态变量
要知道,当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的,如:
public class A{ private static B b = new B(); }
此时静态变量b的生命周期与A类相同,如果A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止
18、及时清除不再需要的会话
为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,当会话不再需要时,应当及时调用HttpSession的invalidate()方法清除会话。
19、实现RandomAccess接口的集合比如ArrayList,应当使用最普通的for循环而不是foreach循环来遍历
这是JDK推荐给用户的。JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现RandomAccess接口的类实例,假如是随机访问的,使用普通for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高。可以使用类似如下的代码作判断:
if (list instanceof RandomAccess){ for (int i = 0; i < list.size(); i++){} }else{ Iterator<?> iterator = list.iterable(); while (iterator.hasNext()){ iterator.next() } }
foreach循环的底层实现原理就是迭代器Iterator,参见Java语法糖1:可变长度参数以及foreach循环原理。所以后半句”反过来,如果是顺序访问的,则使用Iterator会效率更高”的意思就是顺序访问的那些类实例,使用foreach循环去遍历。
20、使用同步代码块替代同步方法
这点在多线程模块中的synchronized锁方法块一文中已经讲得很清楚了,除非能确定一整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。
21、将常量声明为static final,并以大写命名
这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量
22、不要创建一些不使用的对象,不要导入一些不使用的类
这毫无意义,如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”,那么请删除这些无用的内容
23、程序运行过程中避免使用反射
关于,请参见反射。反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存—-用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。
24、使用数据库连接池和线程池
这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程
25、使用带缓冲的输入输出流进行IO操作
带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率
26、顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList
这个,理解ArrayList和LinkedList的原理就知道了
27、不要让public方法中有太多的形参
public方法即对外提供的方法,如果给这些方法太多形参的话主要有两点坏处:
1、违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合
2、参数太多势必导致方法调用的出错概率增加
至于这个”太多”指的是多少个,3、4个吧。比如我们用JDBC写一个insertStudentInfo方法,有10个学生信息字段要插如Student表中,可以把这10个参数封装在一个实体类中,作为insert方法的形参
28、字符串变量和字符串常量equals的时候将字符串常量写在前面
这是一个比较常见的小技巧了,如果有以下代码:
String str = "123"; if (str.equals("123")) { ... }
建议修改为:
String str = "123"; if ("123".equals(str)){ ... }
这么做主要是可以避免空指针异常
29、请知道,在java中if (i == 1)和if (1 == i)是没有区别的,但从阅读习惯上讲,建议使用前者
平时有人问,”if (i == 1)”和”if (1== i)”有没有区别,这就要从C/C++讲起。
在C/C++中,”if (i == 1)”判断条件成立,是以0与非0为基准的,0表示false,非0表示true,如果有这么一段代码:
int i = 2; if (i == 1){ ... }else{ ... }
C/C++判断”i==1″不成立,所以以0表示,即false。但是如果:
int i = 2; if (i = 1) { ... }else{ ... }
万一程序员一个不小心,把”if (i == 1)”写成”if (i = 1)”,这样就有问题了。在if之内将i赋值为1,if判断里面的内容非0,返回的就是true了,但是明明i为2,比较的值是1,应该返回的false。这种情况在C/C++的开发中是很可能发生的并且会导致一些难以理解的错误产生,所以,为了避免开发者在if语句中不正确的赋值操作,建议将if语句写为:
int i = 2; if (1 == i) { ... }else{ ... }
这样,即使开发者不小心写成了”1 = i”,C/C++编译器也可以第一时间检查出来,因为我们可以对一个变量赋值i为1,但是不能对一个常量赋值1为i。
但是,在Java中,C/C++这种”if (i = 1)”的语法是不可能出现的,因为一旦写了这种语法,Java就会编译报错”Type mismatch: cannot convert from int to boolean”。但是,尽管Java的”if (i == 1)”和”if (1 == i)”在语义上没有任何区别,但是从阅读习惯上讲,建议使用前者会更好些。
30、不要对数组使用toString()方法
看一下对数组使用toString()打印出来的是什么:
public static void main(String[] args){ int[] is = new int[]{1, 2, 3}; System.out.println(is.toString()); }
结果是:
[[email protected]
本意是想打印出数组内容,却有可能因为数组引用is为空而导致空指针异常。不过虽然对数组toString()没有意义,但是对集合toString()是可以打印出集合里面的内容的,因为集合的父类AbstractCollections
31、不要对超出范围的基本数据类型做向下强制转型
这绝不会得到想要的结果:
public static void main(String[] args){ long l = 12345678901234L; int i = (int)l; System.out.println(i); }
我们可能期望得到其中的某几位,但是结果却是:
1942892530
解释一下。Java中long是8个字节64位的,所以12345678901234在计算机中的表示应该是:
0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010
一个int型数据是4个字节32位的,从低位取出上面这串二进制数据的前32位是:
0111 0011 1100 1110 0010 1111 1111 0010
这串二进制表示为十进制1942892530,所以就是我们上面的控制台上输出的内容。从这个例子上还能顺便得到两个结论:
1、整型默认的数据类型是int,long l = 12345678901234L,这个数字已经超出了int的范围了,所以最后有一个L,表示这是一个long型数。顺便,浮点型的默认类型是double,所以定义float的时候要写成”"float f = 3.5f”
2、接下来再写一句”int ii = l + i;”会报错,因为long + int是一个long,不能赋值给int
32、公用的集合类中不使用的数据一定要及时remove掉
如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。
33、把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+”"最慢
把一个基本数据类型转为一般有三种方式,我有一个Integer型数据i,可以使用i.toString()、String.valueOf(i)、i+”"三种方式,三种方式的效率如何,看一个测试:
public static void main(String[] args){ int loopTime = 50000; Integer i = 0; long startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++){ String str = String.valueOf(i); } System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++){ String str = i.toString(); } System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++){ String str = i + ""; } System.out.println("i + \"\":" + (System.currentTimeMillis() - startTime) + "ms"); }
运行结果为:
String.valueOf():11ms Integer.toString():5ms i + "":25ms
所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单:
1、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断
2、Integer.toString()方法就不说了,直接调用了
3、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串
三者对比下来,明显是2最快、1次之、3最慢
34、使用最有效率的方式去遍历Map
遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是:
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()); } }
如果你只是想遍历一下这个Map的key值,那用”Set
35、对资源的close()建议分开操作
意思是,比如我有这么一段代码:
try{ XXX.close(); YYY.close(); }catch (Exception e){ ... }
建议修改为:
try{ XXX.close(); }catch (Exception e) { ... } try{ YYY.close(); }catch (Exception e) { ... }
虽然有些麻烦,却能避免资源泄露。我们想,如果没有修改过的代码,万一XXX.close()抛异常了,那么就进入了cath块中了,YYY.close()不会执行,YYY这块资源就不会回收了,一直占用
以上是分享35 個 Java 程式碼效能最佳化方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于结构化数据处理开源库SPL的相关问题,下面就一起来看一下java下理想的结构化数据处理类库,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于PriorityQueue优先级队列的相关知识,Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于java锁的相关问题,包括了独占锁、悲观锁、乐观锁、共享锁等等内容,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关问题,包括了线程安装、线程加锁与线程不安全的原因、线程安全的标准类等等内容,希望对大家有帮助。

本篇文章给大家带来了关于Java的相关知识,其中主要介绍了关于关键字中this和super的相关问题,以及他们的一些区别,下面一起来看一下,希望对大家有帮助。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于枚举的相关问题,包括了枚举的基本操作、集合类对枚举的支持等等内容,下面一起来看一下,希望对大家有帮助。

封装是一种信息隐藏技术,是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法;封装可以被认为是一个保护屏障,防止指定类的代码和数据被外部类定义的代码随机访问。封装可以通过关键字private,protected和public实现。

本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于设计模式的相关问题,主要将装饰器模式的相关内容,指在不改变现有对象结构的情况下,动态地给该对象增加一些职责的模式,希望对大家有帮助。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

禪工作室 13.0.1
強大的PHP整合開發環境

SublimeText3漢化版
中文版,非常好用

SublimeText3 Linux新版
SublimeText3 Linux最新版

記事本++7.3.1
好用且免費的程式碼編輯器

Dreamweaver CS6
視覺化網頁開發工具