Flink 窗口初识

在大数据的世界里,数据源源不断地产生,形成了所谓的 “无限数据流”。想象一下,网络流量监控中,每一秒都有海量的数据包在网络中穿梭,这些数据构成了一个无始无终的流。对于这样的无限数据流,直接处理往往是不现实的,就好比让你一口气喝完大海里的水,这显然是不可能的。

Flink 窗口的出现,就像是给这无尽的数据流加上了一个个 “收纳盒”。它将无限的数据流按照一定的规则分块,把大规模的数据划分成一个个有限的小数据集,然后再对这些小数据集进行处理。这样一来,原本看似无法处理的海量数据,就变得可管理、可计算了。通过 Flink 窗口,我们可以在每个 “收纳盒”(窗口)内进行各种聚合操作,比如计算一段时间内的网站访问量、统计某段时间内的订单金额总和等。

接下来,让我们深入了解 Flink 窗口的各种类型以及它们的使用方法和场景。

Flink 窗口大揭秘

窗口类型大盘点

Flink 提供了多种类型的窗口,每种窗口都有其独特的特点和适用场景,就像不同形状和用途的容器,适用于装不同类型的物品。

  • 滚动窗口(Tumbling Window):滚动窗口是固定大小且不重叠的窗口。比如我们设置一个 5 分钟的滚动窗口,那么数据就会被按照每 5 分钟一个窗口进行划分,前 5 分钟的数据在一个窗口内处理,接着下一个 5 分钟的数据在另一个窗口内处理,以此类推。就像超市里的货物摆放,每一排货架固定存放一定时间段内上架的商品,不同排货架之间的商品不会混淆。在电商场景中,统计每小时的订单数量时,就可以使用滚动窗口,每个小时的订单数据被划分到一个单独的窗口中进行统计。
  • 滑动窗口(Sliding Window):滑动窗口也是固定大小的窗口,但它与滚动窗口的不同之处在于,滑动窗口可以有重叠部分。例如,我们设置一个大小为 10 分钟,滑动步长为 5 分钟的滑动窗口。第一个窗口包含第 0 - 10 分钟的数据,第二个窗口包含第 5 - 15 分钟的数据,这样相邻的两个窗口就有 5 分钟的数据是重叠的。想象一下地铁的检票口,每 5 分钟统计一次过去 10 分钟内的进站人数,这里就可以使用滑动窗口,通过不断滑动窗口来实时获取不同时间段内的进站人数统计信息。
  • 会话窗口(Session Window):会话窗口是根据会话来划分的,会话之间是不重叠的。当一段时间内没有数据到达时,会话就会结束。比如在用户行为分析中,一个用户在网站上的一系列操作构成一个会话,如果这个用户 10 分钟内没有任何操作,那么这个会话就结束了,下一次操作会开启新的会话。每个会话窗口内的数据包含了该用户在这个会话期间的所有操作记录,通过对会话窗口内的数据进行分析,可以了解用户在一次访问中的行为模式,比如用户浏览了哪些页面、停留了多长时间等。
  • 全局窗口(Global Window):全局窗口是一种特殊的窗口,它会将所有具有相同键的数据都分配到同一个窗口中,直到手动触发计算或者满足某些条件才会进行计算。在实际应用中,如果我们要对所有用户的行为数据进行汇总分析,不考虑时间或者其他条件的限制,就可以使用全局窗口。但由于全局窗口不会自动触发计算,所以通常需要结合自定义触发器来使用,比如设置当数据量达到一定数量时触发计算,或者每隔一段时间触发一次计算。
  • 计数窗口(Count Window):计数窗口是基于元素数量来定义窗口大小的。例如,我们设置一个计数窗口大小为 100,那么当窗口内的元素数量达到 100 时,就会触发对这个窗口内数据的计算。在网络流量监控中,如果我们要统计每接收 100 个数据包时的流量情况,就可以使用计数窗口,当收到第 100 个数据包时,对这 100 个数据包所产生的流量数据进行统计分析 。

窗口操作全解析

