Flink学习笔记(四):Flink 四大基石之 Window 和 Time

news2025/2/26 3:54:49

文章目录

  • 1、 概述
  • 2、 Flink 的 Window 和 Time
    • 2.1、Window API
      • 2.1.1、WindowAssigner
      • 2.1.2、Trigger
      • 2.1.3、Evictor
    • 2.2、窗口类型
      • 2.2.1、Tumbling Windows
      • 2.2.2、Sliding Windows
      • 2.2.3、Session Windows
      • 2.2.4、Global Windows
    • 2.3、Time 时间语义
    • 2.4、乱序和延迟数据处理
    • 2.5、综合案例

1、 概述

Flink 基石

  • 窗口 Window

    • 流数据计算中一般对数据尽心操作之前都会先进行开窗,即基于一个什么样的窗口上做这个计算
    • Flink 提供了开箱即用的各种窗口,比如滑动窗口、滚动窗口、会话窗口以及非常灵活的自定义窗口
  • 时间 Time

    • Flink 中窗口计算,基本都是基于时间窗口设置
    • Flink 实现了 Watermark 的机制,能够支持基于事件时间的处理,能够容忍迟到、乱序的数据
  • 状态 State

    • Flink计算引擎,自身就是基于状态计算框架,默认情况下程序自己管理状态
    • 提供一致性的语义,使得用户在编程时能够更轻松、更容易地去管理状态
    • 提供一套非常简单明了的 State API,包括ValueState、ListState、MapState,BroadcastState
  • 检查点 Checkpoint

    • Flink Checkpoint 检查点:保存状态数据
    • 基于 Chandy-Lamport 算法实现了一个分布式的一致性的快照,从而提供了一致性的语义
    • 进行 Checkpoint 后,可以设置自动进行故障恢复
    • 保存点 Savepoint,人工进行 Checkpoint 操作,进行程序恢复执行

2、 Flink 的 Window 和 Time

2.1、Window API

在 Flink 流计算中,提供 Window 窗口 API 分为 2 种:

  • 针对 KeyedStream 窗口 API
    Window 先对数据流 DataStream 进行分组 keyBy ,再设置窗口 Window,最后进行聚合 apply 操作。
    • 第一步、数据流 DataStream 调用 keyBy 函数分组,获取 KeyedStream
    • 第二步、KeyedStream.window 设置窗口
    • 第三步、聚合操作,对窗口中数据进行聚合统计,函数:reduce、aggregate、apply() 等。
stream.keyBy(...)          <-  keyed versus non-keyed windows
       .window(...)         <-  required: "assigner"
      [.trigger(...)]       <-  optional: "trigger" (else default trigger)
      [.evictor(...)]       <-  optional: "evictor" (else no evictor)
      [.allowedLateness()]  <-  optional, else zero
       .reduce/fold/apply() <-  required: "function"
  • 针对 KeyedStream 窗口 API
    • 直接调用窗口函数:windowAll,然后再对窗口所有数据进行处理,未进行分组;
    • 聚合操作,对窗口中数据进行聚合统计,函数:reduce、aggregate、apply() 等。
stream.windowAll(...)      <-  required: "assigner"
      [.trigger(...)]       <-  optional: "trigger" (else default trigger)
      [.evictor(...)]       <-  optional: "evictor" (else no evictor)
      [.allowedLateness()]  <-  optional, else zero
       .reduce/fold/apply() <-  required: "function"

方括号 [ ] 内的命令是可选的,这表明 Flink 允许根据需求自定义 window 逻辑。使用 keyBy 的流,应该使用 window 方法,未使用 keyBy 的流,应该调用 windowAll 方法

2.1.1、WindowAssigner

window/windowAll 方法接收的输入是一个 WindowAssigner, WindowAssigner 负责将每条输入的数据分发到正确的 window 中。如果需要自己定制数据分发策略,则可以实现一个 class,继承自 WindowAssigner。

