背景
使用spark执行mapPartitionsWithIndex((index,iterator)=>{....}),在执行体中将iterator进行一次迭代后,再次根据iterator执行迭代,iterator迭代体未执行。
猜想及验证过程
猜测iterator只能执行一次迭代。
测试例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | val rdd 1 = sc.makeRDD( 1 to 10 , 2 ) val rdd 2 = rdd 1 .mapPartitionsWithIndex{(index,iterator) = >{ var result = List[String]() var sum = 0 var count = 0 while (iterator.hasNext){ sum + = iterator.next() } while (iterator.hasNext){ count + = 1 } result. :: (index + "|" + sum + "|" + count).iterator }} < br >< br > 执行结果res 0 : Array[String] = Array( 0 | 15 | 0 , 1 | 40 | 0 ) |
通过执行结果可以看出sum执行了求和运算,count没有执行统计数量运算或未正确执行统计数量运算,推测可能的原因:1. iterator能够重复执行迭代,但是count的算术运算出现问题;2.iterator只能执行一次迭代;
对原因1的验证例子:
1 2 3 4 5 6 7 8 9 10 11 | val rdd 1 = sc.makeRDD( 1 to 10 , 2 ) val rdd 2 = rdd 1 .mapPartitionsWithIndex{(index,iterator) = >{ var result = List[String]() var sum = 0 var count = 0 while (iterator.hasNext){ sum + = iterator.next() count + = 1 } result. :: (index + "|" + sum + "|" + count).iterator }} < br >< br > 执行结果res 0 : Array[String] = Array( 0 | 15 | 5 , 1 | 40 | 5 ) |
如果iterator能够重复执行迭代,但是count的统计数量计算出现问题,那么将sum和count放在同一个迭代体中,执行结果会和在两个迭代体中执行结果一致。但是执行结果却是能够正常的统计出数量,证明了推测原因1不成立。
对原因2的验证例子:
为了单纯的验证是iterator执行问题,下边的例子去掉了spark相关的函数
1 2 3 4 5 6 7 8 | val iterator = Iterator( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) var sum = 0 while (iterator.hasNext){ sum + = iterator.next } println( "sum is " + sum) val expression = if (iterator.isEmpty) "iterator is empty" else "iterator is not empty" println(expression) |
如果iterator只能执行一次迭代的话,expression的结果是【iterator is empty】,真实执行结果如下
1 2 3 4 5 | sum is 28 iterator is empty iterator : Iterator[Int] = empty iterator sum : Int = 28 expression : String = iterator is empty |
通过执行结果可以看出,expression的结果确实是【iterator is empty】,所以推测原因2成立。
结论
scala中iterator只能执行一次迭代,如果需要多次执行同一个迭代体,建议调用iterator.toList等方法,将迭代体转化为集合,再执行上述的验证例子就会正常。
扩展
1.iterator.min和iterator.max同样是通过迭代获得,所以对于同一个iterator的min和max只能获取一个。
2.java中Iterator类同scala的Iterator,只允许进行一次迭代,如果需要进行多次迭代,需要将iterator转化为集合类
3.C#中没有Iterator类,但是有IEnumerator,这个类可以通过IEnumerator.Reset方法来重置,迭代完进行重置就可以再次迭代,而对于java和scala的Iterator没有相似的方法;
补充
spark的mapPartitionsWithIndex中iterator尽量不要使用toList,原因:toList相当于将迭代数据进行了缓存,容易导致OutOfMemory的异常,iterator是流式的处理,处理完一条记录才会去读取下一条记录并且会丢弃已读的记录,无法重复使用;而iterator.toList会将所有的记录进行缓存,便于重复使用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构