天天看點

字元串操作優化

字元串操作優化

字元串對象

字元串對象或者其等價對象 (如 char 數組),在記憶體中總是占領最大的空間塊。是以怎樣高效地處理字元串,是提高系統總體性能的關鍵。

String 對象能夠覺得是 char 數組的延伸和進一步封裝,它主要由 3 部分組成:char 數組、偏移量和 String 的長度。

char 數組表示 String 的内容。它是 String 對象所表示字元串的超集。String 的真實内容還須要由偏移量和長度在這個 char 數組中進行定位和截取。

String 有 3 個基本特點:

1. 不變性;

2. 針對常量池的優化。

3. 類的 final 定義。

不變性指的是 String 對象一旦生成。則不能再對它進行改變。String 的這個特性能夠泛化成不變 (immutable) 模式,即一個對象的狀态在對象被建立之後就不再發生變化。

不變模式的主要作用在于當一個對象須要被多線程共享。而且訪問頻繁時,能夠省略同步和鎖等待的時間,進而大幅提高系統性能。

針對常量池的優化指的是當兩個 String 對象擁有同樣的值時,它們僅僅引用常量池中的同一個拷貝,當同一個字元串重複出現時。這個技術能夠大幅度節省記憶體空間。

以下代碼 str1、str2、str4 引用了同樣的位址,可是 str3 卻又一次開辟了一塊記憶體空間,盡管 str3 單獨占用了堆空間,可是它所指向的實體和 str1 全然一樣。代碼例如以下清單 1 所看到的。

