天天看點

循環性能優化

循環就是讓我們的程式重複地執行某些業務。在程式設計時,需要處理大量的重複動作,采用循環結構可以降低程式書寫的長度和複雜度,可使複雜問題簡單化,提高程式的可讀性和執行速度。其中,for循環就是循環結構的一種,另外還有while循環和do-while循環語句。但是for循環是開發者最常用的開發方式。

今天要說的是最簡單的 for 循環,一個簡單的 for 循環看似沒有任何優化的意義,但實質上優化前後差距挺大的,那麼該如何優化呢?

循環性能優化

完成同樣的功能,用不同的代碼來實作,性能上可能會有比較大的差别,是以對于一些性能敏感的子產品來說,對代碼進行一定的優化還是很有必要的。今天就來說一下java代碼優化的事情,今天主要聊一下對于for(while等同理)循環的優化。

作為三大結構之一的循環,在我們編寫代碼的時候會經常用到。循環結構讓我們操作數組、集合和其他一些有規律的事物變得更加的友善,但是如果我們在實際開發當中運用不合理,可能會給程式的性能帶來很大的影響。是以我們還是需要掌握一些技巧來優化我們的代碼的。

嵌套循環

stratTime = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
  for (int j = 0; j < 10; j++) {
    
  }
}
endTime = System.nanoTime();
System.out.println("外大内小耗時:"+ (endTime - stratTime));      

應改為:

stratTime = System.nanoTime();
for (int i = 0; i <10 ; i++) {
  for (int j = 0; j < 10000000; j++) {
    
  }
}
endTime = System.nanoTime();
System.out.println("外小内大耗時:"+(endTime - stratTime));      

兩者耗時對比:

外大内小耗時:200192114
外小内大耗時:97995997      

由以上對比可知,優化後性能提升了一倍,嵌套循環應該遵循“外小内大”的原則,這就好比你複制很多個小檔案和複制幾個大檔案的差別。

提取與循環無關的表達式

stratTime = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
  i=i*a*b;
}
endTime = System.nanoTime();
System.out.println("未提取耗時:"+(endTime - stratTime));      

應改為:

stratTime = System.nanoTime();
c = a*b;
for (int i = 0; i < 10000000; i++) {
  i=i*c;
}
endTime = System.nanoTime();
System.out.println("已提取耗時:"+(endTime - stratTime));      

兩者耗時對比:

未提取耗時:45973050
已提取耗時:1955      

代碼中a+b與我們的循環無關,是以應該把它放到外面,避免重複計算,可以看出,優化後性能提升了好幾個數量級,這些是不容忽視的。

消除循環終止判斷時的方法調用

stratTime = System.nanoTime();
for (int i = 0; i < list.size(); i++) {
  
}
endTime = System.nanoTime();
System.out.println("未優化list耗時:"+(endTime - stratTime));      

應改為:

stratTime = System.nanoTime();
int size = list.size();
for (int i = 0; i < size; i++) {
  
}
endTime = System.nanoTime();
System.out.println("優化list耗時:"+(endTime - stratTime));      

兩者耗時對比:

未優化list耗時:27375
優化list耗時:2444      

list.size()每次循環都會被執行一次,這無疑會影響程式的性能,是以應該将其放到循環外面,用一個變量來代替,優化前後的對比也很明顯。

異常捕獲

stratTime = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
  try {
  } catch (Exception e) {
  }
}
endTime = System.nanoTime();
System.out.println("在内部捕獲異常耗時:"+(endTime - stratTime));      

應改為:

stratTime = System.nanoTime();
try {
  for (int i = 0; i < 10000000; i++) {
  }
} catch (Exception e) {
}
endTime = System.nanoTime();
System.out.println("在外部捕獲異常耗時:"+(endTime - stratTime));      

兩者耗時對比:

在内部捕獲異常耗時:12150142
在外部捕獲異常耗時:1955      

大家都知道,捕獲異常是很耗資源的,是以不要講try catch放到循環内部,優化後同樣有好幾個數量級的提升。

盡管傳統 ​

​for​

​​ 循環非常強大,但它有些過于複雜。Java 8 和 Java 9 中的新方法可幫助簡化疊代,甚至是簡化複雜的疊代。方法 ​

​range​

​​、​

​iterate​

​​ 和 ​

​limit​

