1. 程式人生 > >30個精簡程式碼的技巧

30個精簡程式碼的技巧

前言:
優化程式碼,一個很重要的課題。可能有些人覺得沒用,一些細小的地方有什麼好修改的,
改與不改對程式碼的執行效率有什麼影響呢?這個問題我是真麼考慮的,好比人吃飯,吃一粒米,
沒用,但是一萬,十萬呢,這樣的效率就很可觀了。
程式碼優化的目標是:
1.減小程式碼體積;
2.提高程式碼執行效率。

具體方法:
1.儘量指定類,方法的final修飾符
帶有final的修飾符的類是不可派生的。在java核心API中,有許多應用final的例子,例如:
java.long.String,整個類都是final的。為類指定final修飾符可以讓類不可被繼承,為方法
指定final修飾符可以讓方法不被重寫。如果指定了一個類為final,則該類所有的方法都是final的。
java編譯器會尋找機會內聯所有的final方法,內聯對於提升java執行效率作用重大,
大概能使效能提升50%。
內聯:通常是用來消除呼叫函式時所需要的時間。
2.儘量複用物件
特別是String物件,出現字串連線時應該使用StringBuffer/StringBuilder代替。由於java虛擬機器
不僅要花時間生成物件,以後可能還需要對這些物件進行垃圾回收和處理,因此,生成過多物件
會給程式的效能帶來很大影響。
3.儘可能使用區域性變數
呼叫方法是傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧中,相對速度比較快。其他變數
如,靜態變數,例項變數等,都在堆中建立,速度較慢。另外,棧中建立的變數,隨著方法的
執行結束,這些內容就消失了,不需要額外的垃圾回收。
4.及時關閉流
java程式設計過程中,進行的資料庫連線,I/O流等操作時務必當心,在使用完畢後,應及時關閉流以
釋放資源。因為這些大物件的操作會造成系統大的開銷,會大大影響程式執行效率。
5.儘量減少對變數的重複計算
明確概念,對方法的呼叫,即使方法中只有一條語句,也是要載入的。包括建立堆疊。
呼叫方法時保護現場,方法結束時恢復現場等。如:
for(int i = 0 ; i < list.size();i++) {
}
可以替換為:
for(int i = 0,length=list.size();i < length;i++) {
}
這樣,如果list.size()裡的資料有很多時(如2000000左右),會減少很多效能消耗。
6.儘量使用懶載入策略,即在需要時才建立
如:
String str = “aaa”;
if(i == 1) {
list.add(str);
}
可以替換為:
if(i == 1) {
String str = “aaa”;
list.add(str);
}
7.慎用異常
異常對效能不利。丟擲異常首先要建立一個新的物件,Throwable介面的建構函式呼叫名為
filllnStackTrace()的本地同步方法,filllnStackTrace()方法檢查堆疊,收集呼叫跟蹤資訊。
只要有異常被丟擲,java虛擬機器就必須調整呼叫堆疊,因為在處理過程中建立了一個新的物件
。異常只能用於錯誤處理,不應該用來控制流程程式。
8.不要在迴圈中使用try···catch···,應該把它放到最外層。
9.如果能估計到待新增的內容長度,為底層以陣列方式實現的集合,工具類指定初始長度。
比如ArrayList、LinkedList、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,那麼:
		(1) 在4096的基礎上,在申請8194+2個大小的新陣列,加起來相當於共申請了12292的記憶體空間
		,如果一開始就指定長度5000或5500的話,就能節省一倍的空間。
		(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 = 0;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.及時清除不再需要的會話
	當應用伺服器需要儲存更多會話時,如果記憶體不足,作業系統會把部分資料轉移到磁盤裡,
	應用伺服器也可能根據MRU(最近頻繁使用的會話)演算法,把部分不活躍的會話轉存到磁盤裡,
	甚至可能丟擲記憶體不足的異常。如果會話要被轉存到磁碟,就必須先序列化,在大規模叢集
	中,對物件進行序列化代價是很大的。因此,應及時呼叫HttpSession的invalidate()方法
	清除會話。
19.實現RandomAccess介面的集合比如ArrayList,應當使用for迴圈而不是foreach來遍歷
	JDK API對於RandomAccess介面的解釋是:實現RandomAccess介面用來表明其支援快速隨機
	訪問,此介面的主要目的是允許一般的演算法更改其行為,從而將其應用到隨機或連續訪問列表
	時能夠提供良好的效能。
	實現RandomAccess介面類例項,加入是隨機訪問的,使用for迴圈比foreach效率高;如果不是
	隨機訪問的使用foreach效率高。
	如:
		if(list instanceof RandomAccess) {
			for(int i = 0 ;i < list.size();i++){} 
		} else {
			for(List li : list) {
				System.out.println(li);
			}
		}
		foreach底層實現原理就是迭代器(iterator)
20.使用同步程式碼塊代替同步方法
	除非能確定整個方法都是需要進行同步的,否則儘量使用同步程式碼塊,避免對那些不需要
	同步的程式碼也進行同步,從而影響效率。
21.將常量宣告為 STATIC FINAL
	這樣在編譯執行時就可以把這些內容放入常量池中,避免執行期間計算生成常量的值。另外,
	將常量的名字進行大寫的原因(初級就說明了,不懂的話50班見了)。
22.程式執行過程中避免使用反射
	反射是java提供給使用者一個很強大的功能,但是功能強大效率卻不是很高。不建議在程式執行
	過程中頻繁是哦那個反射機制,特別是Method的invoke方法。如果確實必要,建議將那些需要通過
	反射載入的類在專案啟動的時候通過反射例項化出一個物件並放入記憶體。
23.使用資料庫連線池和執行緒池
	這兩個池都是重用與物件的,前者可以避免頻繁開啟和關閉連線:
	後者可以避免頻繁建立和銷燬執行緒。
24.使用帶緩衝的輸入,輸出流進行I/O操作
	帶緩衝的輸入,輸出流即:
		BufferedReader,bufferedWrite,BufferedInputStream,BufferedOutputStream
		它們可以大大提升I/O的效率
25.順序插入和隨機訪問比較多的場景使用ArrayList,元素刪除和中間插入比較多的
	場景使用LinkedList
		理解這兩個集合有何不同即可
26.不要讓public方法中有太多形參
	public方法是對外提供的方法,如果給這些方法太多形參的話有兩點壞處:
		(1) 違反面相物件的思想,java講究萬物皆物件,太多形參,和java程式設計
		思想不和
		(2) 引數較多會導致出錯概率增加
27.字串變數和字串常量equals的時候將字串常量寫在前面
	如:
	Strring str = "123";
	if(str.equals("123")) {
	}
	可以替換為:
	Strring str = "123";
	if("123".equals(str)) {
	}
	這麼做是為了避免空指標的出現(中期專案有講過)
28.不要對陣列使用toString()方法
	本意是想列印數組裡的內容,卻可能因為陣列引用物件為空而導致空指標異常。雖然對陣列
	toString()沒有意義,但是對集合toString()是可以打印出集合中的內容的,因為集合的父類
	AbstractCollections重寫了Object的toString()方法。
29.不要對超出範圍的基本資料型別做向下強制轉換
	 得到的結果絕對是錯誤的。
30.把一個基本資料型別轉為字串,物件點toString()是最快的方法,物件點valueOf(資料)次之,
		資料+""最慢
		如,想把Integer i轉為字串型別,有三種方式:
			(1) i.toString()
			(2) i.valueOf(i)
			(3) 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;
			原理是:
				(1) String.valueOf()方法呼叫了Integer.toString()方法,但是
					在呼叫前先做了一次空判斷;
				(2) Integer.toString()是直接呼叫;
				(3) i + ""是使用了StringBuilder實現,先用了append方法拼接,在用
					toString()獲取字串