Dart語法篇之集合的使用與原始碼解析(二)

極客熊貓發表於2019-10-30

簡述:

我們將繼續Dart語法的第二篇集合,雖然集合在第一篇中已經介紹的差不多,但是在這篇文章中將會更加全面介紹有關Dart中的集合,因為之前只是介紹了dart:core包中的List、Set、Map,實際上在dart中還提供一個非常豐富的dart:collection包, 看過集合原始碼小夥伴都知道dart:core包中的集合實際上是委託到dart:collection包中實現的,所以下面我也會從原始碼的角度去把兩者聯絡起來。當然這裡也只會選擇幾個常用的集合作為介紹。

一、List

在dart中的List集合是具有長度的可索引物件集合,它沒有委託dart:collection包中集合實現,完全由內部自己實現。

  • 初始化

    
    main() {
          //初始化一:直接使用[]形式初始化
          List<String> colorList1 = ['red', 'yellow', 'blue', 'green'];
    
          //初始化二: var + 泛型
          var colorList2 = <String> ['red', 'yellow', 'blue', 'green'];
    
          //初始化三: 初始化定長集合
          List<String> colorList3 = List(4);//初始化指定大小為4的集合,
          colorList3.add('deepOrange');//注意: 一旦指定了集合長度,不能再呼叫add方法,否則會丟擲Cannot add to a fixed-length list。也容易理解因為一個定長的集合不能再擴充套件了。
         print(colorList3[2]);//null,此外初始化4個元素預設都是null
    
         //初始化四: 初始化空集合且是可變長的
         List<String> colorList4 = List();//相當於List<String> colorList4 =  []
         colorList4[2] = 'white';//這裡會報錯,[]=實際上就是一個運算子過載,表示修改指定index為2的元素為white,然而它長度為0所以找不到index為2元素,所以會丟擲IndexOutOfRangeException      
    
    }
    複製程式碼
  • 遍歷

    main() {
          List<String> colorList = ['red', 'yellow', 'blue', 'green'];
          //for-i遍歷
          for(var i = 0; i < colorList.length; i++) {//可以使用var或int
              print(colorList[i]);        
          }
          //forEach遍歷
          colorList.forEach((color) => print(color));//forEach的引數為Function. =>使用了箭頭函式
          //for-in遍歷
          for(var color in colorList) {
              print(color);
          }
          //while+iterator迭代器遍歷,類似Java中的iteator
          while(colorList.iterator.moveNext()) {
              print(colorList.iterator.current);
          }
    }
    複製程式碼
  • 常用的函式

    main() {
          List<String> colorList = ['red', 'yellow', 'blue', 'green'];
          colorList.add('white');//和Kotlin類似通過add新增一個新的元素
          List<String> newColorList = ['white', 'black'];
          colorList.addAll(newColorList);//addAll新增批量元素
          print(colorList[2]);//可以類似Kotlin一樣,直接使用陣列下標形式訪問元素
          print(colorList.length);//獲取集合的長度,這個Kotlin不一樣,Kotlin中使用的是size
          colorList.insert(1, 'black');//在集合指定index位置插入指定的元素
          colorList.removeAt(2);//移除集合指定的index=2的元素,第3個元素
          colorList.clear();//清除所有元素
          print(colorList.sublist(1,3));//擷取子集合
          print(colorList.getRange(1, 3));//獲取集合中某個範圍元素
          print(colorList.join('<--->'));//類似Kotlin中的joinToString方法,輸出: red<--->yellow<--->blue<--->green
          print(colorList.isEmpty);
          print(colorList.contains('green'));    
    }
    複製程式碼
  • 建構函式原始碼分析

    dart中的List有很多個構造器,一個主構造器和多個命名構造器。主構造器中有個length可選引數.

    external factory List([int length]);//主構造器,傳入length可選引數,預設為0
    
    external factory List.filled(int length, E fill, {bool growable = false});//filled命名構造器,只能宣告定長的陣列
    
    external factory List.from(Iterable elements, {bool growable = true});
    
    factory List.of(Iterable<E> elements, {bool growable = true}) =>
          List<E>.from(elements, growable: growable);//委託給List.from構造器來實現
    
    external factory List.unmodifiable(Iterable elements);  
    複製程式碼
  • exteranl關鍵字(插播一條內容)

    注意: 問題來了,可能大家看到List原始碼的時候一臉懵逼,建構函式沒有具體的實現。不知道有沒有注意 到exteranl 關鍵字。external修飾的函式具有一種實現函式宣告和實現體分離的特性。這下應該就明白了,也就是對應實現在別的地方。實際上你可以在DartSDK中的原始碼找到,以List舉例,對應的是 sdk/sdk_nnbd/lib/_internal/vm/lib/array_patch.dart, 此外對應的external函式實現會有一個 @patch註解 修飾.

