Ruby Enumerator 与 Enumerable 详解:惰性求值与迭代的艺术
Enumerable 是 Ruby 集合操作的基石,Enumerator 则带来惰性求值与手动迭代控制。本文讲清两者原理、链式调用、无限序列的写法,以及如何为自定义类实现 Enumerable。
Enumerable 是一个模块,任何类只要定义了 each 并 include 它,就免费获得 map/select/reduce 等几十种集合方法;Enumerator 是控制迭代过程的对象,支持惰性(lazy)求值——处理大文件和无限序列时靠它避免内存爆炸。
Enumerable 的本质:each 一个就够了
Ruby 的集合方法并非数组专利。map、select、reject、reduce、sort_by、group_by……这些全部来自 Enumerable 模块,而它们内部只调用一个方法:each。
这意味着一个反直觉的事实:Hash、Range、Set、甚至文件行都能用同一套集合方法,因为它们都 include 了 Enumerable。
(1..10).select(&:even?) # Range
{ a: 1, b: 2 }.map { |k, v| [k, v * 2] } # Hash
为自定义类实现 Enumerable
class Playlist
include Enumerable
def initialize(songs)
@songs = songs
end
def each(&block)
@songs.each(&block)
end
end
Playlist.new(%w[a b c]).map(&:upcase) # => ["A", "B", "C"]
只实现 each,二十多个方法自动可用。这是 Ruby 模块设计最优雅的示范。
Enumerator:把迭代握在手里
调用不带块的 each(或 map 等)会返回一个 Enumerator,迭代权交到你手上:
e = [1, 2, 3].each
e.next # => 1
e.next # => 2
e.next # => 3
e.next # => StopIteration 异常
典型用途是并行遍历两个长度不一的集合,或实现"外部迭代"——比如逐条喂给状态机。
Enumerator.new 还能从零构造序列:
fib = Enumerator.new do |y|
a, b = 0, 1
loop do
y << a
a, b = b, a + b
end
end
fib.take(10) # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
lazy:处理大数据的救命稻草
直接 File.read_lines.map {...}.select {...} 会把整个文件读进内存。加一行 .lazy 就完全不同:
File.foreach('huge.log')
.lazy
.map { |line| JSON.parse(line) rescue nil }
.compact
.select { |r| r['level'] == 'error' }
.first(100)
惰性枚举器把整条链变成"逐元素流过",需要 100 条就只处理到 100 条为止,内存占用恒定。无限序列(如上面的斐波那契)之所以能 take(10),也是因为 take 触发的是惰性语义。
链式枚举的工程实践
实际代码里常见的链式写法:collection.filter_map { ... }.group_by { ... }.transform_values { ... }。几点建议:
filter_map(Ruby 2.7+)合并 map+compact,少一次遍历;- 多层链式中间产生的临时数组在大数据量下可观,数据超过万级就考虑
lazy或数据库侧处理; each_with_object比reduce更适合构建 Hash/数组结果。
常见问题(FAQ)
Q:Enumerable 和 Enumerator 是一回事吗?
不是。Enumerable 是模块(能力的来源),Enumerator 是类(迭代过程的对象化)。前者回答"能做什么",后者回答"做到哪一步了"。
Q:什么时候必须用 lazy?
两个信号:数据量大到不想全量进内存(日志文件、导出任务),或数据源是无限/未知长度的(流式 API、实时事件)。小数组上加 lazy 反而更慢,别过度使用。
Q:each 和 map 性能差多少?
map 会构建新数组,增加与结果数量相关的内存开销;只要副作用(写库、发请求)时用 each。链式中间环节产生的数组才是优化重点,必要时用 lazy 或重构成单次遍历。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。