生成器在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+)

异步生成器结合 async 和 await 实现异步协程,适合 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 接口、Stream、Spliterator 等方法来实现按需生成和流式计算。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 接口、Stream、Spliterator
延迟计算 是,通过 yield 实现 是,通过 Stream 或 Iterator 实现
内存管理 保留局部状态,避免整个序列加载 通过接口和流实现延迟计算,避免存储整个序列
并行和异步生成 支持异步生成器 Stream.parallel() 或 Spliterator 支持并行
使用复杂度 简洁,生成器函数直接使用 yield 依赖接口和流,需要手动控制生成逻辑

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


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