@patch
class List<E> {
  //對應的是List主建構函式的實現
  @patch
  factory List([int length]) native "List_new";//實際上這裡是通過native層的c++陣列來實現,具體可參考runtime/lib/array.cc

 //對應的是List.filled建構函式的實現,fill是需要填充元素值, 預設growable是false,預設不具有擴充套件功能
  @patch
  factory List.filled(int length, E fill, {bool growable: false}) {
    var result = growable ? new _GrowableList<E>(length) : new _List<E>(length);//可以看到如果是可變長,就會建立一個_GrowableList,否則就建立內部私有的_List
    if (fill != null) {//fill填充元素值不為null,就返回length長度填充值為fill的集合
      for (int i = 0; i < length; i++) {
        result[i] = fill;
      }
    }
    return result;//否則直接返回相應長度的空集合
  }

 //對應的是List.from建構函式的實現,可將Iterable的集合加入到一個新的集合中,預設growable是true,預設具備擴充套件功能
  @patch
  factory List.from(Iterable elements, {bool growable: true}) {
    if (elements is EfficientLengthIterable<E>) {
      int length = elements.length;
      var list = growable ? new _GrowableList<E>(length) : new _List<E>(length);//如果是可變長,就會建立一個_GrowableList,否則就建立內部私有的_List
      if (length > 0) {
        //只有在必要情況下建立iterator
        int i = 0;
        for (var element in elements) {
          list[i++] = element;
        }
      }
      return list;
    }

    //如果elements是一個Iterable<E>,就不需要為每個元素做型別測試
    //因為在一般情況下,如果elements是Iterable<E>,在開始迴圈之前會用單個型別測試替換其中每個元素的型別測試。但是注意下: 等等,我發現下面這段原始碼好像有點問題,難道是我眼神不好,if和else內部執行程式碼一樣。    
    if (elements is Iterable<E>) {
      //建立一個_GrowableList
      List<E> list = new _GrowableList<E>(0);
      //遍歷elements將每個元素重新加入到_GrowableList中
      for (E e in elements) {
        list.add(e);
      }
      //如果是可變長的直接返回這個list即可
      if (growable) return list;
      //否則呼叫makeListFixedLength使得集合變為定長集合,實際上呼叫native層的c++實現
      return makeListFixedLength(list);
    } else {
      List<E> list = new _GrowableList<E>(0);
      for (E e in elements) {
        list.add(e);
      }
      if (growable) return list;
      return makeListFixedLength(list);
    }
  }

  //對應的是List.unmodifiable建構函式的實現
  @patch
  factory List.unmodifiable(Iterable elements) {
    final result = new List<E>.from(elements, growable: false);
    //這裡利用了List.from建構函式建立一個定長的集合result
    return makeFixedListUnmodifiable(result);
  }
  ...
}
複製程式碼

對應的List.from sdk的原始碼解析

//sdk/lib/_internal/vm/lib/internal_patch.dart中的makeListFixedLength
@patch
List<T> makeListFixedLength<T>(List<T> growableList)
 native "Internal_makeListFixedLength";

//runtime/lib/growable_array.cc 中的Internal_makeListFixedLength
DEFINE_NATIVE_ENTRY(Internal_makeListFixedLength, 0, 1) {
 GET_NON_NULL_NATIVE_ARGUMENT(GrowableObjectArray, array,
 arguments->NativeArgAt(0));
 return Array::MakeFixedLength(array, /* unique = */ true);//呼叫Array::MakeFixedLength C++方法變為定長集合
}

//runtime/vm/object.cc中的Array::MakeFixedLength 返回一個RawArray
RawArray* Array::MakeFixedLength(const GrowableObjectArray& growable_array, bool unique) {

 ASSERT(!growable_array.IsNull());
 Thread* thread = Thread::Current();
 Zone* zone = thread->zone();
 intptr_t used_len = growable_array.Length();
 //拿到泛型型別引數,然後準備複製它們
 const TypeArguments& type_arguments =
 TypeArguments::Handle(growable_array.GetTypeArguments());

 //如果集合為空
 if (used_len == 0) {
 //如果type_arguments是空,那麼它就是一個原生List,不帶泛型型別引數的
    if (type_arguments.IsNull() && !unique) {
     //這是一個原生List(沒有泛型型別引數)集合並且是非unique,直接返回空陣列
         return Object::empty_array().raw();
    }
 // 根據傳入List的泛型型別引數,建立一個新的空的陣列
    Heap::Space space = thread->IsMutatorThread() ? Heap::kNew : Heap::kOld;//如果是MutatorThread就開闢新的記憶體空間否則複用舊的
    Array& array = Array::Handle(zone, Array::New(0, space));//建立一個新的空陣列array
    array.SetTypeArguments(type_arguments);//設定拿到的型別引數
    return array.raw();//返回一個相同泛型引數的新陣列
 }

 //如果集合不為空,取出growable_array中的data陣列,且返回一個帶資料新的陣列array
 const Array& array = Array::Handle(zone, growable_array.data());
    ASSERT(array.IsArray());
    array.SetTypeArguments(type_arguments);//設定拿到的型別引數
    //這裡主要是回收原來的growable_array,陣列長度置為0,內部data陣列置為空陣列
    growable_array.SetLength(0);
    growable_array.SetData(Object::empty_array());
    //注意: 定長陣列實現的關鍵點來了,會呼叫Truncate方法將array截斷used_len長度
    array.Truncate(used_len);
    return array.raw();//最後返回array.raw()
}
複製程式碼

