JAVA遍歷機制的性能的比較


    本文首發於cartoon的博客
    轉載請注明出處:https://cartoonyu.github.io/cartoon-blog/post/java/java%E9%81%8D%E5%8E%86%E6%9C%BA%E5%88%B6%E7%9A%84%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83/

緣由

    近段時間在寫leetcode的Lemonade Change時候,發現了for循環與forEach循環的耗時是不一致的,在提交記錄上面差了一倍......
    平常開發絕大部分業務邏輯的實現都需要遍歷機制的幫忙,雖說也有注意到各數據結構操作的性能比較,但是忽視了遍歷機制性能的差異。原本前兩天就開始動手寫,拖延症......

正文

    現階段我所知道JAVA遍歷機制有三種

  • for循環

  • forEach循環

  • Iterator循環

    JAVA數據結構千千萬,但是大部分都是對基礎數據結構的封裝,比較HashMap依賴於Node數組,LinkedList底層是鏈表,ArrayList對數組的再封裝......扯遠了

    總結來說,JAVA的基礎數據結構,我覺得有兩種

  • 數組
  • 鏈表

    如果是加上Hash(Hash的操作與數組以及鏈表不太一致),就是三種

    因為平常開發大部分都優先選擇包裝后的數據結構,所以下面我會使用

  • ArrayList(包裝后的數組)
  • LinkedList(包裝后的鏈表)
  • HashSet(包裝后的Hash類型數組)

    這三種數據結構在遍歷機制不同的時候時間的差異

    可能有人對我為什么不對比HashMap呢,因為JAVA設計中,是先實現了Map,再實現Set。如果你有閱讀過源碼就會發現:每個Set子類的實現中,都有一個序列化后的Map對應屬性實現,而因為Hash的查找時間復雜度為O(1),得出key后查找value的時間大致是一致的,所以我不對比HashMap。

題外話

    我在閱讀《瘋狂JAVA》讀到:JAVA的設計者將Map的內部entry數組中的value設為null進而實現了Set。因為我是以源碼以及官方文檔為准,具體我不清楚正確與否,但是因為Hash中的key互不相同,Set中元素也互不相同,所以我認為這個觀點是正確的。

    為了測試的公平性,我會采取以下的限定

  • 每種數據結構的大小都設置三種量級
    • 10
    • 100
    • 1000
  • 元素都采用隨機數生成
  • 遍歷進行操作都為輸出當前元素的值

    注:時間開銷受本地環境的影響,可能測量值會出現變化,但是總體上比例是正確的