清單 1. 示範樣例代碼
public class StringDemo {
 public static void main(String[] args){
 String str1 = "abc";
 String str2 = "abc";
 String str3 = new String("abc");
 String str4 = str1;
 System.out.println("is str1 = str2?"+(str1==str2));
 System.out.println("is str1 = str3?"+(str1==str3));
 System.out.println("is str1 refer to str3?      

"+(str1.intern()==str3.intern())); System.out.println("is str1 = str4"+(str1==str4)); System.out.println("is str2 = str4"+(str2==str4)); System.out.println("is str4 refer to str3?

"+(str4.intern()==str3.intern())); } }

輸出如清單 2 所看到的。

清單 2. 輸出結果
is str1 = str2?      

true is str1 = str3?

false is str1 refer to str3?true is str1 = str4true is str2 = str4true is str4 refer to str3?true

SubString 使用技巧

String 的 substring 方法源代碼在最後一行建立了一個 String 對象。new String(offset+beginIndex,endIndex-beginIndex,value);該行代碼的目的是為了能高效且高速地共享 String 内的 char 數組對象。但在這樣的通過偏移量來截取字元串的方法中。String 的原生内容 value 數組被拷貝到新的子字元串中。設想,假設原始字元串非常大,截取的字元長度卻非常短,那麼截取的子字元串中包括了原生字元串的全部内容,并占領了對應的記憶體空間,而隻通過偏移量和長度來決定自己的實際取值。

這樣的算法提高了速度卻浪費了空間。

以下代碼示範了使用 substring 方法在一個非常大的 string 獨享裡面截取一段非常小的字元串,假設採用 string 的 substring 方法會造成記憶體溢出,假設採用重複建立新的 string 方法能夠確定正常執行。

清單 3.substring 方法示範
import java.util.ArrayList;
import java.util.List;

public class StringDemo {
 public static void main(String[] args){
 List<String> handler = new ArrayList<String>();
 for(int i=0;i<1000;i++){
 HugeStr h = new HugeStr();
 ImprovedHugeStr h1 = new ImprovedHugeStr();
 handler.add(h.getSubString(1, 5));
 handler.add(h1.getSubString(1, 5));
 }
 }

 static class HugeStr{
 private String str = new String(new char[800000]);
 public String getSubString(int begin,int end){
 return str.substring(begin, end);
 }
 }

 static class ImprovedHugeStr{
 private String str = new String(new char[10000000]);
 public String getSubString(int begin,int end){
 return new String(str.substring(begin, end));
 }
 }
}      

輸出結果如清單 4 所看到的。

清單 4. 輸出結果
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.lang.StringValue.from(Unknown Source)
at java.lang.String.<init>(Unknown Source)
at StringDemo$ImprovedHugeStr.<init>(StringDemo.java:23)
at StringDemo.main(StringDemo.java:9)      

ImprovedHugeStr 能夠工作是由于它使用沒有記憶體洩漏的 String 構造函數又一次生成了 String 對象,使得由 substring() 方法傳回的、存在記憶體洩漏問題的 String 對象失去全部的強引用,進而被垃圾回收器識别為垃圾對象進行回收,保證了系統記憶體的穩定。

String 的 split 方法支援傳入正則表達式幫助處理字元串,可是簡單的字元串切割時性能較差。

對照 split 方法和 StringTokenizer 類的處理字元串性能,代碼如清單 5 所看到的。

切分字元串方式讨論

String 的 split 方法支援傳入正則表達式幫助處理字元串。操作較為簡單。可是缺點是它所依賴的算法在對簡單的字元串切割時性能較差。

清單 5 所看到的代碼對照了 String 的 split 方法和調用 StringTokenizer 類來處理字元串時性能的差距。

清單 5.String 的 split 方法示範
import java.util.StringTokenizer;

public class splitandstringtokenizer {
 public static void main(String[] args){
 String orgStr = null;
 StringBuffer sb = new StringBuffer();
 for(int i=0;i<100000;i++){
 sb.append(i);
 sb.append(",");
 }
 orgStr = sb.toString();
 long start = System.currentTimeMillis();
 for(int i=0;i<100000;i++){
 orgStr.split(",");
 }
 long end = System.currentTimeMillis();
 System.out.println(end-start);

 start = System.currentTimeMillis();
 String orgStr1 = sb.toString();
 StringTokenizer st = new StringTokenizer(orgStr1,",");
 for(int i=0;i<100000;i++){
 st.nextToken();
 }
 st = new StringTokenizer(orgStr1,",");
 end = System.currentTimeMillis();
 System.out.println(end-start);

 start = System.currentTimeMillis();
 String orgStr2 = sb.toString();
 String temp = orgStr2;
 while(true){
 String splitStr = null;
 int j=temp.indexOf(",");
 if(j<0)break;
 splitStr=temp.substring(0, j);
 temp = temp.substring(j+1);
 }
 temp=orgStr2;
 end = System.currentTimeMillis();
 System.out.println(end-start);
 }
}      

輸出如清單 6 所看到的:

清單 6. 執行輸出結果
39015
16
15      

當一個 StringTokenizer 對象生成後,通過它的 nextToken() 方法便能夠得到下一個切割的字元串,通過 hasMoreToken 方法能夠知道是否有很多其它的字元串須要處理。對照發現 split 的耗時非常的長,採用 StringTokenizer 對象處理速度非常快。

我們嘗試自己實作字元串切割算法。使用 substring 方法和 indexOf 方法組合而成的字元串切割算法能夠幫助非常快切分字元串并替換内容。

因為 String 是不可變對象,是以。在須要對字元串進行改動操作時 (如字元串連接配接、替換),String 對象會生成新的對象。是以其性能相對較差。

可是 JVM 會對代碼進行徹底的優化,将多個連接配接操作的字元串在編譯時合成一個單獨的長字元串。

以上執行個體執行結果差異較大的原因是 split 算法對每個字元進行了對照。這樣當字元串較大時。須要把整個字元串讀入記憶體,逐一查找,找到符合條件的字元,這樣做較為耗時。

而 StringTokenizer 類同意一個應用程式進入一個令牌(tokens),StringTokenizer 類的對象在内部已經辨別化的字元串中維持了目前位置。一些操作使得在現有位置上的字元串提前得到處理。 一個令牌的值是由獲得其以前建立 StringTokenizer 類對象的字串所傳回的。

清單 7.split 類源碼
import java.util.ArrayList;

public class Split {
public String[] split(CharSequence input, int limit) { 
int index = 0; 
boolean matchLimited = limit > 0; 
ArrayList<String> matchList = new ArrayList<String>(); 
Matcher m = matcher(input); 
// Add segments before each match found 
while(m.find()) { 
if (!matchLimited || matchList.size() < limit - 1) { 
String match = input.subSequence(index, m.start()).toString(); 
matchList.add(match); 
index = m.end(); 
} else if (matchList.size() == limit - 1) { 
// last one 
String match = input.subSequence(index,input.length()).toString(); 
matchList.add(match); 
index = m.end(); 
} 
} 
// If no match was found, return this 
if (index == 0){ 
return new String[] {input.toString()}; 
}
// Add remaining segment 
if (!matchLimited || matchList.size() < limit){ 
matchList.add(input.subSequence(index, input.length()).toString()); 
}
// Construct result 
int resultSize = matchList.size(); 
if (limit == 0){ 
while (resultSize > 0 && matchList.get(resultSize-1).equals("")) 
resultSize--; 
 String[] result = new String[resultSize]; 
 return matchList.subList(0, resultSize).toArray(result); 
}
}

}      

split 借助于資料對象及字元查找算法完畢了資料切割,适用于資料量較少場景。

合并字元串

因為 String 是不可變對象,是以。在須要對字元串進行改動操作時 (如字元串連接配接、替換),String 對象會生成新的對象,是以其性能相對較差。

可是 JVM 會對代碼進行徹底的優化,将多個連接配接操作的字元串在編譯時合成一個單獨的長字元串。

針對超大的 String 對象。我們採用 String 對象連接配接、使用 concat 方法連接配接、使用 StringBuilder 類等多種方式。代碼如清單 8 所看到的。

清單 8. 處理超大 String 對象的示範樣例代碼
public class StringConcat {
 public static void main(String[] args){
 String str = null;
 String result = "";

 long start = System.currentTimeMillis();
 for(int i=0;i<10000;i++){
 str = str + i;
 }
 long end = System.currentTimeMillis();
 System.out.println(end-start);

 start = System.currentTimeMillis();
 for(int i=0;i<10000;i++){
 result = result.concat(String.valueOf(i));
 }
 end = System.currentTimeMillis();
 System.out.println(end-start);

 start = System.currentTimeMillis();
 StringBuilder sb = new StringBuilder();
 for(int i=0;i<10000;i++){
 sb.append(i);
 }
 end = System.currentTimeMillis();
 System.out.println(end-start);
 }
}      

輸出如清單 9 所看到的。

清單 9. 執行輸出結果
375
187
0      

盡管第一種方法編譯器推斷 String 的加法執行成 StringBuilder 實作,可是編譯器沒有做出足夠聰明的推斷,每次循環都生成了新的 StringBuilder 執行個體進而大大減少了系統性能。

StringBuffer 和 StringBuilder 都實作了 AbstractStringBuilder 抽象類。擁有差點兒同樣的對外借口,兩者的最大不同在于 StringBuffer 對差點兒全部的方法都做了同步,而 StringBuilder 并沒有不論什麼同步。因為方法同步須要消耗一定的系統資源,是以,StringBuilder 的效率也好于 StringBuffer。

可是,在多線程系統中,StringBuilder 無法保證線程安全。不能使用。代碼如清單 10 所看到的。

清單 10.StringBuilderVSStringBuffer
public class StringBufferandBuilder {
public StringBuffer contents = new StringBuffer(); 
public StringBuilder sbu = new StringBuilder();

public void log(String message){ 
for(int i=0;i<10;i++){ 
/*
contents.append(i); 
contents.append(message); 
contents.append("/n"); 
*/
contents.append(i);
contents.append("/n");
sbu.append(i);
sbu.append("/n");
} 
} 
public void getcontents(){ 
//System.out.println(contents); 
System.out.println("start print StringBuffer");
System.out.println(contents); 
System.out.println("end print StringBuffer");
}
public void getcontents1(){ 
//System.out.println(contents); 
System.out.println("start print StringBuilder");
System.out.println(sbu); 
System.out.println("end print StringBuilder");
}

 public static void main(String[] args) throws InterruptedException { 
StringBufferandBuilder ss = new StringBufferandBuilder(); 
runthread t1 = new runthread(ss,"love");
runthread t2 = new runthread(ss,"apple");
runthread t3 = new runthread(ss,"egg");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}

}

class runthread extends Thread{ 
String message; 
StringBufferandBuilder buffer; 
public runthread(StringBufferandBuilder buffer,String message){ 
this.buffer = buffer;
this.message = message; 
} 
public void run(){ 
while(true){ 
buffer.log(message); 
//buffer.getcontents();
buffer.getcontents1();
try {
sleep(5000000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} 
} 

}      

輸出結果如清單 11 所看到的。

清單 11. 執行結果
start print StringBuffer
0123456789
end print StringBuffer
start print StringBuffer
start print StringBuilder
01234567890123456789
end print StringBuffer
start print StringBuilder
01234567890123456789
01234567890123456789
end print StringBuilder
end print StringBuilder
start print StringBuffer
012345678901234567890123456789
end print StringBuffer
start print StringBuilder
012345678901234567890123456789
end print StringBuilder      

StringBuilder 資料并沒有依照預想的方式進行操作。

StringBuilder 和 StringBuffer 的擴充政策是将原有的容量大小翻倍,以新的容量申請記憶體空間。建立新的 char 數組,然後将原數組中的内容拷貝到這個新的數組中。是以,對于大對象的擴容會涉及大量的記憶體複制操作。

假設可以預先評估大小,會提高性能。

資料定義、運算邏輯優化

使用局部變量

調用方法時傳遞的參數以及在調用中建立的暫時變量都儲存在棧 (Stack) 裡面,讀寫速度較快。其它變量,如靜态變量、執行個體變量等,都在堆 (heap) 中建立。讀寫速度較慢。

清單 12 所看到的代碼示範了使用局部變量和靜态變量的操作時間對照。

清單 12. 局部變量 VS 靜态變量
public class variableCompare {
public static int b = 0;
 public static void main(String[] args){
 int a = 0;
 long starttime = System.currentTimeMillis();
 for(int i=0;i<1000000;i++){
 a++;//在函數體内定義局部變量
 }
 System.out.println(System.currentTimeMillis() - starttime);

 starttime = System.currentTimeMillis();
 for(int i=0;i<1000000;i++){
 b++;//在函數體内定義局部變量
 }
 System.out.println(System.currentTimeMillis() - starttime);
 }
}      

執行後輸出如清單 13 所看到的。

清單 13. 執行結果
0
15      

以上兩段代碼的執行時間分别為 0ms 和 15ms。由此可見,局部變量的訪問速度遠遠高于類的成員變量。

位運算取代乘除法

位運算是全部的運算中最為高效的。

是以,能夠嘗試使用位運算取代部分算數運算,來提高系統的執行速度。最典型的就是對于整數的乘除運算優化。清單 14 所看到的代碼是一段使用算數運算的實作。

清單 14. 算數運算
public class yunsuan {
 public static void main(String args[]){
 long start = System.currentTimeMillis();
 long a=1000;
 for(int i=0;i<10000000;i++){
 a*=2;
 a/=2;
 }
 System.out.println(a);
 System.out.println(System.currentTimeMillis() - start);
 start = System.currentTimeMillis();
 for(int i=0;i<10000000;i++){
 a<<=1;
 a>>=1;
 }
 System.out.println(a);
 System.out.println(System.currentTimeMillis() - start);
 }
}      

執行輸出如清單 15 所看到的。

清單 15. 執行結果
1000
546
1000
63      

兩段代碼運作了全然同樣的功能,在每次循環中。整數 1000 乘以 2,然後除以 2。第一個循環耗時 546ms,第二個循環耗時 63ms。

替換 switch

keyword switch 語句用于多條件推斷,switch 語句的功能類似于 if-else 語句,兩者的性能幾乎相同。

可是 switch 語句有性能提升空間。清單 16 所看到的代碼示範了 Switch 與 if-else 之間的對照。

清單 16.Switch 示範樣例
public class switchCompareIf {

public static int switchTest(int value){
int i = value%10+1;
switch(i){
case 1:return 10;
case 2:return 11;
case 3:return 12;
case 4:return 13;
case 5:return 14;
case 6:return 15;
case 7:return 16;
case 8:return 17;
case 9:return 18;
default:return -1;
}
}

public static int arrayTest(int[] value,int key){
int i = key%10+1;
if(i>9 || i<1){
return -1;
}else{
return value[i];
}
}

 public static void main(String[] args){
 int chk = 0;
 long start=System.currentTimeMillis();
 for(int i=0;i<10000000;i++){
 chk = switchTest(i);
 }
 System.out.println(System.currentTimeMillis()-start);
 chk = 0;
 start=System.currentTimeMillis();
 int[] value=new int[]{0,10,11,12,13,14,15,16,17,18};
 for(int i=0;i<10000000;i++){
 chk = arrayTest(value,i);
 }
 System.out.println(System.currentTimeMillis()-start);
 }
}      

執行輸出如清單 17 所看到的。

清單 17. 執行結果
172
93      

使用一個連續的數組取代 switch 語句,因為對資料的随機訪問很快。至少好于 switch 的分支推斷。從上面樣例能夠看到比較的效率差距近乎 1 倍。switch 方法耗時 172ms,if-else 方法耗時 93ms。

一維數組取代二維數組

JDK 非常多類庫是採用數組方式實作的資料存儲。比方 ArrayList、Vector 等,數組的長處是随機訪問性能非常好。一維數組和二維數組的訪問速度不一樣,一維數組的訪問速度要優于二維數組。在性能敏感的系統中要使用二維數組,盡量将二維數組轉化為一維數組再進行處理。以提高系統的響應速度。

清單 18. 數組方式對照
public class arrayTest {
 public static void main(String[] args){
 long start = System.currentTimeMillis();
 int[] arraySingle = new int[1000000];
 int chk = 0;
 for(int i=0;i<100;i++){
 for(int j=0;j<arraySingle.length;j++){
 arraySingle[j] = j;
 }
 }
 for(int i=0;i<100;i++){
 for(int j=0;j<arraySingle.length;j++){
 chk = arraySingle[j];
 }
 }
 System.out.println(System.currentTimeMillis() - start);

 start = System.currentTimeMillis();
 int[][] arrayDouble = new int[1000][1000];
 chk = 0;
 for(int i=0;i<100;i++){
 for(int j=0;j<arrayDouble.length;j++){
 for(int k=0;k<arrayDouble[0].length;k++){
 arrayDouble[i][j]=j;
 }
 }
 }
 for(int i=0;i<100;i++){
 for(int j=0;j<arrayDouble.length;j++){
 for(int k=0;k<arrayDouble[0].length;k++){
 chk = arrayDouble[i][j];
 }
 }
 }
 System.out.println(System.currentTimeMillis() - start);

 start = System.currentTimeMillis();
 arraySingle = new int[1000000];
 int arraySingleSize = arraySingle.length;
 chk = 0;
 for(int i=0;i<100;i++){
 for(int j=0;j<arraySingleSize;j++){
 arraySingle[j] = j;
 }
 }
 for(int i=0;i<100;i++){
 for(int j=0;j<arraySingleSize;j++){
 chk = arraySingle[j];
 }
 }
 System.out.println(System.currentTimeMillis() - start);

 start = System.currentTimeMillis();
 arrayDouble = new int[1000][1000];
 int arrayDoubleSize = arrayDouble.length;
 int firstSize = arrayDouble[0].length;
 chk = 0;
 for(int i=0;i<100;i++){
 for(int j=0;j<arrayDoubleSize;j++){
 for(int k=0;k<firstSize;k++){
 arrayDouble[i][j]=j;
 }
 }
 }
 for(int i=0;i<100;i++){
 for(int j=0;j<arrayDoubleSize;j++){
 for(int k=0;k<firstSize;k++){
 chk = arrayDouble[i][j];
 }
 }
 }
 System.out.println(System.currentTimeMillis() - start);
 }
}      

執行輸出如清單 19 所看到的。

清單 19. 執行結果
343
624
287
390      

第一段代碼操作的是一維數組的指派、取值過程,第二段代碼操作的是二維數組的指派、取值過程。能夠看到一維數組方式比二維數組方式快接近一半時間。而對于數組内假設能夠降低指派運算。則能夠進一步降低運算耗時。加快程式執行速度。

提取表達式

大部分情況下。代碼的反複勞動因為計算機的快速執行。并不會對性能構成太大的威脅,但若希望将系統性能發揮到極緻,還是有非常多地方能夠優化的。

清單 20. 提取表達式
public class duplicatedCode {
 public static void beforeTuning(){
 long start = System.currentTimeMillis();
 double a1 = Math.random();
 double a2 = Math.random();
 double a3 = Math.random();
 double a4 = Math.random();
 double b1,b2;
 for(int i=0;i<10000000;i++){
 b1 = a1*a2*a4/3*4*a3*a4;
 b2 = a1*a2*a3/3*4*a3*a4;
 }
 System.out.println(System.currentTimeMillis() - start);
 }

 public static void afterTuning(){
 long start = System.currentTimeMillis();
 double a1 = Math.random();
 double a2 = Math.random();
 double a3 = Math.random();
 double a4 = Math.random();
 double combine,b1,b2;
 for(int i=0;i<10000000;i++){
 combine = a1*a2/3*4*a3*a4;
 b1 = combine*a4;
 b2 = combine*a3;
 }
 System.out.println(System.currentTimeMillis() - start);
 }

 public static void main(String[] args){
 duplicatedCode.beforeTuning();
 duplicatedCode.afterTuning();
 }
}      

執行輸出如清單 21 所看到的。

清單 21. 執行結果
202
110      

兩段代碼的差別是提取了反複的公式。使得這個公式的每次循環計算僅僅運作一次。分别耗時 202ms 和 110ms,可見,提取複雜的反複操作是相當具有意義的。這個樣例告訴我們,在循環體内,假設可以提取到循環體外的計算公式。最好提取出來,盡可能讓程式少做反複的計算。

優化循環

當性能問題成為系統的主要沖突時,能夠嘗試優化循環,比如降低循環次數,這樣或許能夠加快程式執行速度。

清單 22. 降低循環次數
public class reduceLoop {
public static void beforeTuning(){
 long start = System.currentTimeMillis();
 int[] array = new int[9999999];
 for(int i=0;i<9999999;i++){
 array[i] = i;
 }
 System.out.println(System.currentTimeMillis() - start);
}

public static void afterTuning(){
 long start = System.currentTimeMillis();
 int[] array = new int[9999999];
 for(int i=0;i<9999999;i+=3){
 array[i] = i;
 array[i+1] = i+1;
 array[i+2] = i+2;
 }
 System.out.println(System.currentTimeMillis() - start);
}

public static void main(String[] args){
reduceLoop.beforeTuning();
reduceLoop.afterTuning();
}
}      

執行輸出如清單 23 所看到的。

清單 23. 執行結果
265
31      

這個樣例能夠看出,通過降低循環次數,耗時縮短為原來的 1/8。

布爾運算取代位運算

盡管位運算的速度遠遠高于算術運算,可是在條件推斷時,使用位運算替代布爾運算确實是很錯誤的選擇。在條件推斷時,Java 會對布爾運算做相當充分的優化。如果有表達式 a、b、c 進行布爾運算“a&&b&&c”,依據邏輯與的特點,僅僅要在整個布爾表達式中有一項傳回 false,整個表達式就傳回 false。是以,當表達式 a 為 false 時,該表達式将馬上傳回 false。而不會再去計算表達式 b 和 c。

若此時。表達式 a、b、c 須要消耗大量的系統資源。這樣的處理方式能夠節省這些計算資源。同理,當計算表達式“a||b||c”時。僅僅要 a、b 或 c,3 個表達式當中随意一個計算結果為 true 時,總體表達式馬上傳回 true。而不去計算剩餘表達式。

簡單地說,在布爾表達式的計算中,僅僅要表達式的值能夠确定,就會馬上傳回,而跳過剩餘子表達式的計算。

若使用位運算 (按位與、按位或) 取代邏輯與和邏輯或。盡管位運算本身沒有性能問題,可是位運算總是要将所有的子表達式所有計算完畢後,再給出終于結果。是以,從這個角度看,使用位運算替代布爾運算會使系統進行非常多無效計算。

清單 24. 運算方式對照
public class OperationCompare {
 public static void booleanOperate(){
 long start = System.currentTimeMillis();
 boolean a = false;
 boolean b = true;
 int c = 0;
 //以下循環開始進行位運算。表達式裡面的全部計算因子都會被用來計算
 for(int i=0;i<1000000;i++){
 if(a&b&"Test_123".contains("123")){
 c = 1;
 }
 }
 System.out.println(System.currentTimeMillis() - start);
 }

 public static void bitOperate(){
 long start = System.currentTimeMillis();
 boolean a = false;
 boolean b = true;
 int c = 0;
 //以下循環開始進行布爾運算,僅僅計算表達式 a 就可以滿足條件
 for(int i=0;i<1000000;i++){
 if(a&&b&&"Test_123".contains("123")){
 c = 1;
 }
 }
 System.out.println(System.currentTimeMillis() - start);
 }

 public static void main(String[] args){
 OperationCompare.booleanOperate();
 OperationCompare.bitOperate();
 }
}      

執行輸出如清單 25 所看到的。

清單 25. 執行結果
63
0      

執行個體顯示布爾計算大大優于位運算,可是,這個結果不能說明位運算比邏輯運算慢。由于在全部的邏輯與運算中,都省略了表達式“”Test_123″.contains(“123″)”的計算,而全部的位運算都沒能省略這部分系統開銷。

使用 arrayCopy()

資料複制是一項使用頻率非常高的功能。JDK 中提供了一個高效的 API 來實作它。System.arraycopy() 函數是 native 函數,通常 native 函數的性能要優于普通的函數。是以,僅處于性能考慮,在軟體開發中。應盡可能調用 native 函數。ArrayList 和 Vector 大量使用了 System.arraycopy 來操作資料。特别是同一數組内元素的移動及不同數組之間元素的複制。arraycopy 的本質是讓處理器利用一條指令處理一個數組中的多條記錄,有點像彙編語言裡面的串操作指令 (LODSB、LODSW、LODSB、STOSB、STOSW、STOSB),僅僅需指定頭指針,然後開始循環就可以。即運作一次指令,指針就後移一個位置。操作多少資料就循環多少次。假設在應用程式中須要進行數組複制,應該使用這個函數,而不是自己實作。

詳細應用如清單 26 所看到的。

清單 26. 複制資料樣例
public class arrayCopyTest {
public static void arrayCopy(){
int size = 10000000;
 int[] array = new int[size];
 int[] arraydestination = new int[size];
 for(int i=0;i<array.length;i++){
 array[i] = i;
 }
 long start = System.currentTimeMillis();
 for(int j=0;j>1000;j++){
 System.arraycopy(array, 0, arraydestination, 0, size);//使用 System 級别的本地 arraycopy 方式
 }
 System.out.println(System.currentTimeMillis() - start);
}

public static void arrayCopySelf(){
int size = 10000000;
 int[] array = new int[size];
 int[] arraydestination = new int[size];
 for(int i=0;i<array.length;i++){
 array[i] = i;
 }
 long start = System.currentTimeMillis();
 for(int i=0;i<1000;i++){
 for(int j=0;j<size;j++){
 arraydestination[j] = array[j];//自己實作的方式,採用數組的資料互換方式
 }
 }
 System.out.println(System.currentTimeMillis() - start);
}

 public static void main(String[] args){
 arrayCopyTest.arrayCopy();
 arrayCopyTest.arrayCopySelf();
 }
}      

輸出如清單 27 所看到的。

清單 27. 執行結果
0
23166      

上面的樣例顯示採用 arraycopy 方法運作複制會很的快。原因就在于 arraycopy 屬于本地方法,源碼如清單 28 所看到的。

清單 28.arraycopy 方法
public static native void arraycopy(Object src, int srcPos, 
Object dest, int destPos, 
int length);      

src – 源數組。srcPos – 源數組中的起始位置; dest – 目标數組;destPos – 目标資料中的起始位置。length – 要複制的數組元素的數量。清單 28 所看到的方法使用了 native keyword。調用的為 C++編寫的底層函數。可見其為 JDK 中的底層函數。

結束語

Java 程式設計優化有非常多方面能夠入手,作者将以系列的方式逐漸介紹覆寫全部領域。本文是該系列的第一篇文章。主要介紹了字元串對象操作相關、資料定義方面的優化方案、運算邏輯優化及建議,從實際代碼示範入手,對優化建議及方案進行了驗證。作者始終堅信,沒有什麼優化方案是百分百有效的,須要讀者依據實際情況進行選擇、實踐。