總結一下List.from的原始碼實現,首先傳入elements的Iterate<E>, 如果elements不帶泛型引數,也就是所謂的原生集合型別,並且是非unique,直接返回空陣列; 如果帶泛型引數空集合,那麼會建立新的空集合並帶上原來泛型引數返回;如果是帶泛型引數非空集合,會取出其中data陣列,來建立一個新的複製原來資料的集合並帶上原來泛型引數返回,最後需要截斷把陣列截斷成原始陣列長度。

  • 為什麼需要exteranl function

關鍵就是在於它能實現宣告和實現分離,這樣就能複用同一套對外API的宣告,然後對應多套多平臺的實現,如果對原始碼感興趣的小夥伴就會發現相同API宣告在js中也有另一套實現,這樣不管是dart for web 還是dart for vm對於上層開發而言都是一套API,對於上層開發者是透明的。

二、Set

dart:core包中的Set集合實際上是委託到dart:collection中的LinkedHashSet來實現的。集合Set和列表List的區別在於 集合中的元素是不能重複 的。所以新增重複的元素時會返回false,表示新增不成功.

  • Set初始化方式

    main() {
        Set<String> colorSet= {'red', 'yellow', 'blue', 'green'};//直接使用{}形式初始化
        var colorList = <String> {'red', 'yellow', 'blue', 'green'};
    }
    複製程式碼
  • 集合中的交、並、補集,在Kotlin並沒有直接給到計算集合交、並、補的API

    main() {
        var colorSet1 = {'red', 'yellow', 'blue', 'green'};
        var colorSet2 = {'black', 'yellow', 'blue', 'green', 'white'};
        print(colorSet1.intersection(colorSet2));//交集-->輸出: {'yellow', 'blue', 'green'}
        print(colorSet1.union(colorSet2));//並集--->輸出: {'black', 'red', 'yellow', 'blue', 'green', 'white'}
        print(colorSet1.difference(colorSet2));//補集--->輸出: {'red'}
    }
    複製程式碼
  • Set的遍歷方式(和List一樣)

      main() {
        Set<String> colorSet = {'red', 'yellow', 'blue', 'green'};
        //for-i遍歷
        for (var i = 0; i < colorSet.length; i++) {
          //可以使用var或int
          print(colorSet[i]);
        }
        //forEach遍歷
        colorSet.forEach((color) => print(color)); //forEach的引數為Function. =>使用了箭頭函式
        //for-in遍歷
        for (var color in colorSet) {
          print(color);
        }
        //while+iterator迭代器遍歷,類似Java中的iteator
        while (colorSet.iterator.moveNext()) {
          print(colorSet.iterator.current);
        }
      }
    複製程式碼
  • 建構函式原始碼分析

    factory Set() = LinkedHashSet<E>; //主構造器委託到LinkedHashSet主構造器
    factory Set.identity() = LinkedHashSet<E>.identity; //Set的命名構造器identity委託給LinkedHashSet的identity
    factory Set.from(Iterable elements) = LinkedHashSet<E>.from;//Set的命名構造器from委託給LinkedHashSet的from
    factory Set.of(Iterable<E> elements) = LinkedHashSet<E>.of;//Set的命名構造器of委託給LinkedHashSet的of
    複製程式碼
  • 對應LinkedHashSet的原始碼分析,篇幅有限感興趣可以去深入研究

    abstract class LinkedHashSet<E> implements Set<E> {
      //LinkedHashSet主構造器宣告帶了三個函式型別引數作為可選引數,同樣是通過exteranl實現宣告和實現分離,要深入可找到對應的@Patch實現
      external factory LinkedHashSet(
          {bool equals(E e1, E e2),
          int hashCode(E e),
          bool isValidKey(potentialKey)});
    
      //LinkedHashSet命名構造器from   
      factory LinkedHashSet.from(Iterable elements) {
      //內部直接建立一個LinkedHashSet物件
        LinkedHashSet<E> result = LinkedHashSet<E>();
        //並將傳入elements元素遍歷加入到LinkedHashSet中
        for (final element in elements) {
          result.add(element);
        }
        return result;
      }
    
      //LinkedHashSet命名構造器of,首先建立一個LinkedHashSet物件,通過級聯操作直接通過addAll方法將元素加入到elements
      factory LinkedHashSet.of(Iterable<E> elements) =>
          LinkedHashSet<E>()..addAll(elements);
    
      void forEach(void action(E element));
    
      Iterator<E> get iterator;
    }
    複製程式碼
  • 對應的 sdk/lib/_internal/vm/lib/collection_patch.dart 中的@Patch LinkedHashSet

    @patch
    class LinkedHashSet<E> {
      @patch
      factory LinkedHashSet(
          {bool equals(E e1, E e2),
          int hashCode(E e),
          bool isValidKey(potentialKey)}) {
        if (isValidKey == null) {
          if (hashCode == null) {
            if (equals == null) {
              return new _CompactLinkedHashSet<E>(); //可選引數都為null,預設建立_CompactLinkedHashSet
            }
            hashCode = _defaultHashCode;
          } else {
            if (identical(identityHashCode, hashCode) &&
                identical(identical, equals)) {
              return new _CompactLinkedIdentityHashSet<E>();//建立_CompactLinkedIdentityHashSet
            }
            equals ??= _defaultEquals;
          }
        } else {
          hashCode ??= _defaultHashCode;
          equals ??= _defaultEquals;
        }
        return new _CompactLinkedCustomHashSet<E>(equals, hashCode, isValidKey);//可選引數identical,預設建立_CompactLinkedCustomHashSet
      }
    
      @patch
      factory LinkedHashSet.identity() => new _CompactLinkedIdentityHashSet<E>();
    }
    複製程式碼

