新聞中心
字符串操作優(yōu)化

成都創(chuàng)新互聯(lián)堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的吉縣網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
字符串對象
字符串對象或者其等價對象 (如 char 數(shù)組),在內(nèi)存中總是占據(jù)***的空間塊,因此如何高效地處理字符串,是提高系統(tǒng)整體性能的關(guān)鍵。
String 對象可以認(rèn)為是 char 數(shù)組的延伸和進(jìn)一步封裝,它主要由 3 部分組成:char 數(shù)組、偏移量和 String 的長度。char 數(shù)組表示 String 的內(nèi)容,它是 String 對象所表示字符串的超集。String 的真實內(nèi)容還需要由偏移量和長度在這個 char 數(shù)組中進(jìn)行定位和截取。
String 有 3 個基本特點:
1. 不變性;
2. 針對常量池的優(yōu)化;
3. 類的 final 定義。
不變性指的是 String 對象一旦生成,則不能再對它進(jìn)行改變。String 的這個特性可以泛化成不變 (immutable) 模式,即一個對象的狀態(tài)在對象被創(chuàng)建之后就不再發(fā)生變化。不變模式的主要作用在于當(dāng)一個對象需要被多線程共享,并且訪問頻繁時,可以省略同步和鎖等待的時 間,從而大幅提高系統(tǒng)性能。
針對常量池的優(yōu)化指的是當(dāng)兩個 String 對象擁有相同的值時,它們只引用常量池中的同一個拷貝,當(dāng)同一個字符串反復(fù)出現(xiàn)時,這個技術(shù)可以大幅度節(jié)省內(nèi)存空間。
下面代碼 str1、str2、str4 引用了相同的地址,但是 str3 卻重新開辟了一塊內(nèi)存空間,雖然 str3 單獨(dú)占用了堆空間,但是它所指向的實體和 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. 輸出結(jié)果
- 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 內(nèi)的 char 數(shù)組對象。但在這種通過偏移量來截取字符串的方法中,String 的原生內(nèi)容 value 數(shù)組被復(fù)制到新的子字符串中。設(shè)想,如果原始字符串很大,截取的字符長度卻很短,那么截取的子字符串中包含了原生字符串的所有內(nèi)容,并占據(jù)了相應(yīng)的內(nèi)存空 間,而僅僅通過偏移量和長度來決定自己的實際取值。這種算法提高了速度卻浪費(fèi)了空間。
下面代碼演示了使用 substring 方法在一個很大的 string 獨(dú)享里面截取一段很小的字符串,如果采用 string 的 substring 方法會造成內(nèi)存溢出,如果采用反復(fù)創(chuàng)建新的 string 方法可以確保正常運(yùn)行。
清單 3.substring 方法演示
- import java.util.ArrayList;
- import java.util.List;
- public class StringDemo {
- public static void main(String[] args){
- List
handler = new ArrayList (); - 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));
- }
- }
- }
輸出結(jié)果如清單 4 所示。
清單 4. 輸出結(jié)果
- 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.
(Unknown Source) - at StringDemo$ImprovedHugeStr.
(StringDemo.java:23) - at StringDemo.main(StringDemo.java:9)
ImprovedHugeStr 可以工作是因為它使用沒有內(nèi)存泄漏的 String 構(gòu)造函數(shù)重新生成了 String 對象,使得由 substring() 方法返回的、存在內(nèi)存泄漏問題的 String 對象失去所有的強(qiáng)引用,從而被垃圾回收器識別為垃圾對象進(jìn)行回收,保證了系統(tǒng)內(nèi)存的穩(wěn)定。
String 的 split 方法支持傳入正則表達(dá)式幫助處理字符串,但是簡單的字符串分割時性能較差。
對比 split 方法和 StringTokenizer 類的處理字符串性能,代碼如清單 5 所示。
#p#
切分字符串方式討論
String 的 split 方法支持傳入正則表達(dá)式幫助處理字符串,操作較為簡單,但是缺點是它所依賴的算法在對簡單的字符串分割時性能較差。清單 5 所示代碼對比了 String 的 split 方法和調(diào)用 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. 運(yùn)行輸出結(jié)果
39015
16
15
當(dāng)一個 StringTokenizer 對象生成后,通過它的 nextToken() 方法便可以得到下一個分割的字符串,通過 hasMoreToken 方法可以知道是否有更多的字符串需要處理。對比發(fā)現(xiàn) split 的耗時非常的長,采用 StringTokenizer 對象處理速度很快。我們嘗試自己實現(xiàn)字符串分割算法,使用 substring 方法和 indexOf 方法組合而成的字符串分割算法可以幫助很快切分字符串并替換內(nèi)容。
由于 String 是不可變對象,因此,在需要對字符串進(jìn)行修改操作時 (如字符串連接、替換),String 對象會生成新的對象,所以其性能相對較差。但是 JVM 會對代碼進(jìn)行徹底的優(yōu)化,將多個連接操作的字符串在編譯時合成一個單獨(dú)的長字符串。
以上實例運(yùn)行結(jié)果差異較大的原因是 split 算法對每一個字符進(jìn)行了對比,這樣當(dāng)字符串較大時,需要把整個字符串讀入內(nèi)存,逐一查找,找到符合條件的字符,這樣做較為耗時。而 StringTokenizer 類允許一個應(yīng)用程序進(jìn)入一個令牌(tokens),StringTokenizer 類的對象在內(nèi)部已經(jīng)標(biāo)識化的字符串中維持了當(dāng)前位置。一些操作使得在現(xiàn)有位置上的字符串提前得到處理。 一個令牌的值是由獲得其曾經(jīng)創(chuàng)建 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
matchList = new ArrayList (); - 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 借助于數(shù)據(jù)對象及字符查找算法完成了數(shù)據(jù)分割,適用于數(shù)據(jù)量較少場景。
#p#
合并字符串
由于 String 是不可變對象,因此,在需要對字符串進(jìn)行修改操作時 (如字符串連接、替換),String 對象會生成新的對象,所以其性能相對較差。但是 JVM 會對代碼進(jìn)行徹底的優(yōu)化,將多個連接操作的字符串在編譯時合成一個單獨(dú)的長字符串。針對超大的 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. 運(yùn)行輸出結(jié)果
375
187
0
雖然***種方法編譯器判斷 String 的加法運(yùn)行成 StringBuilder 實現(xiàn),但是編譯器沒有做出足夠聰明的判斷,每次循環(huán)都生成了新的 StringBuilder 實例從而大大降低了系統(tǒng)性能。
StringBuffer 和 StringBuilder 都實現(xiàn)了 AbstractStringBuilder 抽象類,擁有幾乎相同的對外借口,兩者的***不同在于 StringBuffer 對幾乎所有的方法都做了同步,而 StringBuilder 并沒有任何同步。由于方法同步需要消耗一定的系統(tǒng)資源,因此,StringBuilder 的效率也好于 StringBuffer。 但是,在多線程系統(tǒng)中,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();
- }
- }
- }
- }
輸出結(jié)果如清單 11 所示。
清單 11. 運(yùn)行結(jié)果
- 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 數(shù)據(jù)并沒有按照預(yù)想的方式進(jìn)行操作。StringBuilder 和 StringBuffer 的擴(kuò)充策略是將原有的容量大小翻倍,以新的容量申請內(nèi)存空間,建立新的 char 數(shù)組,然后將原數(shù)組中的內(nèi)容復(fù)制到這個新的數(shù)組中。因此,對于大對象的擴(kuò)容會涉及大量的內(nèi)存復(fù)制操作。如果能夠預(yù)先評估大小,會提高性能。
#p#
數(shù)據(jù)定義、運(yùn)算邏輯優(yōu)化
使用局部變量
調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧 (Stack) 里面,讀寫速度較快。其他變量,如靜態(tài)變量、實例變量等,都在堆 (heap) 中創(chuàng)建,讀寫速度較慢。清單 12 所示代碼演示了使用局部變量和靜態(tài)變量的操作時間對比。
清單 12. 局部變量 VS 靜態(tài)變量
- 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++;//在函數(shù)體內(nèi)定義局部變量
- }
- System.out.println(System.currentTimeMillis() - starttime);
- starttime = System.currentTimeMillis();
- for(int i=0;i<1000000;i++){
- b++;//在函數(shù)體內(nèi)定義局部變量
- }
- System.out.println(System.currentTimeMillis() - starttime);
- }
- }
運(yùn)行后輸出如清單 13 所示。
清單 13. 運(yùn)行結(jié)果
0
15
以上兩段代碼的運(yùn)行時間分別為 0ms 和 15ms。由此可見,局部變量的訪問速度遠(yuǎn)遠(yuǎn)高于類的成員變量。
位運(yùn)算代替乘除法
位運(yùn)算是所有的運(yùn)算中最為高效的。因此,可以嘗試使用位運(yùn)算代替部分算數(shù)運(yùn)算,來提高系統(tǒng)的運(yùn)行速度。最典型的就是對于整數(shù)的乘除運(yùn)算優(yōu)化。清單 14 所示代碼是一段使用算數(shù)運(yùn)算的實現(xiàn)。
清單 14. 算數(shù)運(yùn)算
- 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);
- }
- }
運(yùn)行輸出如清單 15 所示。
清單 15. 運(yùn)行結(jié)果
1000
546
1000
63
兩段代碼執(zhí)行了完全相同的功能,在每次循環(huán)中,整數(shù) 1000 乘以 2,然后除以 2。***個循環(huán)耗時 546ms,第二個循環(huán)耗時 63ms。
替換 switch
關(guān)鍵字 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);
- }
- }
運(yùn)行輸出如清單 17 所示。
清單 17. 運(yùn)行結(jié)果
172
93
使用一個連續(xù)的數(shù)組代替 switch 語句,由于對數(shù)據(jù)的隨機(jī)訪問非???,至少好于 switch 的分支判斷,從上面例子可以看到比較的效率差距近乎 1 倍,switch 方法耗時 172ms,if-else 方法耗時 93ms。
#p#
一維數(shù)組代替二維數(shù)組
JDK 很多類庫是采用數(shù)組方式實現(xiàn)的數(shù)據(jù)存儲,比如 ArrayList、Vector 等,數(shù)組的優(yōu)點是隨機(jī)訪問性能非常好。一維數(shù)組和二維數(shù)組的訪問速度不一樣,一維數(shù)組的訪問速度要優(yōu)于二維數(shù)組。在性能敏感的系統(tǒng)中要使用二維數(shù)組,盡量 將二維數(shù)組轉(zhuǎn)化為一維數(shù)組再進(jìn)行處理,以提高系統(tǒng)的響應(yīng)速度。
清單 18. 數(shù)組方式對比
- 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[j] = j;
- }
- }
- for(int i=0;i<100;i++){
- for(int j=0;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
- for(int k=0;k
- arrayDouble[i][j]=j;
- }
- }
- }
- for(int i=0;i<100;i++){
- for(int j=0;j
- for(int k=0;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
- arraySingle[j] = j;
- }
- }
- for(int i=0;i<100;i++){
- for(int j=0;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
- for(int k=0;k
- arrayDouble[i][j]=j;
- }
- }
- }
- for(int i=0;i<100;i++){
- for(int j=0;j
- for(int k=0;k
- chk = arrayDouble[i][j];
- }
- }
- }
- System.out.println(System.currentTimeMillis() - start);
- }
- }
運(yùn)行輸出如清單 19 所示。
清單 19. 運(yùn)行結(jié)果
343
624
287
390
***段代碼操作的是一維數(shù)組的賦值、取值過程,第二段代碼操作的是二維數(shù)組的賦值、取值過程??梢钥吹揭痪S數(shù)組方式比二維數(shù)組方式快接近一半時間。而對于數(shù)組內(nèi)如果可以減少賦值運(yùn)算,則可以進(jìn)一步減少運(yùn)算耗時,加快程序運(yùn)行速度。
#p#
提取表達(dá)式
大部分情況下,代碼的重復(fù)勞動由于計算機(jī)的高速運(yùn)行,并不會對性能構(gòu)成太大的威脅,但若希望將系統(tǒng)性能發(fā)揮到***,還是有很多地方可以優(yōu)化的。
清單 20. 提取表達(dá)式
- 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();
- }
- }
運(yùn)行輸出如清單 21 所示。
清單 21. 運(yùn)行結(jié)果
202
110
兩段代碼的差別是提取了重復(fù)的公式,使得這個公式的每次循環(huán)計算只執(zhí)行一次。分別耗時 202ms 和 110ms,可見,提取復(fù)雜的重復(fù)操作是相當(dāng)具有意義的。這個例子告訴我們,在循環(huán)體內(nèi),如果能夠提取到循環(huán)體外的計算公式,***提取出來,盡可能讓程序 少做重復(fù)的計算。
優(yōu)化循環(huán)
當(dāng)性能問題成為系統(tǒng)的主要矛盾時,可以嘗試優(yōu)化循環(huán),例如減少循環(huán)次數(shù),這樣也許可以加快程序運(yùn)行速度。
清單 22. 減少循環(huán)次數(shù)
- 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();
- }
- }
運(yùn)行輸出如清單 23 所示。
清單 23. 運(yùn)行結(jié)果
265
31
這個例子可以看出,通過減少循環(huán)次數(shù),耗時縮短為原來的 1/8。
布爾運(yùn)算代替位運(yùn)算
雖然位運(yùn)算的速度遠(yuǎn)遠(yuǎn)高于算術(shù)運(yùn)算,但是在條件判斷時,使用位運(yùn)算替代布爾運(yùn)算確實是非常錯誤的選擇。在條件判斷時,Java 會對布爾運(yùn)算做相當(dāng)充分的優(yōu)化。假設(shè)有表達(dá)式 a、b、c 進(jìn)行布爾運(yùn)算“a&&b&&c”,根據(jù)邏輯與的特點,只要在整個布爾表達(dá)式中有一項返回 false,整個表達(dá)式就返回 false,因此,當(dāng)表達(dá)式 a 為 false 時,該表達(dá)式將立即返回 false,而不會再去計算表達(dá)式 b 和 c。若此時,表達(dá)式 a、b、c 需要消耗大量的系統(tǒng)資源,這種處理方式可以節(jié)省這些計算資源。同理,當(dāng)計算表達(dá)式“a||b||c”時,只要 a、b 或 c,3 個表達(dá)式其中任意一個計算結(jié)果為 true 時,整體表達(dá)式立即返回 true,而不去計算剩余表達(dá)式。簡單地說,在布爾表達(dá)式的計算中,只要表達(dá)式的值可以確定,就會立即返回,而跳過剩余子表達(dá)式的計算。若使用位運(yùn)算 (按位與、按位或) 代替邏輯與和邏輯或,雖然位運(yùn)算本身沒有性能問題,但是位運(yùn)算總是要將所有的子表達(dá)式全部計算完成后,再給出最終結(jié)果。因此,從這個角度看,使用位運(yùn)算替 代布爾運(yùn)算會使系統(tǒng)進(jìn)行很多無效計算。
清單 24. 運(yùn)算方式對比
- public class OperationCompare {
- public static void booleanOperate(){
- long start = System.currentTimeMillis();
- boolean a = false;
- boolean b = true;
- int c = 0;
- //下面循環(huán)開始進(jìn)行位運(yùn)算,表達(dá)式里面的所有計算因子都會被用來計算
- 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;
- //下面循環(huán)開始進(jìn)行布爾運(yùn)算,只計算表達(dá)式 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();
- }
- }
運(yùn)行輸出如清單 25 所示。
清單 25. 運(yùn)行結(jié)果
63
0
實例顯示布爾計算大大優(yōu)于位運(yùn)算,但是,這個結(jié)果不能說明位運(yùn)算比邏輯運(yùn)算慢,因為在所有的邏輯與運(yùn)算中,都省略了表達(dá)式“”Test_123″.contains(“123″)”的計算,而所有的位運(yùn)算都沒能省略這部分系統(tǒng)開銷。
使用 arrayCopy()
數(shù)據(jù)復(fù)制是一項使用頻率很高的功能,JDK 中提供了一個高效的 API 來實現(xiàn)它。System.arraycopy() 函數(shù)是 native 函數(shù),通常 native 函數(shù)的性能要優(yōu)于普通的函數(shù),所以,僅處于性能考慮,在軟件開發(fā)中,應(yīng)盡可能調(diào)用 native 函數(shù)。ArrayList 和 Vector 大量使用了 System.arraycopy 來操作數(shù)據(jù),特別是同一數(shù)組內(nèi)元素的移動及不同數(shù)組之間元素的復(fù)制。arraycopy 的本質(zhì)是讓處理器利用一條指令處理一個數(shù)組中的多條記錄,有點像匯編語言里面的串操作指令 (LODSB、LODSW、LODSB、STOSB、STOSW、STOSB),只需指定頭指針,然后開始循環(huán)即可,即執(zhí)行一次指令,指針就后移一個位 置,操作多少數(shù)據(jù)就循環(huán)多少次。如果在應(yīng)用程序中需要進(jìn)行數(shù)組復(fù)制,應(yīng)該使用這個函數(shù),而不是自己實現(xiàn)。具體應(yīng)用如清單 26 所示。
清單 26. 復(fù)制數(shù)據(jù)例子
- 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[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[i] = i;
- }
- long start = System.currentTimeMillis();
- for(int i=0;i<1000;i++){
- for(int j=
新聞名稱:Java字符串操作、基本運(yùn)算方法等優(yōu)化策略
分享URL:http://fisionsoft.com.cn/article/ccshjed.html


咨詢
建站咨詢