2.1.2、Trigger

trigger 用来判断一个窗口是否需要被触发,每个 WindowAssigner 都自带一个默认的 trigger,如果默认的 trigger 不能满足你的需求,则可以自定义一个类,继承自Trigger 即可。

  • onElement()
  • onEventTime()
  • onProcessingTime()

此抽象类的这三个方法会返回一个 TriggerResult, TriggerResult 有如下几种可能的选择:

  • CONTINUE 不做任何事情
  • FIRE 触发 window
  • PURGE 清空整个 window 的元素并销毁窗口
  • FIRE_AND_PURGE 触发窗口,然后销毁窗口

2.1.3、Evictor

evictor 主要用于做一些数据的自定义操作,可以在执行用户代码之前,也可以在执行用户代码之后。本接口提供了两个重要的方法,即 evicBeforeevicAfter两个方法。

Flink 提供了如下三种通用的 evictor:

  • CountEvictor 保留指定数量的元素
  • TimeEvictor 设定一个阈值 interval,删除所有不再 max_ts - interval 范围内的元素,其中 max_ts 是窗口内时间戳的最大值
  • DeltaEvictor 通过执行用户给定的 DeltaFunction 以及预设的 theshold,判断是否删
    除一个元素。

2.2、窗口类型

Flink Window 窗口的结构中,有两个必须的两个操作:

  • 第一、窗口分配器(WindowAssigner):将数据流中的元素分配到对应的窗口。
  • 第二、窗口函数(Window Function):当满足窗口触发条件后,对窗口内的数据使用窗口处理函数(Window Function)进行处理,常用的有 reduce、aggregate、process。

在 Flink 窗口计算中,无论时间窗口还是计数窗口,都可以分为 2 种类型:滚动 Tumbling滑动 Sliding 窗口

  • 滚动窗口(Tumbling Window)
    条件:窗口大小 size = 滑动间隔 slide

  • 滚动窗口(Tumbling Window)
    条件:窗口大小 != 滑动间隔,通常条件【窗口大小 size > 滑动间隔 slide

Window 的生命周期是什么?
简单的说,当有第一个属于该 window 元素到达时就创建了一个 window,当时间或事件触发该 windowremoved 的时候则结束。每个 window 都有一个 Trigger 和 一个 Function,function用于计算,tigger 用于触发 window 条件。同时也可以使用 Evictor 在 Trigger 触发前后对 window 的元素进行处理。

2.2.1、Tumbling Windows

滚动窗口分配器(Tumbling windows assigner)将每个元素分配给指定窗口大小的窗口。滚动窗口具有固定大小,不会重叠。例如,如果指定大小为 5 分钟的滚动窗口,则将评估当前窗口,并且每 5 分钟启动一个新窗口,如下图所示:
滑动窗口
示例代码:

// 3-1. 对数据进行转换处理: 过滤脏数据,解析封装到二元组中
		SingleOutputStreamOperator<Tuple2<String, Integer>> mapStream = inputStream
			.filter(line -> line.trim().split(",").length == 2)
			.map(new MapFunction<String, Tuple2<String, Integer>>() {
				@Override
				public Tuple2<String, Integer> map(String line) throws Exception {
                    System.out.println("item: " + line);
					String[] array = line.trim().split(",");
					Tuple2<String, Integer> tuple = Tuple2.of(array[0], Integer.parseInt(array[1]));
					// 返回
					return tuple;
				}
			});
		
		// todo: 3-2. 窗口计算,每隔5秒计算最近5秒各个卡口流量
		SingleOutputStreamOperator<String> windowStream = mapStream
			// a. 设置分组key,按照卡口分组
			.keyBy(tuple -> tuple.f0)
			// b. 设置窗口,并且为滚动窗口:size=slide
			.window(
				TumblingProcessingTimeWindows.of(Time.seconds(5))
			)
			// c. 窗口计算,窗口函数
			.apply(new WindowFunction<Tuple2<String, Integer>, String, String, TimeWindow>() {
				// 定义变量,对日前时间数据进行转换
				private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss") ;

				@Override
				public void apply(String key, TimeWindow window,
				                  Iterable<Tuple2<String, Integer>> input,
				                  Collector<String> out) throws Exception {
					// 获取窗口时间信息:开始时间和结束时间
					String winStart = this.format.format(window.getStart());
					String winEnd = this.format.format(window.getEnd()) ;

					// 对窗口中数据进行统计:求和
					int sum = 0 ;
					for (Tuple2<String, Integer> tuple : input) {
						sum += tuple.f1 ;
					}

					// 输出结果数据
					String output = "window: [" + winStart + " ~ " + winEnd + "], " + key + " = " + sum ;
					out.collect(output);
				}
			});

2.2.2、Sliding Windows

滑动窗口分配器(sliding windows assigner)将元素分配给固定长度的窗口。与滚动窗口分配器类似,窗口的大小由窗口大小参数配置。窗口滑动参数控制滑动窗口的启动频率。因此,如果 sliding小于size,则滑动窗口可能会重叠。在这种情况下,元素被分配给多个窗口。例如,可以有大小为 10 分钟的窗口,该窗口滑动 5 分钟。这样,您每 5 分钟就会得到一个窗口,其中包含过去 10 分钟内到达的事件,如下图所示:
滑动窗口
示例代码:

// 3-1. 对数据进行转换处理: 过滤脏数据,解析封装到二元组中
		SingleOutputStreamOperator<Tuple2<String, Integer>> mapStream = inputStream
			.filter(line -> line.trim().split(",").length == 2)
			.map(new MapFunction<String, Tuple2<String, Integer>>() {
				@Override
				public Tuple2<String, Integer> map(String line) throws Exception {
                    System.out.println("item: " + line);
					String[] array = line.trim().split(",");
					return Tuple2.of(array[0], Integer.parseInt(array[1]));
				}
			});
		
		// todo: 3-2. 窗口计算,每隔5秒计算最近5秒各个卡口流量
		SingleOutputStreamOperator<String> windowStream = mapStream
			// a. 设置分组key,按照卡口分组
			.keyBy(tuple -> tuple.f0)
			// b. 设置窗口,并且为滚动窗口:size != slide
			.window(
				SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5))
			)
			// c. 窗口计算,窗口函数
			.apply(new WindowFunction<Tuple2<String, Integer>, String, String, TimeWindow>() {
				// 定义变量,对日前时间数据进行转换
				private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss") ;

				@Override
				public void apply(String key, TimeWindow window,
				                  Iterable<Tuple2<String, Integer>> input,
				                  Collector<String> out) throws Exception {
					// 获取窗口时间信息:开始时间和结束时间
					String winStart = this.format.format(window.getStart());
					String winEnd = this.format.format(window.getEnd()) ;

					// 对窗口中数据进行统计:求和
					int sum = 0 ;
					for (Tuple2<String, Integer> tuple : input) {
						sum += tuple.f1 ;
					}

					// 输出结果数据
					String output = "window: [" + winStart + " ~ " + winEnd + "], " + key + " = " + sum ;
					out.collect(output);
				}
			});

2.2.3、Session Windows

会话窗口分配器(session windows assigner)按活动会话对元素进行分组。与滚动窗口和滑动窗口相比,会话窗口不重叠,也没有固定的开始和结束时间。相反,当会话窗口在一段时间内未收到元素时(即,当出现不活动间隙时),会话窗口将关闭。会话窗口分配器可以配置静态会话间隙或会话间隙提取器功能,该函数定义不活动时间的时间。当此时间段到期时,当前会话将关闭,后续元素将分配给新的会话窗口。
Session Windows
示例代码:

// 3-1. 过滤和转换数据类型
		SingleOutputStreamOperator<Integer> mapStream = inputStream
			.filter(line -> line.trim().length() > 0)
			.map(new MapFunction<String, Integer>() {
				@Override
				public Integer map(String value) throws Exception {
					System.out.println("item: " + value);
					return Integer.parseInt(value);
				}
			});

		// 3-2. 直接对DataStream流进行窗口操作
		SingleOutputStreamOperator<String> windowStream = mapStream
			// a. 设置窗口:会话窗口,超时时间为5秒
			.windowAll(
				ProcessingTimeSessionWindows.withGap(Time.seconds(5))
			)
			// b. 设置窗口函数,对窗口中数据进行计算
			.apply(new AllWindowFunction<Integer, String, TimeWindow>() {
				// 定义变量,对日前时间数据进行转换
				private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss") ;

				@Override
				public void apply(TimeWindow window, Iterable<Integer> values, Collector<String> out) throws Exception {
					// 获取窗口时间信息:开始时间和结束时间
					String winStart = this.format.format(window.getStart());
					String winEnd = this.format.format(window.getEnd()) ;

					// 对窗口中数据进行求和
					int sum = 0 ;
					for (Integer value : values) {
						sum += value ;
					}

					// 输出结果数据
					String output = "window: " + winStart + " ~ " + winEnd + " -> " + sum ;
					out.collect(output);
				}
			});

2.2.4、Global Windows

全局窗口分配器(global windows assigner)将具有相同键的所有元素分配给同一个全局窗口。只有自己自定义触发器的时候该窗口才能使用。否则,将不会执行任何计算,因为全局窗口没有一个自然的终点,我们可以在该端点处理聚合元素。
Global Windows
示例代码:

// 3-1. 过滤和转换数据类型
		SingleOutputStreamOperator<Integer> mapStream = inputStream
			.filter(line -> line.trim().length() > 0)
			.map(new MapFunction<String, Integer>() {
				@Override
				public Integer map(String value) throws Exception {
					System.out.println("item: " + value);
					return Integer.parseInt(value);
				}
			});

		// TODO: 3-2. 直接对DataStream流进行窗口操作
		SingleOutputStreamOperator<String> windowStream = mapStream
			// a. 设置窗口,滚动计数窗口
			.countWindowAll(5)
			// b. 设置窗口函数,计算窗口中数据
			.apply(new AllWindowFunction<Integer, String, GlobalWindow>() {
				@Override
				public void apply(GlobalWindow window, Iterable<Integer> values, Collector<String> out) throws Exception {
					// 对窗口中数据进行求和
					int sum = 0 ;
					for (Integer value : values) {
						sum += value ;
					}

					// 输出累加求和值
					String output = "sum = " + sum ;
					out.collect(output);
				}
			});

2.3、Time 时间语义

  • 事件时间 EventTime:事件真真正正发生产生的时间,比如订单数据中订单时间表示订单产生的时间;
  • 摄入时间 IngestionTime:数据被流式程序获取的时间;
  • 处理时间 ProcessingTime:事件真正被处理/计算的时间。

基于事件时间 EventTime 窗口分析,指定事件时间字段,使用 assignTimestampsAndWatermarks 方法,类型必须为 Long 类型。

// 3-1. 过滤脏数据和指定事件时间字段字段
		SingleOutputStreamOperator<String> timeStream = inputStream
			.filter(line -> line.trim().split(",").length == 3)
			// todo: step1、指定事件时间字段,并且数据类型为Long类型
			.assignTimestampsAndWatermarks(
				WatermarkStrategy
					// 暂不考虑数据乱序和延迟
					.<String>forBoundedOutOfOrderness(Duration.ofSeconds(0))
					// 指定事件时间字段
					.withTimestampAssigner(
						new SerializableTimestampAssigner<String>() {
							private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

							@SneakyThrows
							@Override
							public long extractTimestamp(String element, long recordTimestamp) {
								// 2022-04-01 09:00:01,a,1 -> 2022-04-01 09:00:01 -> 1648774801000
								System.out.println("element -> " + element);
								// 分割字符串
								String[] array = element.split(",");
								// 获取事件时间
								String eventTime = array[0];
								// 转换格式
								Date eventDate = format.parse(eventTime);
								// z转换Long类型并返回
								return eventDate.getTime();
							}
						}
					)
			);