三、Map

dart:core 包中的 Map集合 實際上是 委託到dart:collection中的LinkedHashMap 來實現的。集合Map和Kotlin類似,key-value形式儲存,並且 Map物件的中key是不能重複的

  • Map初始化方式

    main() {
        Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};//使用{key:value}形式初始化
     var colorMap = <String, int>{'white': 0xffffffff, 'black':0xff000000};
     var colorMap = Map<String, int>();//建立一個空的Map集合
     //實際上等價於下面程式碼,後面會通過原始碼說明
     var colorMap = LinkedHashMap<String, int>();   
    }
    複製程式碼
  • Map中常用的函式

    main() {
        Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};
        print(colorMap.containsKey('green'));//false
        print(colorMap.containsValue(0xff000000));//true
        print(colorMap.keys.toList());//['white','black']
        print(colorMap.values.toList());//[0xffffffff, 0xff000000]
        colorMap['white'] = 0xfffff000;//修改指定key的元素
        colorMap.remove('black');//移除指定key的元素
    }
    複製程式碼
  • Map的遍歷方式

    main() {
        Map<String, int> colorMap = {'white': 0xffffffff, 'black':0xff000000};
        //for-each key-value
        colorMap.forEach((key, value) => print('color is $key, color value is $value'));
    }
    複製程式碼
  • Map.fromIterables將List集合轉化成Map

    main() {
        List<String> colorKeys = ['white', 'black'];
        List<int> colorValues = [0xffffffff, 0xff000000];
        Map<String, int> colorMap = Map.fromIterables(colorKeys, colorValues);
    }
    複製程式碼
  • 建構函式原始碼分析

    external factory Map(); //主構造器交由外部@Patch實現, 實際上對應的@Patch實現還是委託給LinkedHashMap
    
    factory Map.from(Map other) = LinkedHashMap<K, V>.from;//Map的命名構造器from委託給LinkedHashMap的from
    
    factory Map.of(Map<K, V> other) = LinkedHashMap<K, V>.of;//Map的命名構造器of委託給LinkedHashMap的of
    
    external factory Map.unmodifiable(Map other);//unmodifiable構造器交由外部@Patch實現
    
    factory Map.identity() = LinkedHashMap<K, V>.identity;//Map的命名構造器identity交由外部@Patch實現
    
    factory Map.fromIterable(Iterable iterable,
          {K key(element), V value(element)}) = LinkedHashMap<K, V>.fromIterable;//Map的命名構造器fromIterable委託給LinkedHashMap的fromIterable
    
    factory Map.fromIterables(Iterable<K> keys, Iterable<V> values) =
          LinkedHashMap<K, V>.fromIterables;//Map的命名構造器fromIterables委託給LinkedHashMap的fromIterables   
    複製程式碼
  • 對應LinkedHashMap建構函式原始碼分析

    abstract class LinkedHashMap<K, V> implements Map<K, V> {
      //主構造器交由外部@Patch實現
      external factory LinkedHashMap(
          {bool equals(K key1, K key2),
          int hashCode(K key),
          bool isValidKey(potentialKey)});
    
      //LinkedHashMap命名構造器identity交由外部@Patch實現
      external factory LinkedHashMap.identity();
    
      //LinkedHashMap的命名構造器from
      factory LinkedHashMap.from(Map other) {
        //建立一個新的LinkedHashMap物件
        LinkedHashMap<K, V> result = LinkedHashMap<K, V>();
        //遍歷other中的元素,並新增到新的LinkedHashMap物件
        other.forEach((k, v) {
          result[k] = v;
        });
        return result;
      }
    
      //LinkedHashMap的命名構造器of,建立一個新的LinkedHashMap物件,通過級聯操作符呼叫addAll批量新增map到新的LinkedHashMap中
      factory LinkedHashMap.of(Map<K, V> other) =>
          LinkedHashMap<K, V>()..addAll(other);
    
      //LinkedHashMap的命名構造器fromIterable,傳入的引數是iterable物件、key函式引數、value函式引數兩個可選引數
      factory LinkedHashMap.fromIterable(Iterable iterable,
          {K key(element), V value(element)}) {
        //建立新的LinkedHashMap物件,通過MapBase中的static方法_fillMapWithMappedIterable,給新的map新增元素  
        LinkedHashMap<K, V> map = LinkedHashMap<K, V>();
        MapBase._fillMapWithMappedIterable(map, iterable, key, value);
        return map;
      }
    
      //LinkedHashMap的命名構造器fromIterables
      factory LinkedHashMap.fromIterables(Iterable<K> keys, Iterable<V> values) {
      //建立新的LinkedHashMap物件,通過MapBase中的static方法_fillMapWithIterables,給新的map新增元素
        LinkedHashMap<K, V> map = LinkedHashMap<K, V>();
        MapBase._fillMapWithIterables(map, keys, values);
        return map;
      }
    }
    
    //MapBase中的_fillMapWithMappedIterable  
     static void _fillMapWithMappedIterable(
          Map map, Iterable iterable, key(element), value(element)) {
        key ??= _id;
        value ??= _id;
    
        for (var element in iterable) {//遍歷iterable,給map對應複製
          map[key(element)] = value(element);
        }
     }
    
    // MapBase中的_fillMapWithIterables
      static void _fillMapWithIterables(Map map, Iterable keys, Iterable values) {
        Iterator keyIterator = keys.iterator;//拿到keys的iterator
        Iterator valueIterator = values.iterator;//拿到values的iterator
    
        bool hasNextKey = keyIterator.moveNext();//是否有NextKey
        bool hasNextValue = valueIterator.moveNext();//是否有NextValue
    
        while (hasNextKey && hasNextValue) {//同時遍歷迭代keys,values
          map[keyIterator.current] = valueIterator.current;
          hasNextKey = keyIterator.moveNext();
          hasNextValue = valueIterator.moveNext();
        }
    
        if (hasNextKey || hasNextValue) {//最後如果其中只要有一個為true,說明key與value的長度不一致,丟擲異常
          throw ArgumentError("Iterables do not have same length.");
        }
      }
    複製程式碼
  • Map的@Patch對應實現,對應 sdk/lib/_internal/vm/lib/map_patch.dart

    @patch
    class Map<K, V> {
      @patch
      factory Map.unmodifiable(Map other) {
        return new UnmodifiableMapView<K, V>(new Map<K, V>.from(other));
      }
    
      @patch
      factory Map() => new LinkedHashMap<K, V>(); //可以看到Map的建立實際上最終還是對應建立了LinkedHashMap<K, V>
    }
    複製程式碼

