【第四阶段-数据处理与网络】第四章:Stream流式数据 - 数据的河流 - 指南
文章目录
2.1 理解Stream
Stream就像是一条数据河流,数据会源源不断地流过来,你可以在河边"监听"并处理每一个流过的数据。
2.2 Stream的书写方法
生活场景类比

想象你在看直播:
Future (一次性)
- 就像网购:下单→等待→收货(只有一次)
- 拿到商品后,交易就结束了
Stream (持续性)
- 就像看直播:主播持续发送内容
- 你可以随时开始观看(监听)
- 你可以随时停止观看(取消监听)
- 主播会不断发送新内容(持续的数据流)
Stream就像是一个"直播频道" - 数据会源源不断地流过来!
Stream的四种创建方法
方法一: StreamController (最常用)
/// 使用StreamController创建和管理Stream
/// 就像: 你是主播,可以控制直播内容
import 'dart:async';
class LiveStreamDemo {
/// 创建一个String类型的StreamController
final StreamController<String> _controller = StreamController<String>();
/// 获取Stream供外部监听
Stream<String> get stream => _controller.stream;
/// 发送数据到Stream
void sendMessage(String message) {
_controller.add(message);
print(' 发送消息: $message');
}
/// 发送错误
void sendError(String error) {
_controller.addError(error);
print('❌ 发送错误: $error');
}
/// 关闭Stream
void close() {
_controller.close();
print(' 直播结束');
}
}
// 使用示例
void main() async {
final liveStream = LiveStreamDemo();
// 监听直播
liveStream.stream.listen(
(message) {
print(' 收到消息: $message');
},
onError: (error) {
print('⚠️ 收到错误: $error');
},
onDone: () {
print('✅ 直播结束了');
},
);
// 发送一些消息
liveStream.sendMessage('大家好!');
await Future.delayed(Duration(seconds: 1));
liveStream.sendMessage('欢迎来到直播间!');
await Future.delayed(Duration(seconds: 1));
liveStream.sendMessage('感谢观看!');
await Future.delayed(Duration(seconds: 1));
// 结束直播
liveStream.close();
}
// 运行结果:
// 发送消息: 大家好!
// 收到消息: 大家好!
// 发送消息: 欢迎来到直播间!
// 收到消息: 欢迎来到直播间!
// 发送消息: 感谢观看!
// 收到消息: 感谢观看!
// 直播结束
// ✅ 直播结束了
生活场景: 你是主播,完全控制直播内容的发送。
方法二: Stream.periodic (定时发送) ⏰
periodic的英文意思是周期
/// 创建定时发送数据的Stream
/// 就像: 整点报时,每隔一段时间自动播报
void periodicStreamDemo() {
print('⏰ 开始定时播报...\n');
// Stream.periodic() 参数说明:
// 参数1: Duration period - 时间间隔,每隔多久发送一次数据
// 参数2: T computation(int count) - 可选的计算函数
// - count: 从0开始的计数器,表示这是第几次触发
// - 返回值: 每次要发送的数据
// - 如果不提供此参数,Stream会发送null
final timeStream = Stream<String>.periodic(
// 第1个参数: 时间间隔 - 每2秒触发一次
Duration(seconds: 2),
// 第2个参数: 计算函数 - 根据count生成要发送的数据
(count) {
// count从0开始: 0, 1, 2, 3, 4...
// 第1次触发时 count=0
// 第2次触发时 count=1
// 第3次触发时 count=2
// 以此类推...
final now = DateTime.now();
return '第${count + 1}次播报: ${now.hour}:${now.minute}:${now.second}';
},
);
// 只监听前5次播报
timeStream.take(5).listen(
(time) {
print(' $time');
},
onDone: () {
print('\n✅ 播报结束');
},
);
}
// 运行结果:
// ⏰ 开始定时播报...
//
// 第1次播报: 14:30:15 (count=0, 等待2秒后触发)
// 第2次播报: 14:30:17 (count=1, 再等待2秒后触发)
// 第3次播报: 14:30:19 (count=2, 再等待2秒后触发)
// 第4次播报: 14:30:21 (count=3, 再等待2秒后触发)
// 第5次播报: 14:30:23 (count=4, 再等待2秒后触发)
//
// ✅ 播报结束
// 更多示例:
// 示例1: 不提供计算函数,只发送null
void example1() {
Stream.periodic(Duration(seconds: 1)).take(3).listen((data) {
print('收到: $data'); // 输出: 收到: null
});
}
// 示例2: 使用count生成递增数字
void example2() {
Stream<int>.periodic(
Duration(seconds: 1),
(count) => count * 10, // 0, 10, 20, 30...
).take(4).listen((num) {
print('数字: $num'); // 输出: 0, 10, 20, 30
});
}
// 示例3: 生成倒计时
void example3() {
Stream<int>.periodic(
Duration(seconds: 1),
(count) => 10 - count, // 从10倒数到0
).take(11).listen((num) {
print('倒计时: $num'); // 输出: 10, 9, 8, 7...0
});
}
生活场景: 像整点报时,每隔固定时间自动播报。
参数总结:
- ⏱️ Duration period: 必需参数,控制发送频率
- computation(count): 可选参数,根据count生成数据
- count从0开始自动递增
- 不提供此参数时发送null
- 可以用count做各种计算(递增、递减、倍数等)
方法三: Stream.fromIterable (从列表创建)
/// 从现有数据列表创建Stream
/// 就像: 播放歌单,依次播放每首歌
void fromIterableDemo() async {
print(' 开始播放歌单...\n');
final playlist = [
' 歌曲1: 晴天',
' 歌曲2: 七里香',
' 歌曲3: 稻香',
' 歌曲4: 青花瓷',
];
// 从列表创建Stream
final musicStream = Stream<String>.fromIterable(playlist);
// 监听并播放
await for (var song in musicStream) {
print('▶️ 正在播放: $song');
await Future.delayed(Duration(seconds: 1));
}
print('\n✅ 歌单播放完毕');
}
// 运行结果:
// 开始播放歌单...
//
// ▶️ 正在播放: 歌曲1: 晴天
// ▶️ 正在播放: 歌曲2: 七里香
// ▶️ 正在播放: 歌曲3: 稻香
// ▶️ 正在播放: 歌曲4: 青花瓷
//
// ✅ 歌单播放完毕
生活场景: 像播放歌单,按顺序播放每首歌。
方法四: Stream.fromFuture (从Future创建)
/// 将Future转换为Stream
/// 就像: 把一次性的外卖订单变成可监听的配送过程
void fromFutureDemo() async {
print(' 开始订餐...\n');
// 模拟外卖订单
Future<String> orderFood() async {
await Future.delayed(Duration(seconds: 3));
return ' 披萨已送达!';
}
// 将Future转换为Stream
final deliveryStream = Stream<String>.fromFuture(orderFood());
print('⏳ 等待配送中...');
deliveryStream.listen(
(status) {
print('✅ $status');
},
onDone: () {
print(' 订单完成!');
},
);
}
// 运行结果:
// 开始订餐...
//
// ⏳ 等待配送中...
// (等待3秒...)
// ✅ 披萨已送达!
// 订单完成!
生活场景: 把一次性的外卖订单转换成可监听的配送过程。
Stream的三种监听方法
监听方法一: listen() (最灵活)
/// 使用listen()方法监听Stream
/// 可以处理数据、错误和完成事件
void listenMethodDemo() {
final controller = StreamController<int>();
// 监听Stream
final subscription = controller.stream.listen(
// 处理数据
(data) {
print(' 收到数据: $data');
},
// 处理错误
onError: (error) {
print('❌ 发生错误: $error');
},
// 处理完成
onDone: () {
print('✅ Stream已关闭');
},
// 是否取消错误后继续监听
cancelOnError: false,
);
// 发送数据
controller.add(1);
controller.add(2);
controller.addError('出错了!');
controller.add(3);
// 暂停监听
subscription.pause();
print('⏸️ 暂停监听');
// 恢复监听
Future.delayed(Duration(seconds: 1), () {
subscription.resume();
print('▶️ 恢复监听');
controller.add(4);
controller.close();
});
}
//运行结果:
//⏸️ 暂停监听
//▶️ 恢复监听
// 收到数据: 1
// 收到数据: 2
//❌ 发生错误: 出错了!
// 收到数据: 3
// 收到数据: 4
//✅ Stream已关闭
监听方法二: await for (同步风格) ✨
/// 使用await for循环监听Stream
/// 代码看起来像同步循环,但实际是异步的
void awaitForDemo() async {
final controller = StreamController<String>();
// 在后台发送数据
Future.delayed(Duration(seconds: 1), () => controller.add('消息1'));
Future.delayed(Duration(seconds: 2), () => controller.add('消息2'));
Future.delayed(Duration(seconds: 3), () => controller.add('消息3'));
Future.delayed(Duration(seconds: 4), () => controller.close());
print(' 开始监听...\n');
// 使用await for循环
await for (var message in controller.stream) {
print(' 收到: $message');
}
print('\n✅ 监听结束');
}
// 运行结果:
// 开始监听...
//
// 收到: 消息1
// 收到: 消息2
// 收到: 消息3
//
// ✅ 监听结束
监听方法三: StreamBuilder (UI组件)
/// 在Flutter中使用StreamBuilder构建UI
/// 自动根据Stream数据更新界面
class StreamBuilderDemo extends StatelessWidget {
// 创建一个每秒递增的计数Stream
final Stream<int> counterStream = Stream<int>.periodic(
Duration(seconds: 1),
(count) => count, // 0, 1, 2, 3...
).take(10); // 只取前10个数据
Widget build(BuildContext context) {
// StreamBuilder<T> 参数说明:
// 参数1: Stream<T>? stream - 要监听的Stream数据源
// 参数2: T? initialData - 可选的初始数据,在Stream发送数据前显示
// 参数3: AsyncWidgetBuilder<T> builder - 构建UI的函数
// - BuildContext context: 构建上下文
// - AsyncSnapshot<T> snapshot: Stream的快照,包含当前状态和数据
return StreamBuilder<int>(
// 参数1: stream - 要监听的Stream
stream: counterStream,
// 参数2: initialData - 初始数据(可选)
// initialData: 0, // 如果提供,在第一个数据到来前会显示这个值
// 参数3: builder - 构建UI的函数
// 每当Stream发送新数据时,这个函数会被调用
builder: (context, snapshot) {
// AsyncSnapshot<T> snapshot 包含以下重要属性:
// - snapshot.connectionState: 连接状态
// * ConnectionState.none: 没有连接Stream
// * ConnectionState.waiting: 等待第一个数据
// * ConnectionState.active: Stream正在发送数据
// * ConnectionState.done: Stream已关闭
// - snapshot.hasData: 是否有数据
// - snapshot.data: 当前数据(类型为T)
// - snapshot.hasError: 是否有错误
// - snapshot.error: 错误信息
// 1. 检查是否有错误
if (snapshot.hasError) {
return Text('错误: ${snapshot.error}');
}
// 2. 检查是否有数据
if (!snapshot.hasData) {
// 没有数据时显示等待状态
// 这会在Stream第一次发送数据前显示
return Text('等待数据...');
}
// 3. 有数据时显示数据
return Text(
'计数: ${snapshot.data}', // snapshot.data 是 int 类型
style: TextStyle(fontSize: 24),
);
},
);
}
}
Stream的数据转换
/// Stream提供了丰富的数据转换方法
void streamTransformDemo() async {
final numbers = Stream<int>.fromIterable([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
// 1. where() - 过滤数据
print(' 只保留偶数:');
await numbers
.where((n) => n % 2 == 0)
.forEach((n) => print(' $n'));
// 2. map() - 转换数据
print('\n 每个数字乘以2:');
await Stream<int>.fromIterable([1, 2, 3, 4, 5])
.map((n) => n * 2)
.forEach((n) => print(' $n'));
// 3. take() - 只取前N个
print('\n 只取前3个:');
await Stream<int>.fromIterable([1, 2, 3, 4, 5])
.take(3)
.forEach((n) => print(' $n'));
// 4. skip() - 跳过前N个
print('\n 跳过前2个:');
await Stream<int>.fromIterable([1, 2, 3, 4, 5])
.skip(2)
.forEach((n) => print(' $n'));
// 5. distinct() - 去重
print('\n 去除重复:');
await Stream<int>.fromIterable([1, 2, 2, 3, 3, 3, 4])
.distinct()
.forEach((n) => print(' $n'));
}
// 运行结果:
// 只保留偶数:
// 2
// 4
// 6
// 8
// 10
//
// 每个数字乘以2:
// 2
// 4
// 6
// 8
// 10
//
// 只取前3个:
// 1
// 2
// 3
//
// 跳过前2个:
// 3
// 4
// 5
//
// 去除重复:
// 1
// 2
// 3
// 4
关键要点
Stream vs Future
- Future: 一次性结果(网购)
- Stream: 持续数据流(直播)
创建Stream的方法
StreamController→ 完全控制 ⭐Stream.periodic→ 定时发送Stream.fromIterable→ 从列表创建Stream.fromFuture→ 从Future转换
监听Stream的方法
listen()→ 最灵活 ⭐await for→ 同步风格StreamBuilder→ UI组件
重要概念
- 单订阅Stream: 只能被监听一次
- 广播Stream: 可以被多次监听(使用
.broadcast()) - 必须关闭StreamController防止内存泄漏
记住这个公式:
// 创建Stream final controller = StreamController<T>(); // 监听Stream controller.stream.listen( (data) { // 处理数据 }, onError: (error) { // 处理错误 }, onDone: () { // 处理完成 }, ); // 发送数据 controller.add(data); // 关闭Stream controller.close();
浙公网安备 33010602011771号