默认情况下(不考虑乱序和延迟),当数据事件时间EventTime >= 窗口结束时间,触发窗口数据计算
基于事件时间EventTime窗口分析,如果不考虑数据延迟乱序,当窗口被触发计算以后,延迟乱序到达的数据将不会被计算,而是直接丢弃。

窗口起始时间计算方式:
timestamp - (timestamp - offset + wondowsize)%windowsize
如:00:00:01 窗口大小:5s 乱序时间:0s,则:
1 -(1 - 0 + 5 )% 5 = 0

2.4、乱序和延迟数据处理

  • Watermark 水印机制
    在实际业务数据中,数据乱序到达流处理程序,属于正常现象,原因在于网络延迟导致数据延迟,无法避免的,所以应该可以允许数据乱序达到(在某个时间范围内),依然参与窗口计算。
    Watermark水印机制

  • Allowed Lateness 允许延迟
    默认情况下,当 watermark 超过 end-of-window 之后,再有之前的数据到达时,这些数据会被删除。为了避免有些迟到的数据被删除,因此产生了 allowedLateness 的概念。
    允许延迟

  • 乱序数据:Watermark,窗口数据计算等一下

    • 使用水位线Watermark,给每条数据加上一个时间戳
    • Watermark = 数据事件时间 - 最大允许乱序时间
    • 当数据的Watermark >= 窗口结束时间,并且窗口内有数据,触发窗口数据计算
  • 延迟数据:AllowedLateness,窗口计算状态保存一段时间

    • 设置方法参数:allowedLateness,表示允许延迟数据最多可以迟到多久,还可以进行计算(保存窗口,并且触发窗口计算)
    • 当某个窗口触发计算以后,继续等待多长时间,如果在等待时间范围内,有数据达到时,依然会触发窗口计算。如果到达等待时长以后,没有数据达到,销毁窗口数据信息。

真正迟到的数据默认会被丢弃,可通过侧边流输出到文件:
侧边输出

  • 1、窗口 window 的作用是为了周期性的获取数据
  • 2、watermark 作用是防止数据出现乱序(经常),事件时间内获取不到指定的全部数据,做的一种保险方法;
  • 3、allowLateNess 是将窗口关闭时间再延迟一段时间
  • 4、sideOutPut 是最后兜底操作,所有过期延迟数据,指定窗口已经彻底关闭,就会把数据放到侧输出流

2.5、综合案例