四、Queue

Queue佇列顧名思義先進先出的一種資料結構,在Dart對佇列也做了一定的支援, 實際上Queue的實現是委託給ListQueue來實現。 Queue繼承於EfficientLengthIterable<E>介面,然後EfficientLengthIterable<E>介面又繼承了Iterable<E>.所以意味著Queue可以向List那樣使用豐富的操作函式。並且由Queue派生出了 DoubleLinkedQueueListQueue

  • 初始化

    import 'dart:collection';//注意: Queue位於dart:collection包中需要導包
    
    main() {
      //通過主構造器初始化
      var queueColors = Queue();
      queueColors.addFirst('red');
      queueColors.addLast('yellow');
      queueColors.add('blue');
      //通過from命名構造器初始化
      var queueColors2 = Queue.from(['red', 'yellow', 'blue']);
      //通過of命名構造器初始化
      var queueColors3 = Queue.of(['red', 'yellow', 'blue']);
    }
    複製程式碼
  • 常用的函式

    import 'dart:collection';//注意: Queue位於dart:collection包中需要導包
    main() {
     var queueColors = Queue()
     ..addFirst('red')
     ..addLast('yellow')
     ..add('blue')
     ..addAll(['white','black'])
     ..remove('black')
     ..clear();
    }
    複製程式碼
  • 遍歷

    import 'dart:collection'; //注意: Queue位於dart:collection包中需要導包
    
    main() {
      Queue<String> colorQueue = Queue.from(['red', 'yellow', 'blue', 'green']);
      //for-i遍歷
      for (var i = 0; i < colorQueue.length; i++) {
        //可以使用var或int
        print(colorQueue.elementAt(i)); //注意: 獲取佇列中的元素不用使用colorQueue[i], 因為Queue內部並沒有去實現[]運算子過載
      }
      //forEach遍歷
      colorQueue.forEach((color) => print(color)); //forEach的引數為Function. =>使用了箭頭函式
      //for-in遍歷
      for (var color in colorQueue) {
        print(color);
      }
    }
    複製程式碼
  • 建構函式原始碼分析

      factory Queue() = ListQueue<E>;//委託給ListQueue<E>主構造器
    
      factory Queue.from(Iterable elements) = ListQueue<E>.from;//委託給ListQueue<E>的命名構造器from
    
      factory Queue.of(Iterable<E> elements) = ListQueue<E>.of;//委託給ListQueue<E>的命名構造器of
    複製程式碼
  • 對應的ListQueue的原始碼分析

    class ListQueue<E> extends ListIterable<E> implements Queue<E> {
      static const int _INITIAL_CAPACITY = 8;//預設佇列的初始化容量是8
      List<E?> _table;
      int _head;
      int _tail;
      int _modificationCount = 0;
    
      ListQueue([int? initialCapacity])
          : _head = 0,
            _tail = 0,
            _table = List<E?>(_calculateCapacity(initialCapacity));//有趣的是可以看到ListQueque內部實現是一個List<E?>集合, E?還是一個泛型型別為可空型別,但是目前dart的可空型別特性還在實驗中,不過可以看到它的原始碼中已經用起來了。
    
      //計算佇列所需要容量大小
      static int _calculateCapacity(int? initialCapacity) {
        //如果initialCapacity為null或者指定的初始化容量小於預設的容量就是用預設的容量大小
        if (initialCapacity == null || initialCapacity < _INITIAL_CAPACITY) {
    
              return _INITIAL_CAPACITY;
        } else if (!_isPowerOf2(initialCapacity)) {//容量大小不是2次冪
          return _nextPowerOf2(initialCapacity);//找到大小是接近number的2次冪的數
        }
        assert(_isPowerOf2(initialCapacity));//斷言檢查
        return initialCapacity;//最終返回initialCapacity,返回的容量大小一定是2次冪的數
      }
    
      //判斷容量大小是否是2次冪
      static bool _isPowerOf2(int number) => (number & (number - 1)) == 0;
    
      //找到大小是接近number的二次冪的數
      static int _nextPowerOf2(int number) {
        assert(number > 0);
        number = (number << 1) - 1;
        for (;;) {
          int nextNumber = number & (number - 1);
          if (nextNumber == 0) return number;
          number = nextNumber;
        }
      }
    
      //ListQueue的命名建構函式from
      factory ListQueue.from(Iterable<dynamic> elements) {
        //判斷elements 是否是List<dynamic>型別
        if (elements is List<dynamic>) {
          int length = elements.length;//取出長度
          ListQueue<E> queue = ListQueue<E>(length + 1);//建立length + 1長度的ListQueue
          assert(queue._table.length > length);//必須保證新建立的queue的長度大於傳入elements的長度
          for (int i = 0; i < length; i++) {
            queue._table[i] = elements[i] as E;//然後就是給新queue中的元素賦值,注意需要強轉成泛型型別E
          }
          queue._tail = length;//最終移動佇列的tail尾部下標,因為可能存在實際長度大於實際元素長度
          return queue;
        } else {
          int capacity = _INITIAL_CAPACITY;
          if (elements is EfficientLengthIterable) {//如果是EfficientLengthIterable型別,就將elements長度作為初始容量不是就使用預設容量
            capacity = elements.length;
          }
          ListQueue<E> result = ListQueue<E>(capacity);
          for (final element in elements) {
            result.addLast(element as E);//通過addLast從佇列尾部插入
          }
          return result;//最終返回result
        }
      }
    
      //ListQueue的命名建構函式of
      factory ListQueue.of(Iterable<E> elements) =>
          ListQueue<E>()..addAll(elements); //直接建立ListQueue<E>()並通過addAll把elements加入到新的ListQueue中
      ...
    }
    複製程式碼

