生成器在Python和Java中的实践

AI训练的代码中,包括很多教学代码,有很多Python的生成器实践(尤其李沐的课件),但是Web端写日常业务到是很少用到,就顺手查了一下资料,比较了一下各自的实现方式和原理。

Python 中的生成器

Python 生成器使用 yield 关键字和协程特性,能够在延迟计算的基础上节省内存并实现流式处理。生成器的实现基于协程(Coroutine)机制,使得函数可以暂停和恢复,从而可以在迭代过程中按需生成值。


常用写法

1. 基于 yield 的生成器

yield 关键字将函数变成生成器,每次调用 __next__() 时都会恢复函数的执行,直到下一个 yield 表达式。

代码示例

1
2
3
4
5
6
7
8
9
10
def fibonacci_generator():
a, b = 0, 1
while True:
yield a
a, b = b, a + b

# 使用生成器
fib_gen = fibonacci_generator()
for _ in range(10):
print(next(fib_gen))

使用场景

  • 按需生成:需要逐个生成数据的情况,如惰性计算和无限序列。
  • 流数据处理:处理大数据流和无限数据源,避免一次性加载全部数据。
2. 生成器表达式

生成器表达式类似列表推导式,但使用小括号 ()。它不会一次性生成所有数据,而是按需生成。

代码示例

1
2
3
gen_exp = (x * x for x in range(10))
for value in gen_exp:
print(value)

使用场景

  • 轻量级生成:在简单生成和过滤中按需生成数据。
  • 内存节省:在不需要将数据全部存储为列表时使用生成器表达式节省内存。
3. 异步生成器(Python 3.6+)

异步生成器结合 asyncawait 实现异步协程,适合 I/O 密集型任务,如数据流的异步处理。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
import asyncio

async def async_counter():
for i in range(3):
await asyncio.sleep(1) # 异步暂停
yield i

# 使用异步生成器
async def main():
async for value in async_counter():
print(value)

asyncio.run(main())

使用场景

  • I/O 密集型操作:如异步网络请求或文件读取。
  • 大规模异步并发:通过协程并发处理大量 I/O 操作,提高效率。

实现原理

  • 协程与控制流:生成器使用协程机制控制函数的执行流程,yield 会保存当前执行状态,暂停函数运行,函数可以在下次调用时继续。
  • 异步生成器与异步调度:异步生成器使用 await 异步等待操作,使得在 I/O 密集场景中可以在等待时处理其他任务,实现非阻塞操作。

Java 中的生成器

Java 没有原生的生成器支持,因此需要通过 Iterator 接口、StreamSpliterator 等方法来实现按需生成和流式计算。Java 生成器的实现依赖接口设计,通过控制生成器函数的调用和延迟计算实现生成效果。


常用写法

1. 使用 Iterator 接口

通过实现 Iterator 接口的 hasNext()next() 方法来控制数据生成。每次调用 next() 返回一个值,hasNext() 则控制生成终止条件。

代码示例

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
import java.util.Iterator;

public class FibonacciIterator implements Iterator<Integer> {
private int current = 0;
private int next = 1;

@Override
public boolean hasNext() {
return true; // 无限生成
}

@Override
public Integer next() {
int result = current;
int newNext = current + next;
current = next;
next = newNext;
return result;
}
}

// 使用生成器
public class Main {
public static void main(String[] args) {
FibonacciIterator fibonacci = new FibonacciIterator();
for (int i = 0; i < 10; i++) {
System.out.println(fibonacci.next());
}
}
}

使用场景

  • 自定义序列生成:通过手动控制生成逻辑实现自定义序列,如数学序列。
  • 无限序列:生成无限数据流(例如斐波那契数列)时有很大优势。
2. 使用 Stream API

Java 8 引入了 Stream,支持 Stream.generate()Stream.iterate() 实现惰性计算和流式生成。

代码示例

1
2
3
4
5
6
7
8
9
10
11
import java.util.stream.Stream;

public class StreamGeneratorExample {
public static void main(String[] args) {
Stream<Integer> fibonacci = Stream.iterate(new int[]{0, 1},
arr -> new int[]{arr[1], arr[0] + arr[1]})
.map(arr -> arr[0]);

fibonacci.limit(10).forEach(System.out::println);
}
}

使用场景

  • 延迟计算:流式处理大型数据集,避免一次性加载,适合处理大数据。
  • 无限序列:通过 Stream.generate() 按需生成无限数据。
3. 使用 Spliterator

Spliterator 可以自定义生成器逻辑,支持并行分割,适用于大数据的并行处理。

代码示例

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
import java.util.Spliterator;
import java.util.function.Consumer;

public class InfiniteSpliterator implements Spliterator<Integer> {
private int value = 0;

@Override
public boolean tryAdvance(Consumer<? super Integer> action) {
action.accept(value++);
return true;
}

@Override
public Spliterator<Integer> trySplit() {
return null; // 不支持拆分
}

@Override
public long estimateSize() {
return Long.MAX_VALUE;
}

@Override
public int characteristics() {
return ORDERED | NONNULL;
}
}

使用场景

  • 并行处理:通过 Spliterator 分割并行处理大数据集。
  • 自定义流式生成:在需要精确控制生成逻辑和并行化时非常有用。

实现原理

  • 接口设计:生成器通过 Iterator 接口实现 next() 方法按需生成数据,避免存储整个序列。
  • 流式处理与延迟计算Stream API 提供了延迟计算特性,只有在真正需要数据时才会调用生成函数,避免一次性加载。
  • 并行生成Spliterator 支持并行分割,可以将生成器分割成多个部分并行处理,适合大数据集的高效并行处理。

总结对比

特性 Python 生成器 Java 生成器
实现方式 协程与 yield,支持异步生成器 Iterator 接口、StreamSpliterator
延迟计算 是,通过 yield 实现 是,通过 StreamIterator 实现
内存管理 保留局部状态,避免整个序列加载 通过接口和流实现延迟计算,避免存储整个序列
并行和异步生成 支持异步生成器 Stream.parallel()Spliterator 支持并行
使用复杂度 简洁,生成器函数直接使用 yield 依赖接口和流,需要手动控制生成逻辑

Python 的生成器通过 yield 和协程提供轻量化、简便的延迟生成方式,而 Java 借助接口和流式 API 提供了灵活且适合并行计算的生成器实现。


生成器在Python和Java中的实践
https://yangtzeshore.github.io/2024/10/30/Generator/
作者
Chen Peng
发布于
2024年10月30日
许可协议