public static void main(String[] args) throws Exception {
		// 1. 执行环境-env
		Configuration configuration = new Configuration();
		configuration.setString("rest.port", "8081");
		StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(configuration);
		env.setParallelism(1) ;
		// todo: 设置Checkpoint
		setEnvCheckpoint(env) ;
		// todo: 设置重启策略
		env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, 10000));

		// 2. 数据源-source
		DataStreamSource<String> inputStream = env.socketTextStream("127.0.0.1", 9999);

		// 3. 数据转换-transformation
		/*
			业务数据:
				o_101,u_121,11.50,2022-04-05 10:00:02
			3-1. 过滤、解析和封装数据
			3-2. 设置事假时间字段值和水位线Watermark
			3-3. 窗口设置及处理数据
		 */
		// 3-1. 过滤、解析和封装数据
		SingleOutputStreamOperator<OrderEvent> orderStream = inputStream
			.filter(line -> null != line && line.trim().split(",").length == 4)
			.map(new MapFunction<String, OrderEvent>() {
				@Override
				public OrderEvent map(String value) throws Exception {
					// 分割为单次
					String[] array = value.split(",");
					// 封装实体类对象
					OrderEvent orderEvent = new OrderEvent() ;
					orderEvent.setOrderId(array[0]);
					orderEvent.setUserId(array[1]);
					orderEvent.setOrderMoney(Double.parseDouble(array[2]));
					orderEvent.setOrderTime(array[3]);
					// 返回实例对象
					return orderEvent;
				}
			});

		// 3-2. 设置事假时间字段值和水位线Watermark
		SingleOutputStreamOperator<OrderEvent> timeStream = orderStream.assignTimestampsAndWatermarks(
			WatermarkStrategy
				// 允许最大乱序时间:2秒,等待2秒钟触发窗口计算
				.<OrderEvent>forBoundedOutOfOrderness(Duration.ofSeconds(2))
				// 获取订单时间,设置事件事假
				.withTimestampAssigner(new SerializableTimestampAssigner<OrderEvent>() {
					private FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

					@SneakyThrows
					@Override
					public long extractTimestamp(OrderEvent element, long recordTimestamp) {
						System.out.println("order -> " + element);
						// 获取订单时间
						String orderTime = element.getOrderTime();
						// 转换为Date日期类型
						Date orderDate = format.parse(orderTime);
						// 转换Long并返回
						return orderDate.getTime();
					}
				})
		);

		// 3-3. 窗口设置及处理数据
		OutputTag<OrderEvent> lateOutputTag = new OutputTag<OrderEvent>("late-order"){} ;
		SingleOutputStreamOperator<OrderReport> windowStream = timeStream
			// 按照用户分组   event -> event.getUserId()
			.keyBy(OrderEvent::getUserId)
			// 设置窗口:10s,滚动窗口
			.window(TumblingEventTimeWindows.of(Time.seconds(10)))
			// 设置最大允许延迟时间
			.allowedLateness(Time.seconds(3))
			// 设置延迟很久数据侧边输出
			.sideOutputLateData(lateOutputTag)
			// 设置窗口函数,进行计算
			.apply(new OrderWindowFunction());

		// 4. 数据终端-sink
		windowStream.printToErr();

		// 获取侧边流中延迟数据
		DataStream<OrderEvent> lateOrderStream = windowStream.getSideOutput(lateOutputTag);
		lateOrderStream.printToErr("late>");

		// 5. 触发执行-execute
		env.execute("StreamOrderWindowReport");
	}

	/**
	 * 流式应用Checkpoint检查点设置
	 */
	private static void setEnvCheckpoint(StreamExecutionEnvironment env) {
		// 1. 启动Checkpoint
		env.enableCheckpointing(10000) ;

		// 2.设置StateBackend
		env.setStateBackend(new HashMapStateBackend());
		// 3.设置Checkpoint存储
		env.getCheckpointConfig().setCheckpointStorage("file:///D:/ckpt/");

		// 4. 设置相邻Checkpoint至少时间间隔
		env.getCheckpointConfig().setMinPauseBetweenCheckpoints(500);
		// 5. 设置Checkpoint最大失败次数
		env.getCheckpointConfig().setTolerableCheckpointFailureNumber(3);
		// 6. 设置取消job时Checkpoint是删除还是保留
		env.getCheckpointConfig().enableExternalizedCheckpoints(CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

		// 7.设置Checkpoint超时时间
		env.getCheckpointConfig().setCheckpointTimeout(10 * 60 * 1000);
		// 8. 设置Checkpoint最大并发次数
		env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
		// 9. 设置模式
		env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
	}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux系统安装Googletest单元测试框架

环境信息 系统&#xff1a;ubuntn cmake版本&#xff1a;3.5.1 gcc版本&#xff1a;5.4.0 1、下载googletest git clone https://github.com/google/googletest.git注意&#xff01;不选branch的话默认下载最新版本&#xff08;需要编译器能够支持C14&#xff09;&#xff0c;…

如何生成osg的动画路径文件

目录 1. 前言 2. 生成动画路径文件 2.1. 粗糙方式 2.2. 精确方式 1. 前言 在进行osg的开发中&#xff0c;有时需要对模型按某个路径或规则进行动画&#xff0c;如下&#xff1a; 奶牛在10秒时间段从起始的osg::Vec3d(0.0, 18, 1.0)位置 匀速直线运动到osg::Vec3d(0.0, -8, …

ubuntu 中使用Qt连接MMSQl,报错libqsqlodbc.so: undefined symbol: SQLAllocHandle

Qt4.8.7的源码编译出来的libqsqlodbc.so&#xff0c;在使用时报错libqsqlodbc.so: undefined symbol: SQLAllocHandle&#xff0c;需要在编译libqsqlodbc.so 的项目pro文件加上LIBS -L/usr/local/lib -lodbc。 这里的路径根据自己的实际情况填写。 编辑&#xff1a; 使用uni…

Python数据结构(队列)

Python数据结构&#xff08;队列&#xff09; 队列(queue)是只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表。 队列是一种先进先出的 (First n First ut)的线性表&#xff0c;简称FIFO。允许插入的一端为队尾&#xff0c;允许删除的一端为队头&#xff…

深度学习——图像分类(CIFAR-10)

深度学习——图像分类&#xff08;CIFAR-10&#xff09; 文章目录 前言一、实现图像分类1.1. 获取并组织数据集1.2. 划分训练集、验证集1.3. 图像增广1.4. 引入数据集1.5. 定义模型1.6. 定义训练函数1.7. 训练模型并保存模型参数 二、生成一个桌面小程序2.1. 使用QT设计师设计界…

建筑工程模板分类以及特点?

建筑工程模板可以根据材料、结构和用途等方面进行分类。以下是一些常见的建筑工程模板分类及其特点&#xff1a; 1. 木质模板&#xff1a; - 特点&#xff1a;木质模板是最常见和传统的模板类型。它们由木材制成&#xff0c;具有良好的可塑性和可加工性。木质模板适用于各种形状…

京东数据分析:2023年9月京东白酒行业品牌销售排行榜

鲸参谋监测的京东平台9月份白酒市场销售数据已出炉&#xff01; 9月白酒市场的整体热度较高&#xff0c;贵州茅台先是与瑞幸联名推出酱香拿铁&#xff0c;后又宣布与德芙推出联名产品酒心巧克力&#xff0c;引起了诸多消费者的关注。在这一热度的加持下&#xff0c;从销售上看&…

Kali Linux 安装搭建 hadoop 平台 调用 wordcount 示例程序 详细教程

步骤一&#xff1a; 目标&#xff1a;*安装虚拟机&#xff0c;在自己虚拟机上完成hadoop的伪分布式安装。&#xff08;安装完成后要检查&#xff09;* 1&#xff09;前期环境准备&#xff1a;&#xff08;虚拟机、jdk、ssh&#xff09; 2&#xff09;SSH相关配置 安装SSH Se…

【MyBatis篇】MyBatis动态代理总结

本人正在浅学mybatis&#xff0c;正学到mybatis动态代理&#xff0c;在查询多方资料之后做出以下总结&#xff0c;以便于系统学习时回顾&#xff1b; 目录 MyBatis为什么引入动态代理 mybatis的动态代理 Dao代理技术 MyBatis为什么引入动态代理 因为程序员的 懒&#xff0c;…

访问控制2

文章目录 主要内容一.Role和ClusterRole1.ClusterRole示例&#xff0c;创建一个名为test-clusterrole且仅有创建Pod和deployment的集群角色代码如下&#xff08;示例&#xff09;: 2.YAML文件创建代码如下&#xff08;示例&#xff09;: 3.将udbs用户和Clusterrole进行绑定&…

0基础学习VR全景平台篇第112篇:控制点和遮罩工具 - PTGui Pro教程

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 前情回顾&#xff1a;上节&#xff0c;我们用PTGui拼接了一张全景图&#xff0c;全景编辑器里的各项功能帮助我们进行了初步的检查和编辑。 之后我们需要使用【控制点】和【遮罩…

智慧垃圾站:AI视频智能识别技术助力智慧环保项目,以“智”替人强监管

一、背景分析 建设“技术先进、架构合理、开放智能、安全可靠”的智慧环保平台&#xff0c;整合环境相关的数据&#xff0c;对接已建业务系统&#xff0c;将环境相关数据进行统一管理&#xff0c;结合GIS技术进行监测、监控信息的展现和挖掘分析&#xff0c;实现业务数据的快速…

【AI视野·今日CV 计算机视觉论文速览 第271期】Thu, 19 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Thu, 19 Oct 2023 Totally 63 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Learning from Rich Semantics and Coarse Locations for Long-tailed Object Detection Authors Lingchen Meng, Xiyang D…

偕行十年,阿里云助力众安保险用科技温暖每一张保单

云布道师 前言&#xff1a;应云而生的众安保险&#xff0c;其 IT 架构都搭建在阿里云上&#xff0c;云服务器 ECS 关键应用上的数据需要保护&#xff0c;需要简单易用、稳定性高的方案&#xff0c;助力保险业务的快速开发和上线。在经过全面充分地沟通评估后&#xff0c;众安保…

凌晨!腾讯云终于发布了2023年度双十一优惠活动!

2023腾讯云双11优惠价格表终于来了&#xff0c;轻量2核2G3M云服务器88元一年、轻量2核4G5M服务器166.6元一年、3年轻量2核2G4M带宽优惠价366.6元、3年轻量2核4G5M配置566.6元&#xff0c;CVM云服务器2核2G配置SA2实例172.3元一年、标准型S5服务器2核2G配置280.8元一年&#xff…

个人微信管理系统都有些什么功能呢?

个人微信管理系统是一项高效的工具&#xff0c;可协助企业有效管理多个微信账号&#xff0c;从而提升工作效率。该系统允许企业在电脑上集中管理所有微信账号&#xff0c;省去了频繁切换设备的繁琐&#xff0c;同时还可以将微信账号分派给员工以进行更高效的微信管理。 来看看微…

融合通信系统在大型运动会中的应用

随着经济社会的发展&#xff0c;大型体育运动会的规模不断扩大&#xff0c;融合通信设备及指挥调度系统的应用逐渐成为不可或缺的一部分。通过融合通信设备的产品保障以及指挥调度系统的技术保障&#xff0c;可以对比赛进行高效、准确和实时的指挥和调度&#xff0c;确保比赛的…

安防视频监控平台EasyCVR新版(V.3.4)平台界面更新2.0

视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;在视频监控播放上&#xff0c;TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放&#xff0c;可同时播放多路视频流&#…

软考系统架构师知识点集锦一:系统工程与信息系统基础

一、考情分析 二、考点精讲 2.1 软件开发方法 &#xff08;1&#xff09;结构化开发方法 用户至上&#xff0c;自顶向下,逐步分解(求解)&#xff0c;严格区分工作阶段&#xff0c;每阶段有任务与成果&#xff0c;强调系统开发过程的整体性和全局性&#xff0c;系统开发过程工…

自动驾驶的未来展望和挑战

自动驾驶技术是一项引人瞩目的创新&#xff0c;将在未来交通领域产生深远影响。然而&#xff0c;随着技术的不断演进&#xff0c;自动驾驶也面临着一系列挑战和障碍。本文将探讨自动驾驶的未来发展方向、技术面临的挑战&#xff0c;以及自动驾驶对社会和环境的潜在影响。 自动驾…