五、LinkedList

在dart中LinkedList比較特殊,它不是一個帶泛型集合,因為它泛型型別上界是LinkedListEntry, 內部的資料結構實現是一個雙連結串列,連結串列的結點是LinkedListEntry的子類,且內部維護了_next_previous指標。此外它並沒有實現List介面

  • 初始化

    import 'dart:collection'; //注意: LinkedList位於dart:collection包中需要導包
    main() {
      var linkedList = LinkedList<LinkedListEntryImpl<int>>();
      var prevLinkedEntry = LinkedListEntryImpl<int>(99);
      var currentLinkedEntry = LinkedListEntryImpl<int>(100);
      var nextLinkedEntry = LinkedListEntryImpl<int>(101);
      linkedList.add(currentLinkedEntry);
      currentLinkedEntry.insertBefore(prevLinkedEntry);//在當前結點前插入一個新的結點
      currentLinkedEntry.insertAfter(nextLinkedEntry);//在當前結點後插入一個新的結點
      linkedList.forEach((entry) => print('${entry.value}'));
    }
    
    //需要定義一個LinkedListEntry子類
    class LinkedListEntryImpl<T> extends LinkedListEntry<LinkedListEntryImpl<T>> {
      final T value;
    
      LinkedListEntryImpl(this.value);
    
      @override
      String toString() {
        return "value is $value";
      }
    }
    
    複製程式碼
  • 常用的函式

    currentLinkedEntry.insertBefore(prevLinkedEntry);//在當前結點前插入一個新的結點
    currentLinkedEntry.insertAfter(nextLinkedEntry);//在當前結點後插入一個新的結點
    currentLinkedEntry.previous;//獲取當前結點的前一個結點
    currentLinkedEntry.next;//獲取當前結點的後一個結點
    currentLinkedEntry.list;//獲取LinkedList
    currentLinkedEntry.unlink();//把當前結點entry從LinkedList中刪掉
    複製程式碼
  • 遍歷

     //forEach迭代
     linkedList.forEach((entry) => print('${entry.value}'));
     //for-i迭代
     for (var i = 0; i < linkedList.length; i++) {
         print('${linkedList.elementAt(i).value}');
     }
     //for-in迭代
     for (var element in linkedList) {
         print('${element.value}');
     }
    複製程式碼

