Flutter 小技巧之 Dart 里的 List 和 Iterable 你真的搞懂了吗?

举报
GSYTech 发表于 2022/06/17 00:09:29 2022/06/17
【摘要】 今天我们介绍关于 List 和 Iterable 里有趣的知识点 ,你可能会觉得这有什么好介绍,不就是列表吗?但是其实在 Dart 里 List 和 Iterable 也是很有意思设定,比如有时候我们可以...

今天我们介绍关于 ListIterable 里有趣的知识点 ,你可能会觉得这有什么好介绍,不就是列表吗?但是其实在 Dart 里 ListIterable 也是很有意思设定,比如有时候我们可以对 List 进行 map 操作,如下代码所示,你觉得运行之后会打印出什么内容

var list = ["1", "2", "3", "4", "5"];
var map = list.map((e) {
  var result = int.parse(e) + 10;
  print("######### $result");
  return result;
});

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

答案是:什么都不会输出,因为通过 List 返回一个 Iterable 的操作(如 map \ where)的都是 Lazy 的,也就是它们只会在每次“迭代”时才会被调用。

比如调用 toList(); 或者 toString(); 等方法,就会触发上面的 map 执行,从而打印出对应的内容,那新问题来了,假如我们把下图四个方法都执行一遍,会输出几次 log ?em····答案是 3 次。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u84ezvs9-1655338513705)(http://img.cdn.guoshuyu.cn/20220615_N6/image1.png)]

其中除了 isEmpty 之外,其他的三个操作都会重新触发 map 方法的执行,那究竟是为什么呢?

其实当我们对一个 List 进行 map 等操作时,返回的是一个 Iterable 的 Lazy 对象,而每当我们需要访问里面 value 时, Iterable 都会重新执行一遍操作,因为它不会对上次操作的结果进行缓存记录

是不是有点懵?这里借用 fast_immutable_collections 作者的一个例子来介绍可能更会清晰,如下代码所示:

  • 我们对同样的数组都调用了 where 去获取一个 Iterable
  • 区别在于在 evenFilterEager 里多调用了 .toList() 操作
  • 每次 where 执行的时候都对各自的 Counter 进行 +1
  • 最后分别调用三次 length,输出 Counter 结果
var lazyCounter = 0;
var eagerCounter = 0;

var lazyOddFilter = [1, 2, 3, 4, 5, 6, 7].where((i) {
  lazyCounter++;
  return i % 2 == 0;
});

var evenFilterEager = [1, 2, 3, 4, 5, 6, 7].where((i) {
  eagerCounter++;
  return i % 2 == 0;
}).toList();

print("\n\n---------- Init ----------\n\n");

lazyOddFilter.length;
lazyOddFilter.length;
lazyOddFilter.length;

evenFilterEager.length;
evenFilterEager.length;
evenFilterEager.length;

print("\n\n---------- Lazy vs Eager ----------\n\n");

print("Lazy: $lazyCounter");
print("Eager: $eagerCounter");

print("\n\n---------- END ----------\n\n");

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

如下图所示,这个例子最终会输出 Lazy: 21 Eager: 7 这样的结果:

  • 因为 lazyCounter 每次调用 length 都是直接操作 Iterable 这个对象 ,所以每次都会重新执行一次 where ,所以 3 * 7 = 21
  • eagerCounter 对应的是 toList(); ,在调用 toList(); 时就执行了 7 次 where ,之后不管调用几次 length 都和 whereIterable 无关

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NKe4r2mK-1655338513707)(http://img.cdn.guoshuyu.cn/20220615_N6/image2.png)]

到这里你应该理解了 Iterable 的 Lazy 性质的特殊之处了吧?

那接下来看一个升级的例子,如下代码所示,我们依然是分了 eager 和 lazy 两组做对比,只是这次我们在 where 里添加了判断条件,并且做了嵌套调用,那么你觉得输出结果会是什么?


List<int> removeOdd_eager(Iterable<int> source) {
  return source.where((i) {
    print("removeOdd_eager");
    return i % 2 == 0;
  }).toList();
}

List<int> removeLessThan10_eager(Iterable<int> source) {
  return source.where((i) {
    print("removeLessThan10_eager");
    return i >= 10;
  }).toList();
}

Iterable<int> removeOdd_lazy(Iterable<int> source) {
  return source.where((i) {
    print("removeOdd_lazy");
    return i % 2 == 0;
  });
}

Iterable<int> removeLessThan10_lazy(Iterable<int> source) {
  return source.where((i) {
    print("removeLessThan10_lazy");
    return i >= 10;
  });
}

var list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];