在 Flink 中,窗口操作主要涉及到KeyedStream和Datastream两种流的处理,不同的流有不同的窗口操作方式,它们就像是不同的工具,用于处理不同类型的数据流。

  • KeyedStream 上的窗口操作:当我们对KeyedStream进行窗口操作时,通常使用.window()方法来指定窗口分配器。例如:
    DataStream<Tuple2<String, Integer>> stream = env.fromElements(Tuple2.of("a", 1), Tuple2.of("b", 2), Tuple2.of("a", 3), Tuple2.of("b", 4));KeyedStream<Tuple2<String, Integer>, String> keyedStream = stream.keyBy(t -> t.f0);WindowedStream<Tuple2<String, Integer>, String, TimeWindow> windowedStream = keyedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(5)));

首先将DataStream通过keyBy操作转换为KeyedStream,然后使用window方法指定了一个大小为 5 秒的滚动处理时间窗口。这样,相同键(这里是Tuple2的第一个元素)的数据会被分配到同一个窗口中进行处理。

在KeyedStream的窗口操作中,还可以设置触发器(Trigger)、退出器(Evictor)以及处理迟到数据等。触发器用于决定窗口何时被触发计算,比如可以设置当窗口中的元素数量达到一定值时触发,或者当时间到达某个点时触发。退出器则用于在窗口触发计算之前或之后对窗口中的元素进行处理,比如可以根据某些条件删除窗口中的部分元素。处理迟到数据时,可以通过allowedLateness方法来指定允许数据迟到的时间,对于迟到的数据,可以通过sideOutputLateData方法将其输出到侧输出流中进行单独处理。

  • Datastream 上的窗口操作:对于Datastream,我们使用.windowAll()方法来进行窗口操作。不过需要注意的是,windowAll操作会将所有分区的流都汇集到单个的 Task 中进行处理,这可能会导致性能问题,所以在实际应用中一般不推荐使用,除非数据量较小或者有特殊需求。例如:
    DataStream<Tuple2<String, Integer>> stream = env.fromElements(Tuple2.of("a", 1), Tuple2.of("b", 2), Tuple2.of("a", 3), Tuple2.of("b", 4));AllWindowedStream<Tuple2<String, Integer>, TimeWindow> allWindowedStream = stream.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(5)));

这段代码将DataStream通过windowAll方法指定了一个 5 秒的滚动处理时间窗口,所有的数据都会被分配到这个全局的窗口中进行处理。同样,在windowAll操作中也可以设置触发器、退出器和处理迟到数据,方式与KeyedStream类似。

窗口函数大赏

窗口函数是对窗口内的数据进行计算和转换的关键工具,Flink 提供了多种窗口函数,每种函数都有其独特的作用和适用场景。

  • ReduceFunction:ReduceFunction是一种简单的聚合函数,它通过将窗口内的元素两两合并,最终得到一个聚合结果。例如,我们要计算每个窗口内订单金额的总和,可以使用ReduceFunction:
DataStream<Tuple2<String, Double>> orderStream = env.fromElements(Tuple2.of("user1", 100.0), Tuple2.of("user1", 200.0), Tuple2.of("user2", 150.0));KeyedStream<Tuple2<String, Double>, String> keyedOrderStream = orderStream.keyBy(t -> t.f0);SingleOutputStreamOperator<Tuple2<String, Double>> resultStream = keyedOrderStream.window(TumblingProcessingTimeWindows.of(Time.seconds(5))).reduce((acc, value) -> Tuple2.of(acc.f0, acc.f1 + value.f1));

在上述代码中,ReduceFunction将同一个窗口内相同用户的订单金额进行累加,最终得到每个用户在每个窗口内的订单总金额。

  • AggregateFunction:AggregateFunction是一种更灵活的聚合函数,它允许我们自定义聚合的逻辑,包括初始化累加器、将元素添加到累加器以及从累加器中获取最终结果。例如,我们要计算每个窗口内订单的平均金额,可以使用AggregateFunction:
public class AverageAggregate implements AggregateFunction<Tuple2<String, Double>, Tuple2<String, Double, Integer>, Tuple2<String, Double>> {@Overridepublic Tuple2<String, Double, Integer> createAccumulator() {return Tuple2.of("", 0.0, 0);}@Overridepublic Tuple2<String, Double, Integer> add(Tuple2<String, Double> value, Tuple2<String, Double, Integer> accumulator) {return Tuple2.of(value.f0, accumulator.f1 + value.f1, accumulator.f2 + 1);}@Overridepublic Tuple2<String, Double> getResult(Tuple2<String, Double, Integer> accumulator) {if (accumulator.f2 == 0) {return Tuple2.of(accumulator.f0, 0.0);}return Tuple2.of(accumulator.f0, accumulator.f1 / accumulator.f2);}@Overridepublic Tuple2<String, Double, Integer> merge(Tuple2<String, Double, Integer> a, Tuple2<String, Double, Integer> b) {return Tuple2.of(a.f0, a.f1 + b.f1, a.f2 + b.f2);}}使用时:DataStream<Tuple2<String, Double>> orderStream = env.fromElements(Tuple2.of("user1", 100.0), Tuple2.of("user1", 200.0), Tuple2.of("user2", 150.0));KeyedStream<Tuple2<String, Double>, String> keyedOrderStream = orderStream.keyBy(t -> t.f0);SingleOutputStreamOperator<Tuple2<String, Double>> resultStream = keyedOrderStream.window(TumblingProcessingTimeWindows.of(Time.seconds(5))).aggregate(new AverageAggregate());

AverageAggregate实现了AggregateFunction接口,通过自定义的逻辑,在窗口内计算出每个用户订单的平均金额。

  • FoldFunction:FoldFunction与ReduceFunction类似,也是用于聚合操作,但它在聚合过程中可以携带一个初始值。例如,我们要计算每个窗口内订单金额的总和,并且设置初始值为 100,可以使用FoldFunction:
DataStream<Tuple2<String, Double>> orderStream = env.fromElements(Tuple2.of("user1", 100.0), Tuple2.of("user1", 200.0), Tuple2.of("user2", 150.0));KeyedStream<Tuple2<String, Double>, String> keyedOrderStream = orderStream.keyBy(t -> t.f0);SingleOutputStreamOperator<Tuple2<String, Double>> resultStream = keyedOrderStream.window(TumblingProcessingTimeWindows.of(Time.seconds(5))).fold(Tuple2.of("", 100.0), (acc, value) -> Tuple2.of(acc.f0, acc.f1 + value.f1));

这里,FoldFunction从初始值 100 开始,将窗口内的订单金额依次累加到初始值上,最终得到每个用户在每个窗口内加上初始值后的订单总金额。

  • ProcessWindowFunction:ProcessWindowFunction是一种更高级的窗口函数,它不仅可以访问窗口内的所有元素,还可以访问窗口的元数据,如窗口的开始时间、结束时间等。例如,我们要输出每个窗口内订单金额的总和以及窗口的开始和结束时间,可以使用ProcessWindowFunction:
public class OrderSumWithWindowInfo extends ProcessWindowFunction<Tuple2<String, Double>, Tuple3<String, Double, String>, String, TimeWindow> {@Overridepublic void process(String key, Context context, Iterable<Tuple2<String, Double>> elements, Collector<Tuple3<String, Double, String>> out) throws Exception {double sum = 0;for (Tuple2<String, Double> element : elements) {sum += element.f1;}String windowInfo = "Start: " + context.window().getStart() + ", End: " + context.window().getEnd();out.collect(Tuple3.of(key, sum, windowInfo));}}使用时:DataStream<Tuple2<String, Double>> orderStream = env.fromElements(Tuple2.of("user1", 100.0), Tuple2.of("user1", 200.0), Tuple2.of("user2", 150.0));KeyedStream<Tuple2<String, Double>, String> keyedOrderStream = orderStream.keyBy(t -> t.f0);SingleOutputStreamOperator<Tuple3<String, Double, String>> resultStream = keyedOrderStream.window(TumblingProcessingTimeWindows.of(Time.seconds(5))).process(new OrderSumWithWindowInfo());

OrderSumWithWindowInfo继承自ProcessWindowFunction,在process方法中,通过遍历窗口内的元素计算出订单金额总和,并结合窗口的元数据(开始时间和结束时间)输出结果。

代码实战:Flink 窗口应用

接下来,我们通过一个具体的代码示例,来深入了解如何在实际应用中使用 Flink 窗口进行数据处理。假设我们有一个电商平台的用户行为数据,数据包含用户 ID、行为类型(如点击、购买、收藏等)、商品 ID 以及时间戳等信息。我们的目标是使用不同类型的窗口来计算一段时间内的用户行为统计,比如统计每小时内每个用户的点击次数、每 10 分钟内每个商品的购买次数等 。

首先,我们需要创建一个 Flink 的StreamExecutionEnvironment执行环境,这是 Flink 程序的入口,就像打开一扇通往数据处理世界的大门:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

然后,我们从数据源读取用户行为数据,这里假设数据源是一个 Kafka 主题,我们使用 Flink 提供的FlinkKafkaConsumer来读取数据,并将数据转换为UserBehavior对象,UserBehavior是一个自定义的 POJO 类,包含用户行为的相关字段:

Properties props = new Properties();props.setProperty("bootstrap.servers", "localhost:9092");props.setProperty("group.id", "user-behavior-analysis");FlinkKafkaConsumer<String> consumer = new FlinkKafkaConsumer<>("user_behavior_topic", new SimpleStringSchema(), props);DataStream<String> dataStream = env.addSource(consumer);DataStream<UserBehavior> userBehaviorStream = dataStream.map(new MapFunction<String, UserBehavior>() {@Overridepublic UserBehavior map(String value) throws Exception {String[] fields = value.split(",");return new UserBehavior(Long.parseLong(fields[0]),Long.parseLong(fields[1]),Integer.parseInt(fields[2]),fields[3],Long.parseLong(fields[4]));}});

滚动窗口应用

使用滚动窗口统计每小时内每个用户的点击次数。我们先按用户 ID 进行分组,然后使用大小为 1 小时的滚动处理时间窗口,再通过ReduceFunction对窗口内的点击次数进行累加:

KeyedStream<UserBehavior, Long> keyedStream = userBehaviorStream.filter(behavior -> "pv".equals(behavior.getBehavior())).keyBy(UserBehavior::getUserId);SingleOutputStreamOperator<Tuple2<Long, Integer>> tumblingWindowResult = keyedStream.window(TumblingProcessingTimeWindows.of(Time.hours(1))).reduce((acc, value) -> Tuple2.of(acc.f0, acc.f1 + 1));tumblingWindowResult.print("Tumbling Window Result: ");

滑动窗口应用

使用滑动窗口统计每 10 分钟内每个商品的购买次数,窗口大小为 10 分钟,滑动步长为 5 分钟。同样先按商品 ID 分组,然后应用滑动窗口,并通过AggregateFunction进行聚合计算:

KeyedStream<UserBehavior, Long> itemKeyedStream = userBehaviorStream.filter(behavior -> "buy".equals(behavior.getBehavior())).keyBy(UserBehavior::getItemId);public class PurchaseCountAggregate implements AggregateFunction<UserBehavior, Integer, Integer> {@Overridepublic Integer createAccumulator() {return 0;}@Overridepublic Integer add(UserBehavior value, Integer accumulator) {return accumulator + 1;}@Overridepublic Integer getResult(Integer accumulator) {return accumulator;}@Overridepublic Integer merge(Integer a, Integer b) {return a + b;}}SingleOutputStreamOperator<Tuple2<Long, Integer>> slidingWindowResult = itemKeyedStream.window(SlidingProcessingTimeWindows.of(Time.minutes(10), Time.minutes(5))).aggregate(new PurchaseCountAggregate());slidingWindowResult.print("Sliding Window Result: ");

会话窗口应用

使用会话窗口统计每个用户每次会话内的操作次数,假设会话间隙时间为 15 分钟。先按用户 ID 分组,然后应用会话窗口,通过ProcessWindowFunction获取窗口内的所有元素并计算操作次数:

KeyedStream<UserBehavior, Long> sessionKeyedStream = userBehaviorStream.keyBy(UserBehavior::getUserId);public class SessionOperationCount extends ProcessWindowFunction<UserBehavior, Tuple2<Long, Integer>, Long, TimeWindow> {@Overridepublic void process(Long key, Context context, Iterable<UserBehavior> elements, Collector<Tuple2<Long, Integer>> out) throws Exception {int count = 0;for (UserBehavior element : elements) {count++;}out.collect(Tuple2.of(key, count));}}SingleOutputStreamOperator<Tuple2<Long, Integer>> sessionWindowResult = sessionKeyedStream.window(ProcessingTimeSessionWindows.withGap(Time.minutes(15))).process(new SessionOperationCount());sessionWindowResult.print("Session Window Result: ");

最后,我们执行 Flink 作业,让数据在这些窗口操作中流动并得到处理:

env.execute("User Behavior Analysis with Flink Windows");

通过以上代码示例,我们展示了如何在实际应用中使用 Flink 的滚动窗口、滑动窗口和会话窗口进行用户行为统计分析。在实际场景中,根据不同的业务需求选择合适的窗口类型和函数,能够有效地对实时数据流进行处理和分析,为企业决策提供有力的数据支持。

避坑指南:Flink 窗口使用注意事项

在使用 Flink 窗口时,虽然它为我们处理数据流提供了强大的功能,但也容易遇到一些问题,以下是一些常见的问题及解决方案。

窗口大小设置陷阱

  • 问题描述:窗口大小设置不当可能导致数据处理不准确或性能问题。如果窗口设置过大,可能会包含过多的数据,导致内存占用过高,处理时间过长;如果窗口设置过小,可能无法满足业务需求,例如统计每小时的订单金额总和,若窗口设置为 1 分钟,就无法直接得到每小时的结果 。
  • 解决方案:在设置窗口大小时,需要充分考虑业务需求和数据量。对于数据量较大且对实时性要求不高的场景,可以适当增大窗口大小,减少窗口数量,提高处理效率;对于对实时性要求较高的场景,如实时监控网络流量,窗口大小应根据实际情况合理设置,确保能够及时反映流量变化。同时,可以通过性能测试来确定最佳的窗口大小,例如在测试环境中,使用不同大小的窗口对相同的数据集进行处理,观察内存使用、处理时间等指标,选择最适合的窗口大小。

数据乱序挑战

  • 问题描述:在实际应用中,由于网络延迟、分布式系统等原因,数据可能会乱序到达。这对于基于事件时间的窗口处理会产生很大影响,可能导致窗口计算结果不准确,例如在统计用户行为事件时,由于事件乱序,可能会将本应属于上一个窗口的事件计算到当前窗口中。
  • 解决方案:Flink 提供了水印(Watermark)机制来处理数据乱序问题。水印是一种特殊的时间戳,用于表示时间进度,它可以告诉 Flink 哪些数据已经全部到达。通过设置合适的水印生成策略,如WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(5))表示允许数据最多乱序 5 秒,Flink 可以在水印到达窗口结束时间时,触发窗口计算,从而保证窗口计算结果的准确性。此外,还可以结合allowedLateness方法来指定窗口允许数据迟到的时间,对于迟到的数据,可以通过sideOutputLateData方法将其输出到侧输出流中进行单独处理 。

状态管理难题

  • 问题描述:Flink 窗口是有状态的,窗口状态管理不当可能导致内存溢出或状态丢失等问题。特别是在处理大规模数据和长时间窗口时,窗口状态可能会占用大量内存,如果不及时清理,可能会导致内存耗尽,影响任务的正常运行。
  • 解决方案:合理使用 Flink 提供的状态清理机制,例如对于滚动窗口,可以在窗口关闭后及时清理窗口状态;对于滑动窗口,由于一个元素可能会被多个窗口共享,需要谨慎管理状态。可以使用Evictor来在窗口触发计算之前或之后对窗口中的元素进行处理,例如根据某些条件删除窗口中的部分元素,从而减少状态占用。同时,要注意选择合适的状态后端,Flink 提供了多种状态后端,如内存状态后端、文件系统状态后端等,根据数据量和性能要求选择合适的状态后端,对于大规模数据,建议使用文件系统状态后端,以避免内存溢出问题 。

触发器设置误区

  • 问题描述:触发器用于决定窗口何时被触发计算,如果触发器设置不合理,可能会导致窗口计算不及时或频繁触发,影响系统性能和数据处理的准确性。例如,将触发器设置为每收到一个元素就触发计算,这在数据量较大时会导致系统负载过高;而设置的触发条件过于苛刻,可能会导致窗口长时间不触发计算,数据积压。
  • 解决方案:根据业务需求选择合适的触发器。对于大多数时间窗口,Flink 提供的默认触发器(如ProcessingTimeTrigger和EventTimeTrigger)通常能够满足需求,但在某些特殊场景下,可能需要自定义触发器。例如,在需要根据窗口内元素数量触发计算时,可以自定义一个基于元素数量的触发器。在自定义触发器时,要仔细考虑触发条件和逻辑,确保窗口能够在合适的时机被触发计算,同时避免不必要的性能开销。

性能优化盲点

  • 问题描述:在使用 Flink 窗口处理大规模数据时,如果不进行性能优化,可能会导致任务执行效率低下,资源利用率不高。例如,窗口操作中的聚合函数如果实现不当,可能会导致计算量过大,影响处理速度;并行度设置不合理,可能会导致任务执行不均衡,部分节点负载过高,而部分节点闲置。
  • 解决方案:优化窗口操作中的聚合函数,尽量使用高效的算法和数据结构,减少计算量。对于ReduceFunction和AggregateFunction,要确保其实现简洁高效,避免不必要的计算和内存分配。合理设置并行度,根据数据量和集群资源情况,通过性能测试来确定最佳的并行度。可以使用 Flink 提供的setParallelism方法来设置算子的并行度,同时要注意避免数据倾斜问题,对于可能出现数据倾斜的场景,可以使用rebalance、shuffle等算子对数据进行重新分区,使数据均匀分布到各个并行任务中,提高资源利用率和处理效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90912.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90912.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/90912.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java排序算法之<希尔排序>

目录 1、希尔排序介绍 1.1、定义 1.2、核心思想 2、希尔排序的流程 第 1 轮&#xff1a;gap 4 第 2 轮&#xff1a;gap 2 第 3 轮&#xff1a;gap 1 3、希尔排序的实现 4、时间复杂度分析 5、希尔排序的优缺点 6、适用场景 前言 希尔排序&#xff08;Shell Sort&…

c++加载qml文件

这里展示了c加载qml文件的三种方式以及qml文件中根节点的访问准备在创建工程的初期&#xff0c;遇到了一个问题&#xff0c;cmake文件以前都是系统自动生成的&#xff0c;不需要我做过多的操作修改&#xff0c;但是&#xff0c;加载qml的程序主函数是需要用到QGuiApplication&a…

007TG洞察:GPT-5前瞻与AI时代竞争力构建:技术挑战与落地路径

最近&#xff0c;GPT-5 即将发布的消息刷爆了科技圈&#xff0c;更让人期待的是&#xff0c;GPT-6 已经悄悄启动训练了&#xff0c;OpenAI 的奥特曼表示对未来1-2年的模型充满信心&#xff0c;预测AI将进化为能够发现新知识的“AI科学家”。面对日益强大的通用AI&#xff0c;企…

Windows下编译OpenVDB

本文记录在Windows下编译OpenVDB的流程。 零、环境 操作系统Windows 11VS Code1.92.1Git2.34.1MSYS2msys2-x86_64-20240507Visual StudioVisual Studio Community 2022CMake3.22.1 一、编译 1.1 下载 git clone https://github.com/AcademySoftwareFoundation/openvdb.git …

react 内置hooks 详细使用场景,使用案例

useState场景&#xff1a;组件中管理局部状态&#xff0c;如表单值、开关、计数器等。const [count, setCount] useState(0); return <button onClick{() > setCount(count 1)}>Click {count}</button>;useEffect 场景&#xff1a;组件挂载时执行副作用&#…

从0到1学Pandas(九):Pandas 高级数据结构与操作

目录一、探秘多级索引1.1 创建多级索引1.2 多级索引操作1.3 索引转换二、探索 Panel 与 xarray2.1 Panel 数据结构2.2 xarray 库2.3 高维数据操作三、时间序列高级应用3.1 时区处理3.2 时间序列重采样与频率转换3.3 时间序列分解与预测四、数据透视与重塑高级技巧4.1 复杂透视表…

C# 图像转换实战:Bitmap 转 BitmapSource 的 2 种方法

C# 图像转换实战:Bitmap 转 BitmapSource 的 2 种方法 引言 两种转换方法的完整实现 1. 基于GDI句柄的直接转换 (ToBitmapSourceFast) 2. 基于内存流的编码转换 (ToBitmapSourceSafe) 方法对比与选型指南 避坑指南 GDI句柄泄漏问题 图像显示不完整 多线程访问图像引发异常 不同…

Spring Boot 整合 Spring MVC:自动配置与扩展实践

Spring MVC 作为 Java Web 开发的核心框架&#xff0c;在传统 SSM 项目中需要大量 XML 配置&#xff08;如 DispatcherServlet、视图解析器等&#xff09;。而 Spring Boot 通过 "自动配置" 特性&#xff0c;简化了 Spring MVC 的整合过程&#xff0c;同时保留了灵活…

print(“\033[31m红\033[32m绿\033[34m蓝\033[0m默认色“)

可以让python的终端字体有着不一样的颜色。代码&#xff1a;print("\033[31m红\033[32m绿\033[34m蓝\033[0m默认色")效果&#xff1a;

LNMP-zblog分布式部署

一、准备3台主机&#xff08;rocky8&#xff09;下载相应服务[rootnginx ~]# yum install -y nginx nfs-utils[rootphp ~]# yum install -y nfs-utils php-mysqlnd php php-fpm[rootmysql ~]# yum install -y mysql-server二、挂载php端[rootphp ~]# vim /etc/exports [rootphp…

常见代码八股

1. 利用梯度下降法&#xff0c;计算二次函数yx^2x4的最小值 def target_function(x):return x ** 2 x 4def gradient(x):return 2*x 1x_init 10 x x_init steps 100 lr 0.1 for i in range(100):x x - lr*gradient(x)print(f"最小值 f(x) {target_function(x):.4f…

【深入底层】C++开发简历4+4技能描述6

简历书写 熟悉C的封装、继承、多态&#xff0c;STL常用容器&#xff0c;熟悉C11的Lambda表达式、智能指针等&#xff0c;熟悉C20协程语法&#xff0c;具有良好的编码习惯与文档能力。 回答思路 这里是基本上就是要全会&#xff0c;考察的问题也很固定&#xff0c;stl这块可以定…

forest远程调用注意事项

1、如果在项目中&#xff0c;同时依赖了其中多个框架&#xff0c;那么按 Fastjson2 > Fastjson > Jackson > Gson 这样的优先级来判断&#xff0c;Forest 会以优先级最高的框架作为 JSON 转换器。2、Forest 支持哪几种 JSON 框架&#xff1f;A: 支持 Jackson、Gson、F…

网络资源模板--基于Android Studio 实现的新闻App

目录 一、测试环境说明 二、项目简介 三、项目演示 四、部设计详情&#xff08;部分) 登录页 首页 五、项目源码 一、测试环境说明 电脑环境 Windows 11 编写语言 JAVA 开发软件 Android Studio (2020) 开发软件只要大于等于测试版本即可(近几年官网直接下载也可…

通过Location API精准获取位置信息并优化定位精度!

&#x1f44b; 你好&#xff0c;欢迎来到我的博客&#xff01;我是【菜鸟不学编程】    我是一个正在奋斗中的职场码农&#xff0c;步入职场多年&#xff0c;正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上&#xff0c;我决定记录下自己的学习与成…

构建可扩展的状态系统:基于 ArkTS 的模块化状态管理设计与实现

摘要 在 HarmonyOS 的日常开发中&#xff0c;很多人都会遇到一个问题&#xff1a;多个页面之间的数据状态如何共享&#xff1f;尤其是在组件结构越来越复杂的场景下&#xff0c;如果还用传统方式来传值&#xff0c;不仅代码混乱&#xff0c;维护也很吃力。 为了解决这个问题&am…

重生之我在暑假学习微服务第二天《MybatisPlus-下篇》

本系列参考黑马程序员微服务课程&#xff0c;有兴趣的可以去查看相关视频&#xff0c;本系列内容采用渐进式方式讲解微服务核心概念与实践方法&#xff0c;每日更新确保知识点的连贯性。通过系统化学习路径帮助开发者掌握分布式系统构建的关键技术。读者可通过平台订阅功能获取…

系统整理Python的条件语句和常用方法

Python 的条件语句&#xff08;if 语句&#xff09;是控制程序流程的基础之一&#xff0c;结构清晰、语法简洁&#xff0c;非常适合初学者掌握。一、基本语法结构if 条件:执行代码块1 elif 条件2:执行代码块2 else:执行代码块3示例&#xff1a;score 85if score > 90:print…

记录个IAR程序下载后硬件复位不运行,必须断电复位才运行的问题

【问题测试】有个F407的跑马灯的例子&#xff0c;是MDK和IAR两个版本&#xff0c;MDK版本的例子下载并复位后可以正常看到LED闪烁&#xff0c;而IAR的例子下进去后&#xff0c;不会闪烁。使用TOOL的上位机内核寄存器监测工具测试发现&#xff0c;硬件复位后竟然还在调试状态&am…

观察者模式(Observer Pattern)和 发布-订阅模式(Publisher-Subscriber Pattern)

你对 观察者模式&#xff08;Observer Pattern&#xff09;和 发布-订阅模式&#xff08;Publisher-Subscriber Pattern&#xff09;的描述是非常准确的&#xff0c;并且阐明了它们的核心区别。为了帮助你更好地理解这两者的细微差异&#xff0c;下面是一个更详细的对比分析&am…