六、HashMap

  • 初始化

    import 'dart:collection'; //注意: HashMap位於dart:collection包中需要導包
    main() {
      var hashMap = HashMap();//通過HashMap主構造器初始化
      hashMap['a'] = 1;
      hashMap['b'] = 2;
      hashMap['c'] = 3;
      var hashMap2 = HashMap.from(hashMap);//通過HashMap命名構造器from初始化
      var hashMap3 = HashMap.of(hashMap);//通過HashMap命名構造器of初始化
      var keys = ['a', 'b', 'c'];
      var values = [1, 2, 3]
      var hashMap4 = HashMap.fromIterables(keys, values);//通過HashMap命名構造器fromIterables初始化
    
      hashMap2.forEach((key, value) => print('key: $key  value: $value'));
    }
    複製程式碼
  • 常用的函式

    import 'dart:collection'; //注意: HashMap位於dart:collection包中需要導包
    main() {
       var hashMap = HashMap();//通過HashMap主構造器初始化
       hashMap['a'] = 1;
       hashMap['b'] = 2;
       hashMap['c'] = 3;
       print(hashMap.containsKey('a'));//false
       print(hashMap.containsValue(2));//true
       print(hashMap.keys.toList());//['a','b','c']
       print(hashMap.values.toList());//[1, 2, 3]
       hashMap['a'] = 55;//修改指定key的元素
       hashMap.remove('b');//移除指定key的元素
    }
    複製程式碼
  • 遍歷

    import 'dart:collection'; //注意: HashMap位於dart:collection包中需要導包
    main() {
       var hashMap = HashMap();//通過HashMap主構造器初始化
       hashMap['a'] = 1;
       hashMap['b'] = 2;
       hashMap['c'] = 3;
       //for-each key-value
       hashMap.forEach((key, value) => print('key is $key, value is $value'));
    }
    複製程式碼
  • 建構函式原始碼分析

    //主構造器交由外部@Patch實現
    external factory HashMap(
          {bool equals(K key1, K key2),
          int hashCode(K key),
          bool isValidKey(potentialKey)});
    
    //HashMap命名構造器identity交由外部@Patch實現
    external factory HashMap.identity();
    
    //HashMap命名構造器from
    factory HashMap.from(Map other) {
        //建立一個HashMap物件
        Map<K, V> result = HashMap<K, V>();
        //遍歷other集合並把元素賦值給新的HashMap物件
        other.forEach((k, v) {
          result[k] = v;
        });
        return result;
     }
    
    //HashMap命名構造器of,把other新增到新建立HashMap物件
    factory HashMap.of(Map<K, V> other) => HashMap<K, V>()..addAll(other);      
    
    //HashMap命名構造器fromIterable
    factory HashMap.fromIterable(Iterable iterable,
          {K key(element), V value(element)}) {
        Map<K, V> map = HashMap<K, V>();//建立一個新的HashMap物件
        MapBase._fillMapWithMappedIterable(map, iterable, key, value);//通過MapBase中的_fillMapWithMappedIterable賦值給新的HashMap物件
        return map;
    }
    
    //HashMap命名構造器fromIterables
    factory HashMap.fromIterables(Iterable<K> keys, Iterable<V> values) {
        Map<K, V> map = HashMap<K, V>();//建立一個新的HashMap物件
        MapBase._fillMapWithIterables(map, keys, values);//通過MapBase中的_fillMapWithIterables賦值給新的HashMap物件
        return map;
    }
    複製程式碼
  • HashMap對應的@Patch原始碼實現,sdk/lib/_internal/vm/lib/collection_patch.dart

    @patch
    class HashMap<K, V> {
      @patch
      factory HashMap(
          {bool equals(K key1, K key2),
          int hashCode(K key),
          bool isValidKey(potentialKey)}) {
        if (isValidKey == null) {
          if (hashCode == null) {
            if (equals == null) {
              return new _HashMap<K, V>();//建立私有的_HashMap物件
            }
            hashCode = _defaultHashCode;
          } else {
            if (identical(identityHashCode, hashCode) &&
                identical(identical, equals)) {
              return new _IdentityHashMap<K, V>();//建立私有的_IdentityHashMap物件
            }
            equals ??= _defaultEquals;
          }
        } else {
          hashCode ??= _defaultHashCode;
          equals ??= _defaultEquals;
        }
        return new _CustomHashMap<K, V>(equals, hashCode, isValidKey);//建立私有的_CustomHashMap物件
      }
    
      @patch
      factory HashMap.identity() => new _IdentityHashMap<K, V>();
    
      Set<K> _newKeySet();
    }
    複製程式碼