ArrayList的比較

  • 代碼

    public class TextArray {
    
        private static Random random;
    
        private static List<Integer> list1;
    
        private static List<Integer> list2;
    
        private static List<Integer> list3;
    
        public static void execute(){
            random=new Random();
            initArray();
            testForWith10Object();
            testForEachWith10Object();
            testIteratorWith10Object();
            testForWith100Object();
            testForEachWith100Object();
            testIteratorWith100Object();
            testForWith1000Object();
            testForEachWith1000Object();
            testIteratorWith1000Object();
        }
    
        private static void testForWith10Object(){
            printFor(list1);
        }
    
        private static void testForWith100Object(){
            printFor(list2);
        }
    
        private static void testForWith1000Object(){
            printFor(list3);
        }
    
        private static void testForEachWith10Object(){
            printForeach(list1);
        }
    
        private static void testForEachWith100Object(){
            printForeach(list2);
        }
    
        private static void testForEachWith1000Object(){
            printForeach(list3);
        }
    
        private static void testIteratorWith10Object() {
            printIterator(list1);
        }
    
        private static void testIteratorWith100Object() {
            printIterator(list2);
        }
    
        private static void testIteratorWith1000Object() {
            printIterator(list3);
        }
    
        private static void printFor(List<Integer> list){
            System.out.println();
            System.out.print("data:");
            long start=System.currentTimeMillis();
            for(int i=0,length=list.size();i<length;i++){
                System.out.print(list.get(i)+" ");
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("for for "+list.size()+":"+(end-start)+"ms");
        }
    
        private static void printForeach(List<Integer> list){
            System.out.println();
            System.out.print("data:");
            long start=System.currentTimeMillis();
            for(int temp:list){
                System.out.print(temp+" ");
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("foreach for "+list.size()+":"+(end-start)+"ms");
        }
    
        private static void printIterator(List<Integer> list){
            System.out.println();
            System.out.print("data:");
            Iterator<Integer> it=list.iterator();
            long start=System.currentTimeMillis();
            while(it.hasNext()){
                System.out.print(it.next()+" ");
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("iterator for "+list.size()+":"+(end-start)+"ms");
        }
    
        private static void initArray(){
            list1=new ArrayList<>();
            list2=new ArrayList<>();
            list3=new ArrayList<>();
            for(int i=0;i<10;i++){
                list1.add(random.nextInt());
            }
            for(int i=0;i<100;i++){
                list2.add(random.nextInt());
            }
            for(int i=0;i<1000;i++){
                list3.add(random.nextInt());
            }
        }
    }
    
  • 輸出(忽略對元素的輸出)

    for for 10:1ms
    foreach for 10:0ms
    iterator for 10:2ms
    
    for for 100:5ms
    foreach for 100:4ms
    iterator for 100:12ms
    
    for for 1000:33ms
    foreach for 1000:7ms
    iterator for 1000:16ms
    
    10 100 1000
    for 1ms 5ms
    forEach 0ms 4ms
    Iterator 2ms 12ms
  • 結論

        for的性能最不穩定,foreach次之,Iterator最好

  • 使用建議

    1. 在數據量不明確的情況下(可能1w,10w或其他),建議使用Iterator進行遍歷

    2. 在數據量明確且量級小的時候,優先使用foreach

    3. 需要使用索引時,使用遞增變量的開銷比for的要小

LinkedList的比較

  • 代碼

    public class TextLinkedList {
    
        private static Random random;
    
        private static List<Integer> list1;
    
        private static List<Integer> list2;
    
        private static List<Integer> list3;
    
        public static void execute(){
            random=new Random();
            initList();
            testForWith10Object();
            testForEachWith10Object();
            testIteratorWith10Object();
            testForWith100Object();
            testForEachWith100Object();
            testIteratorWith100Object();
            testForWith1000Object();
            testForEachWith1000Object();
            testIteratorWith1000Object();
        }
    
        private static void testForWith10Object() {
            printFor(list1);
        }
    
        private static void testForEachWith10Object() {
            printForeach(list1);
        }
    
        private static void testIteratorWith10Object() {
            printIterator(list1);
        }
    
        private static void testForWith100Object() {
            printFor(list2);
        }
    
        private static void testForEachWith100Object() {
            printForeach(list2);
        }
    
        private static void testIteratorWith100Object() {
            printIterator(list2);
        }
    
        private static void testForWith1000Object() {
            printFor(list3);
        }
    
        private static void testForEachWith1000Object() {
            printForeach(list3);
        }
    
        private static void testIteratorWith1000Object() {
            printIterator(list3);
        }
    
        private static void printFor(List<Integer> list){
            System.out.println();
            System.out.print("data:");
            long start=System.currentTimeMillis();
            for(int i=0,size=list.size();i<size;i++){
                System.out.print(list.get(i));
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("for for "+list.size()+":"+(end-start)+"ms");
        }
    
        private static void printForeach(List<Integer> list){
            System.out.println();
            System.out.print("data:");
            long start=System.currentTimeMillis();
            for(int temp:list){
                System.out.print(temp+" ");
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("foreach for "+list.size()+":"+(end-start)+"ms");
        }
    
        private static void printIterator(List<Integer> list){
            System.out.println();
            System.out.print("data:");
            Iterator<Integer> it=list.iterator();
            long start=System.currentTimeMillis();
            while(it.hasNext()){
                System.out.print(it.next()+" ");
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("iterator for "+list.size()+":"+(end-start)+"ms");
        }
    
    
        private static void initList() {
            list1=new LinkedList<>();
            list2=new LinkedList<>();
            list3=new LinkedList<>();
            for(int i=0;i<10;i++){
                list1.add(random.nextInt());
            }
            for(int i=0;i<100;i++){
                list2.add(random.nextInt());
            }
            for(int i=0;i<1000;i++){
                list3.add(random.nextInt());
            }
        }
    }
    
  • 輸出(忽略對元素的輸出)

    for for 10:0ms
    foreach for 10:1ms
    iterator for 10:0ms
    
    for for 100:1ms
    foreach for 100:0ms
    iterator for 100:3ms
    
    for for 1000:23ms
    foreach for 1000:25ms
    iterator for 1000:4ms
    
    10 100 1000
    for 0ms 1ms
    forEach 1ms 0ms
    Iterator 0ms 3ms
  • 結論

        foreach的性能最不穩定,for次之,Iterator最好

  • 使用建議

    1. 盡量使用Iterator進行遍歷

    2. 需要使用索引時,使用遞增變量的開銷比for的要小

HashSet的比較

    注:因Hash遍歷算法與其他類型不一致,所以取消了for循環的比較

  • 代碼

    public class TextHash {
    
        private static Random random;
    
        private static Set<Integer> set1;
    
        private static Set<Integer> set2;
    
        private static Set<Integer> set3;
    
        public static void execute(){
            random=new Random();
            initHash();
            testIteratorWith10Object();
            testForEachWith10Object();
            testIteratorWith100Object();
            testForEachWith100Object();
            testIteratorWith1000Object();
            testForEachWith1000Object();
        }
    
        private static void testIteratorWith10Object() {
            printIterator(set1);
        }
    
        private static void testForEachWith10Object() {
            printForeach(set1);
        }
    
        private static void testIteratorWith100Object() {
            printIterator(set2);
        }
    
        private static void testForEachWith100Object() {
            printForeach(set2);
        }
    
        private static void testIteratorWith1000Object() {
            printIterator(set3);
        }
    
        private static void testForEachWith1000Object() {
            printForeach(set3);
        }
    
        private static void initHash() {
            set1=new HashSet<>();
            set2=new HashSet<>();
            set3=new HashSet<>();
            for(int i=0;i<10;i++){
                set1.add(random.nextInt());
            }
            for(int i=0;i<100;i++){
                set2.add(random.nextInt());
            }
            for(int i=0;i<1000;i++){
                set3.add(random.nextInt());
            }
        }
    
        private static void printIterator(Set<Integer> data){
            System.out.println();
            System.out.print("data:");
            long start=System.currentTimeMillis();
            Iterator<Integer> it=data.iterator();
            while (it.hasNext()){
                System.out.print(it.next()+" ");
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("iterator for "+data.size()+":"+(end-start)+"ms");
        }
    
        private static void printForeach(Set<Integer> data){
            System.out.println();
            System.out.print("data:");
            long start=System.currentTimeMillis();
            for(int temp:data){
                System.out.print(temp+" ");
            }
            System.out.println();
            long end=System.currentTimeMillis();
            System.out.println("foreach for "+data.size()+":"+(end-start)+"ms");
        }
    }
    
  • 輸出(忽略對元素的輸出)

    iterator for 10:0ms
    foreach for 10:0ms
    
    iterator for 100:6ms
    foreach for 100:0ms
    
    iterator for 1000:30ms
    foreach for 1000:9ms
    
    10 100 1000
    foreach 0ms 0ms
    Iterator 0ms 6ms
  • 結論

        foreach性能遙遙領先於Iterator

  • 使用建議

        以后就選foreach了,性能好,寫起來也方便。

總結

  1. for循環性能在三者的對比中總體落於下風,而且開銷遞增幅度較大。以后即使在需要使用索引時我寧願使用遞增變量也不會使用for了。
  2. Iterator的性能在數組以及鏈表的表現都是最好的,應該是JAVA的設計者優化過了。在響應時間敏感的情況下(例如web響應),優先考慮。
  3. foreach的性能屬於兩者之間,寫法簡單,時間不敏感的情況下我會盡量選用。

    以上就是我對常見數據結構遍歷機制的一點比較,雖然只是很初步,但是從中我也學到了很多東西,希望你們也有所收獲。

    如果你喜歡本文章,可以收藏閱讀,如果你對我的其他文章感興趣,歡迎到我的博客查看。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM