【第四阶段-数据处理与网络】第四章:Stream流式数据 - 数据的河流 - 指南

2.1 理解Stream

Stream就像是一条数据河流,数据会源源不断地流过来,你可以在河边"监听"并处理每一个流过的数据。

2.2 Stream的书写方法

生活场景类比

在这里插入图片描述

想象你在看直播:

  1. Future (一次性)

    • 就像网购:下单→等待→收货(只有一次)
    • 拿到商品后,交易就结束了
  2. 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
关键要点
  1. Stream vs Future

    • Future: 一次性结果(网购)
    • Stream: 持续数据流(直播)
  2. 创建Stream的方法

    • StreamController → 完全控制 ⭐
    • Stream.periodic → 定时发送
    • Stream.fromIterable → 从列表创建
    • Stream.fromFuture → 从Future转换
  3. 监听Stream的方法

    • listen() → 最灵活 ⭐
    • await for → 同步风格
    • StreamBuilder → UI组件
  4. 重要概念

    • 单订阅Stream: 只能被监听一次
    • 广播Stream: 可以被多次监听(使用.broadcast())
    • 必须关闭StreamController防止内存泄漏
  5. 记住这个公式:

    // 创建Stream
    final controller = StreamController<T>();
      // 监听Stream
      controller.stream.listen(
      (data) {
      // 处理数据
      },
      onError: (error) {
      // 处理错误
      },
      onDone: () {
      // 处理完成
      },
      );
      // 发送数据
      controller.add(data);
      // 关闭Stream
      controller.close();

posted on 2025-12-20 09:17  ljbguanli  阅读(0)  评论(0)    收藏  举报