print("\n\n---------- Init ----------\n\n");

Iterable<int> eager = removeLessThan10_eager(removeOdd_eager(list));

Iterable<int> lazy = removeLessThan10_lazy(removeOdd_lazy(list));

print("\n\n---------- Lazy ----------\n\n");

print(lazy);

print("\n\n---------- Eager ----------\n\n");

print(eager);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

如下所示,可以看到 :

  • 虽然我们先 print(lazy); 之后才输出 print(eager); ,但是先输出的还是 removeOdd_eager ,因为 Eager 相关的调用里有 .toList(); ,它在 removeOdd_eager(list) 时就执行了,所以会先完整输出 removeOdd_eager 之后再完整输出 removeLessThan10_eager ,最后在我们 print(eager); 的时候输出值
  • lazy 因为是 Iterable ,所以只有被操作时才会输出,并且输出规律是:输出两次 removeOdd_lazy 之后输出一次 removeLessThan10_lazy ,因为从数据源 1-15 上,每两次就符合 i % 2 == 0; 的条件,所以会执行 removeLessThan10_lazy ,从而变成这样的规律执行
I/flutter (23298): ---------- Init ----------
I/flutter (23298): 
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): ---------- Lazy ----------
I/flutter (23298): 
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): (10, 12, 14)
I/flutter (23298): ---------- Eager ----------
I/flutter (23298): 
I/flutter (23298): [10, 12, 14]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

是不是很觉得,这种时候 Iterable 把事情变得很复杂? 确实在这种复杂嵌套的时候, Iterable 会把逻辑变得很难维护,而官方也表示:

由于 Iterable 可能被多次迭代,因此不建议在迭代器中使用 side-effects 。

那了解 Iterable 有什么用?或者说 Iterable 可以用在什么场景?其实还是不少, 例如:

  • 分页,可以确保只有适合用户屏幕渲染时,才执行对应逻辑去加载数据
  • 数据库查询,可以实现使用数据时执行的懒加载效果,并且每次都重新迭代数据请求

举个例子,如下代码所示,感受下 naturalsFunc 这里 Iterable 配合 Stream 为什么可以正常:

Iterable<int> naturalsFunc() sync* {
  int k = 0;
  // Infinite loop!
  while (true) yield k++;
}

var naturalsIter = naturalsFunc();

print("\n\n---------- Init ----------\n\n");
print("The infinite list/iterable was created, but not evaluated.");
print("\n\n--------------------\n\n");
print("\n\n---------- takeWhile ----------\n\n");
print("It's possible to work with it,"
    "but it's necessary to add a method to "
    "stop the processing at some point");
var naturalsUpTo10 = naturalsIter.takeWhile((value) => value <= 10);
print("Naturals up to 10: $naturalsUpTo10");
print("\n\n---------- END ----------\n\n");

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-khCkQuZX-1655338513708)(http://img.cdn.guoshuyu.cn/20220615_N6/image3.png)]

那到这里你可能会问:List 不也是 Iterable 么,它和 map whereexpand 等操作返回的 Iterable 又有什么区别

如果我们看 List 本身,你会看到它是一个 abstract 对象,它作为 Iterable 的子类,其实一般情况下实现对象会是 dart vm 里的 _GrowableList,而 _GrowableList 的结构关系如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNKjXtjy-1655338513709)(http://img.cdn.guoshuyu.cn/20220615_N6/image4.png)]

List 和其他 Iterable 的不同在于在于:

  • List 是具有长度的可索引集合,因为其内部 ListIterator 是通过 _iterable.length;_iterable.elementAt 来进行实现
  • 普通 Iterable ,如 map 操作后的 MappedIterable 是按顺序访问的集合,通过 MappedIterator 来顺序访问 iterable 的元素,也不关心 length

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WEcI1wt8-1655338513709)(http://img.cdn.guoshuyu.cn/20220615_N6/image5.png)]

最后做个总结:本篇的知识点很单一,内容也很简单,就是带大家快速感受下 List 和一般 Iterable 的区别,并且通过例子理解 Iterable 懒加载的特性和应用场景,这样有利于在开发过程中 Iterable 进行选型和问题定位。

如果你还有什么疑惑,欢迎留言评论。

文章来源: carguo.blog.csdn.net,作者:恋猫de小郭,版权归原作者所有,如需转载,请联系作者。

原文链接:carguo.blog.csdn.net/article/details/125308714

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。