七、Map、HashMap、LinkedHashMap、SplayTreeMap區別

在Dart中還有一個SplayTreeMap,它的初始化、常用的函式和遍歷方式和LinkedHashMap、HashMap使用類似。但是Map、HashMap、LinkedHashMap、SplayTreeMap有什麼區別呢。

  • Map

    Map是key-value鍵值對集合。在Dart中的Map中的每個條目都可以迭代的。迭代順序取決於HashMap,LinkedHashMap或SplayTreeMap的實現。如果您使用Map建構函式建立例項,則預設情況下會建立一個LinkedHashMap

  • HashMap

    HashMap不保證插入順序。如果先插入key為A的元素,然後再插入具有key為B的另一個元素,則在遍歷Map時,有可能先獲得元素B。

  • LinkedHashMap

    LinkedHashMap保證插入順序。根據插入順序對儲存在LinkedHashMap中的資料進行排序。如果先插入key為A的元素,然後再插入具有key為B的另一個元素,則在遍歷Map時,總是先取的key為A的元素,然後再取的key為B的元素。

  • SplayTreeMap

    SplayTreeMap是一個自平衡二叉樹,它允許更快地訪問最近訪問的元素。基本操作如插入,查詢和刪除可以在O(log(n))時間複雜度中完成。它通過使經常訪問的元素靠近樹的根來執行樹的旋轉。因此,如果需要更頻繁地訪問某些元素,則使用SplayTreeMap是一個不錯的選擇。但是,如果所有元素的資料訪問頻率幾乎相同,則使用SplayTreeMap是沒有用的。

八、命名建構函式from和of的區別以及使用建議

通過上述各個集合原始碼可以看到,基本上每個集合(List、Set、LinkedHashSet、LinkedHashMap、Map、HashMap等)中都有from和of命名建構函式。可能有的人有疑問了,它們有什麼區別,各自的應用場景呢。其實答案從原始碼中就看出一點了。以List,Map中的from和of為例。

main() {
  var map = {'a': 1, 'b': 2, 'c': 3};
  var fromMap = Map.from(map); //返回型別是Map<dynamic, dynamic>
  var ofMap = Map.of(map); //返回型別是Map<String, int>

  var list = [1, 2, 3, 4];
  var fromList = List.from(list); //返回型別是List<dynamic>
  var ofList = List.of(list); //返回型別是List<int>
}
複製程式碼

從上述例子可以看出List、Map中的from函式返回對應的集合泛型型別是 List<dynamic>Map<dynamic, dynamic> 而of函式返回對應集合泛型型別實際型別是 List<int>Map<String, int>。我們都知道dynamic是一種無法確定的型別,在編譯期不檢查型別,只在執行器檢查型別,而具體型別是在編譯期檢查型別。而且從原始碼中可以看到 from函式往往會處理比較複雜邏輯比如需要重新遍歷傳入的集合然後把元素加入到新的集合中,而of函式只需要建立一個新的物件通過addAll函式批量新增傳入的集合元素。

所以這裡為了程式碼效率考慮給出建議是: 如果你傳入的原有集合元素型別是確定的,請儘量使用of函式建立新的集合,否則就可以考慮使用from函式。

總結

到這裡我們dart語法系列第二篇就結束了,相信通過這篇文章大家對dart中的集合應該有了全面的瞭解,下面我們將繼續研究dart和Flutter相關內容。

我的公眾號

這裡有最新的Dart、Flutter、Kotlin相關文章以及優秀國外文章翻譯,歡迎關注~~~

Dart語法篇之集合的使用與原始碼解析(二)

相關文章