​ 的可變部分較少,這有助于提高代碼效率。這些方法還滿足了 Java 的一個長期以來的要求,那就是局部變量必須聲明為 final,然後才能從内部類通路它。将一個可變索引變量更換為實際的 final 參數隻有很小的語義差别,但它減少了大量垃圾變量。最終您會得到更簡單、更優雅的代碼。

循環性能優化

Java 8的優化

java8推出了強大的流stream

我們開始吧。男人類有很多卡類,先初始化一些資料。

List<Man> mans = new ArrayList<>();
        mans.add(new Man("001","張三",Arrays.asList(new Card("工商銀行","9558800001"),new Card("工商銀行","9558800002"),new Card("建設銀行","6227001234"))));
        mans.add(new Man("002","李四",Arrays.asList(new Card("招商銀行","6225800002"),new Card("建設銀行","6227035248"))));
        mans.add(new Man("003","王五",Arrays.asList(new Card("建設銀行","6227056547"),new Card("中國銀行","6013832547"),new Card("民生銀行","4074058542"))));
        mans.add(new Man("004","趙六",Arrays.asList(new Card("工商銀行","9558832458"),new Card("工商銀行","9558832547"),new Card("建設銀行","6227032578"))));
        mans.add(new Man("005","孫七",Arrays.asList(new Card("中國銀行","6013825847"),new Card("農業銀行","6228836547"),new Card("招商銀行","6225014582"))));
        mans.add(new Man("006","張三",Arrays.asList(new Card("工商銀行","9558832587"),new Card("交通銀行","6222814578"),new Card("工商銀行","9558865427"))));      

查找張三的男人,for是這樣的,

public List<Man> getByName(List<Man> mans){
        List<Man> temp = new ArrayList<>();
        for(Man man : mans){
            if("張三".equals(man.getName())){
                temp.add(man);
            }
        }
        return temp;
    }      

改進後為:

public List<Man> getByName8(List<Man> mans) {
        return mans.stream().filter(m -> "張三".equals(m.getName())).collect(Collectors.toList());
    }      

這裡的集合相當與資料庫的表,而filter相當于資料庫的where。 

繼續,查找id為007的男人,id唯一,for是這樣的

public Man getById(List<Man> mans) {
        for (Man man : mans) {
            if ("007".equals(man.getId())) {
                return man;
            }
        }
        return null;
    }      

改進後為:

public Man getById8(List<Man> mans) {
        return mans.stream().filter(m -> "oo7".equals(m.getId())).findFirst().orElse(null);
    }      

繼續,擷取名字叫張三(因有同名)的所有銀行卡,這裡不讨論實際業務意義,隻講技術,哈哈,用for是這樣的。

public List<Card> getAllCardByName(List<Man> mans) {
        List<Card> cards = new ArrayList<>();
        for (Man man : mans) {
            if ("張三".equals(man.getName())) {
                cards.addAll(man.getCards());
            }
        }
        return cards;
    }      

改進後是這樣的

public List<Card> getAllCardByName8(List<Man> mans) {
        return mans.stream().filter(m -> "張三".equals(m.getName())).flatMap(m -> m.getCards().stream())
                .collect(Collectors.toList());
    }      

繼續,在3的條件上加個工商銀行條件,for

public List<Card> getSomeCardByName(List<Man> mans) {
        List<Card> cards = new ArrayList<>();
        for (Man man : mans) {
            if ("張三".equals(man.getName())) {
                for (Card card : man.getCards()) {
                    if ("工商銀行".equals(card.getName())) {
                        cards.add(card);
                    }
                }
            }
        }
        return cards;
    }      

改進後是這樣的

public List<Card> getSomeCardByName8(List<Man> mans) {
        return mans.stream().filter(m -> "張三".equals(m.getName())).flatMap(m -> m.getCards().stream())
                .filter(c -> "工商銀行".equals(c.getName())).collect(Collectors.toList());
    }      

把張三的名字修改為新張三,for,注意會改變源資料

public List<Man> changeName(List<Man> mans) {
        for (Man man : mans) {
            if ("張三".equals(man.getName())) {
                man.setName("新張三");
            }
        }
        return mans;
    }      

改進:

public List<Man> changeName8(List<Man> mans) {
        return mans.stream().peek(m -> {
            if ("張三".equals(m.getName()))
                m.setName("新張三");
        }).collect(Collectors.toList());
    }