天天看點

疊代器(C#和Java)

疊代器是一種設計模式,不管是Java還是C#,基本都是和foreach配合使用的,Java又略有不同。疊代器存在的目的是不給使用者看到原始資料,客戶需要資料的時候,由提供者提供給客戶一個疊代器,客戶通過這個疊代器來拿資料。不管内部的資料結構如何,内部的周遊如何實作,對外的疊代器的格式都是固定的。是以資料具體什麼樣的,由什麼格式來存儲,存儲在什麼位置,客戶是不知情的。疊代器的作用就是查詢,而不是修改,如果直接把原始資料丢給客戶,那麼客戶就可以随意修改原始資料了,如果不想讓客戶随意修改原始資料,那麼就要拷貝一份,這會導緻記憶體開銷加大。有些不必要的操作也沒必要,比如要擷取第一個,那麼隻需要疊代一次就可以了,但是如果選擇傳統的方式,則需要先拷貝一份資料源,然後拿到第一個,或者寫一個first()方法擷取到第一個,這都不如疊代器友善。

總結疊代器的作用:

1)資料對外不可見,使用者不可以随意修改原始資料源。

2)有些資料結構比較複雜,比如二叉樹結構的資料,使用者周遊起來麻煩,那麼則對外提供一個疊代器,内部如果對這部分資料進行周遊,使用者不再需要去關心。

3)有些場景下,疊代的過程需要複雜的操作,例如讀取磁盤下的所有檔案 ,那麼提供給使用者一個疊代器,視使用者周遊的情況來取資料,需要多少取多少,不用一次性從磁盤裡面讀出所有的資訊。

本文将會針對c#和java兩種語言的疊代器使用進行講解。

C#:

c#建立疊代器的關鍵字是yield,要求要進行疊代的類繼承IEnumerable接口,比如MyEnumerator這個類:

public class MyEnumerator : IEnumerable
    {
        private int[] nums = new int[5] { 1, 2, 3, 4, 5 };
        public IEnumerator GetEnumerator()
        {
            foreach (var v in nums)
                yield return v;
        }
    }

    public class MyEnumerator : IEnumerable
    {
        public IEnumerator GetEnumerator()
        {
            yield return 1;
            yield return 2;
            yield return 3;
            yield return 4;
            yield return 5;
        }
    }

        //使用代碼
        static void Main(string[] args)
        {
            var myEnumerator = new MyEnumerator();
            //這裡不需要調用GetEnumerator方法。
            foreach (var v in myEnumerator)
            {
                Console.WriteLine(v);
            }
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
           

上面的兩種寫法結果是完全一樣的。

yield 關鍵字在這裡的作用是向編譯器訓示它所在的方法是疊代器塊。 編譯器生成一個類來實作疊代器塊中表示的行為。 在疊代器塊中,yield 關鍵字與 return 關鍵字結合使用,向枚舉器對象提供值。

 盡管這裡以方法的形式編寫疊代器,但編譯器會将其轉換為一個實際上是狀态機的嵌套類。 隻要用戶端代碼中的 foreach 循環繼續進行,此類就會跟蹤疊代器的位置。

是以關鍵的點在于用yield告訴編譯器某一塊的代碼是疊代器,然後編譯器會在編譯時将其進行轉換成實際上是狀态機的嵌套類。

這就是為什麼傳回之後繼續疊代,能夠接着之前的位置繼續往下查找結果的原因。另外在使用的時候要使用foreach語句,而不是其他的循環語句。

在為類或結建構立疊代器時,不必實作整個 IEnumerator 接口。 當編譯器檢測到疊代器時,它将自動生成 IEnumerator 或 IEnumerator<T> 接口的 Current、MoveNext 和 Dispose 方法。

下面是官方提供的一個泛型清單疊代器的例子(https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2010/ee5kxzk0(v=vs.100)):

using System.Collections;
using System.Collections.Generic;

namespace GenericIteratorExample
{
    public class Stack<T> : IEnumerable<T>
    {
        private T[] values = new T[100];
        private int top = 0;

        public void Push(T t) { values[top++] = t; }
        public T Pop() { return values[--top]; }

        // These make Stack<T> implement IEnumerable<T> allowing
        // a stack to be used in a foreach statement.
        public IEnumerator<T> GetEnumerator()
        {
            for (int i = top - 1; i >= 0; i-- )
            {
                yield return values[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        // Iterate from top to bottom.
        public IEnumerable<T> TopToBottom
        {
            get
            {
                // Since we implement IEnumerable<T>
                // and the default iteration is top to bottom,
                // just return the object.
                return this;
            }
        }

        // Iterate from bottom to top.
        public IEnumerable<T> BottomToTop
        {
            get
            {
                for (int i = 0; i < top; i++)
                {
                    yield return values[i];
                }
            }
        }

        // A parameterized iterator that return n items from the top
        public IEnumerable<T> TopN(int n)
        {
            // in this example we return less than N if necessary 
            int j = n >= top ? 0 : top - n;

            for (int i = top; --i >= j; )
            {
                yield return values[i];
            }
        }
    }

    // This code uses a stack and the TopToBottom and BottomToTop properties 
    // to enumerate the elements of the stack.
    class Test
    {
        static void Main()
        {
            Stack<int> s = new Stack<int>();
            for (int i = 0; i < 10; i++)
            {
                s.Push(i);
            }

            // Prints: 9 8 7 6 5 4 3 2 1 0
            // Foreach legal since s implements IEnumerable<int>
            foreach (int n in s)
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Prints: 9 8 7 6 5 4 3 2 1 0
            // Foreach legal since s.TopToBottom returns IEnumerable<int>
            foreach (int n in s.TopToBottom)
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Prints: 0 1 2 3 4 5 6 7 8 9
            // Foreach legal since s.BottomToTop returns IEnumerable<int>
            foreach (int n in s.BottomToTop)
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Prints: 9 8 7 6 5 4 3
            // Foreach legal since s.TopN returns IEnumerable<int>
            foreach (int n in s.TopN(7))
            {
                System.Console.Write("{0} ", n);
            }
            System.Console.WriteLine();

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }
}
/* Output:
    9 8 7 6 5 4 3 2 1 0
    9 8 7 6 5 4 3 2 1 0
    0 1 2 3 4 5 6 7 8 9
    9 8 7 6 5 4 3
*/
           

這個例子基本包含了疊代器的正常操作,疊代器的作用就是疊代資料,不要把它想得太複雜。

Java:

//實作Iterable接口後,這個類就可以被foreach循環了
public class MyEnumerator implements Iterable<Integer>{
    private Integer[] list=new Integer[]{1,2,3,4,5};
    private Integer[] list1=new Integer[]{10,20,30,40,50};
    //當這個類被foreach循環的時候,會首先調用這個方法擷取疊代器,然後進行循環
    @Override
    public Iterator<Integer> iterator() {
        return new Itr();
    }
    //接受lamda表達式的foreach循環,有預設實作,可以不實作
    @Override
    public void forEach(Consumer<? super Integer> action) {
        Objects.requireNonNull(action);
        for (Integer t : list) {
            action.accept(t);
        }
    }
    //具體的疊代器類的實作
    // 利用的是成員内部類的特性, 可以友善的拿到上層類的資料集,然後在這個基礎上進行疊代
    class Itr implements Iterator<Integer>
    {
        private int index=0;
        @Override
        public boolean hasNext() {
            return index<list1.length;
        }
        @Override
        public Integer next() {
            return list1[index++];
        }
    }
}
           

上面是java的一個疊代器的标準實作,使用的是成員内部類去實作,成員内部類編譯器會自動幫你生成一個構造方法,将生層類作為參數傳遞給内部類,這樣内部類就可以很友善地拿到上層類的變量了。如果不了解内部類的話,可以看我的另外一篇部落格https://blog.csdn.net/dap769815768/article/details/87625620

使用上面的自定義疊代器:

public class Main {
    public static void main(String[] args) {
        System.out.println("hello world");
        MyEnumerator myEnumerator=new MyEnumerator();
        for (Integer i:myEnumerator)
        {
            System.out.println(i);
        }
        myEnumerator.forEach(x->System.out.println(x));
    }
}
           

輸出結果:

hello world
10
20
30
40
50
1
2
3
4
5
           

總結下來就是,如果類實作了Iterable,那麼這個類就可以被foreach循環,同時還可以調用forEach方法進行循環,forEach方法接口有預設實作,同時也可以寫自己的實作,這個列子裡我寫了一個自己的實作。

要想被疊代就必須添加自己的疊代器類,也就是本例中的内部類Itr,它實作Iterator接口,并實作了next和hasNext方法,這樣這個疊代器才能正常使用。

當這個類被foreach循環,其過程是,首先調用iterator方法擷取到這個類的疊代器,然後利用這個疊代器将資料周遊一遍。

如果你使用java反編譯工具,就可以看到,原本的foreach循環被改掉了,這裡我使用idea自帶的反編譯工具,檢視到的代碼變成了下面的這樣:

疊代器(C#和Java)

即foreach循環隻是一個文法糖,最終編譯的位元組檔案,使用的還是傳統的擷取疊代器的方法。

繼續閱讀