【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序

DataStream编程模型之 窗口的划分-时间概念-窗口计算程序

1. 窗口的划分

1.1 窗口分为:基于时间的窗口 和 基于数量的窗口

基于时间的窗口:基于起始时间戳终止时间戳来决定窗口的大小
基于数量的窗口:根据固定的数量定义窗口 的大小

这里我看到的都是只有基于时间的窗口做的划分,没有数量的,发现运用数量窗口划分也比较少,因此很多地方都省略了。

Count Window 也有滚动窗口、滑动窗口等,可以借鉴Flink实战之CountWindowr的滚动窗口、滑动窗口WindowsAPI使用示例
接下来就开始介绍基于时间的窗口

1.2 在Flink中窗口的设定和数据本身无关,而是系统事先定义好的

窗口是Flink划分数据一个基本单位,窗口的划分方式是默认会根据自然时间划分,并且划分方式是前闭后开。
如图:窗口的划分,左闭右开
左闭右开

2.时间概念

流数据中,数据具有时间属性。Flink根据时间的产生时间把时间划分为3中类型:1.事件生成时间(Event time)2.事件接入时间 (Ingestion Time)3.事件处理时间 (Processing Time)
可以借鉴下图理解:
在这里插入图片描述

1.基站产生数据,分区传入Flink数据源
2.传入数据的时间 IngstionTime
3.划分窗口进行处理时间 ProcessingTime

2.1 事件生成时间

是每个独立事件在产生它的设备上发生的时间,这个时间通常在事件进入Flink前就已经进入到事件当中了,也就是说,事件时间是从原始的消息中提取到的。比如 Kafka消息,每个生成的消息中自带一个时间戳代表每条数据的产生时间.

2.2事件接入时间

是数据进入Flink系统的时间,它主要依赖于其数据源算子所在主机的系统时钟。理论上,接入时间处于事件时间和处理时间之间。接入时间不能处理乱序问题或者延迟数据。如果需要处理此类问题,建议使用事件时间

2.3 事件处理时间

是指数据在操作算子计算过程中获取到的所在主机时间,这个时间是由Flink系统自己提供的。这种处理时间方式实时性是最好的,但计算结果未必准确,主要用于时间计算精度要求不是特别高的计算场景,比如延时比较高的日志数据

2.4事件时间和处理时间区别

这里的时间方便了解,比如事件时间,一个在米国产生的时间,一个在中国产生的时间,这两个有时差 不一样,但是数据世界是一样的,应该是从1970计算的时间戳。
在这里插入图片描述
在Flink初始化流式运行环境时,会设置流处理的时间特性

//设置执行环境 
val env = StreamExecutionEnvironment.getExecutionEnvironment 
//把时间特性设置为“事件时间” 
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) //或者,把时间特性设置为“处理时间” 
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

3.窗口计算

3.1 窗口计算的程序结构

3.1.1.分组

分为:分组窗口和非分组窗口
看一组图:
左侧为分组窗口,右侧不分组。
分组窗口

3.2窗口的计算过程

如图
在这里插入图片描述

在窗口计算时,需要将数据流先分组 keyby,再分窗口。那么在写程序的时候也是先进行这两步,当窗口程序计算完成后(ruduce,aggregate,process等),又变为DataStream。

分组数据流程序结构如下:

dataStream.keyBy(...)         //是分组数据流.window(...)          //指定窗口分配器类型[.trigger(...)]           //指定触发器类型(可选)[.evictor(...)]           //指定驱逐器或者不指定(可选)[.allowedLateness()]    //指定是否延迟处理数据(可选).reduce/fold/apply()   //指定窗口计算函数

非分组数据流程序结构如下:

dataStream.windowAll(...)      //指定窗口分配器类型[.trigger(...)]           //指定触发器类型(可选)[.evictor(...)]           //指定驱逐器或者不指定(可选)[.allowedLateness()]     //指定是否延迟处理数据(可选).reduce/fold/apply()    //指定窗口计算函数

不分组理解为所有数据为一个窗口。 windowAll(…)

3.2 窗口分配器

窗口分配器是负责将每一个到来的元素分配给一个或者多个窗口。
Flink提供预定义窗口分配器
在这里插入图片描述
窗口分配器在程序就一行可以了。

这几个窗口可以理解为火车站/汽车站/飞机场的屏幕,假设有25条消息,一页显示10条消息。
滚动:一页显示10条消息,滚动一下,下一页 11-20,滚动 21-25.
滑动:这里涉及一次滑动步长(假设为1),1-10 滑动 ,2-11滑动,3-12 …

3.2.1滚动窗口

滚动窗口是根据固定时间或大小对数据流进行切分,且窗口和窗口之间的元素不会重叠
在这里插入图片描述
DataStream API提供了两种滚动窗口类型,
即基于事件时间的滚动窗口(TumblingEventTimeWindows)和

基于处理时间的滚动窗口(TumblingProcessingTimeWindows),

二者对应的窗口分配器分别为TumblingEventTimeWindows
和TumblingProcessingTimeWindows。

窗口的长度

窗口的长度可以用org.apache.flink.streaming.api.windowing.time.Time中的
seconds、minutes、hours和days来设置。

3.2.1.1 滚动窗口的实例

1.事件 时间 滚动 ,窗口大小5秒
关键词:TumblingEventTimeWindows

val dataStream: DataStream[T] = ...//基于事件时间的滚动窗口,窗口大小为5秒钟
dataStream.keyBy(...).window(TumblingEventTimeWindows.of(Time.seconds(5))).<window function>(...)

2.基于处理时间的滚动
关键词:TumblingProcessingTimeWindows

//基于处理时间的滚动窗口,窗口大小为5秒钟
dataStream.keyBy(...).window(TumblingProcessingTimeWindows.of(Time.seconds(5))).<window function>(...)

3.事件时间的滚动窗口,窗口大小为1小时,偏移量为15分钟
偏移量调整窗口开始时间的数字。比如就会从整点的15分,30分,45分,00分开始,允许数据进行移位,用于时效性不强的数据。

//基于事件时间的滚动窗口,窗口大小为1小时,偏移量为15分钟
dataStream.keyBy(...).window(TumblingEventTimeWindows.of(Time.hours(1), Time.minutes(15))).<window function>(...)

还可以使用快捷方法 timeWindow() 来定义TumblingEventTimeWindowsTumblingProcessingTimeWindows,举例如下:

dataStream.keyBy(...).timeWindow(Time.seconds(1)).<window function>(...)

如果使用的是timewindow,那么就没说明是-事件时间-还是-处理时间。窗口类型就要根据程序中设置的TimeCharacteristic的值来决定。

当我们在程序中设置了env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)时,Flink会创建TumblingEventTimeWindows

当设置了env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)时,Flink会创建TumblingProcessingTimeWindows
认识英文单词就能很好的理解啦。

3.2.2滑动窗口

滑动窗口有重叠。(就是大屏幕的一个个向下滑动那种)
在这里插入图片描述

3.2.2.1 滑动窗口的实例

继续学习英文单词 slide :滑动(v) 它还有ppt幻灯片的意思。
window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5))) 里面得有两个参数,一个是窗口大小,一个是滑动步长。

不一样的就是处理时间和事件时间。

val dataStream: DataStream[T] = ...//基于事件时间的滑动窗口,窗口大小为10秒,滑动步长为5秒
dataStream.keyBy(...).window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5))).<window function>(...)

//基于处理时间的滑动窗口,窗口大小为10秒,滑动步长为5秒
dataStream.keyBy(<...>).window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5))).<window function>(...)

这个是参数多了偏移量。

//基于处理时间的滑动窗口,窗口大小为12小时,滑动步长为1小时,偏移量为8小时
dataStream.keyBy(<...>).window(SlidingProcessingTimeWindows.of(Time.hours(12), Time.hours(1), Time.hours(-8))).<window function>(...)
3.2.2.2 滑动步长与窗口大小的关系

在这里插入图片描述

3.2.3 会话窗口

会话窗口根据会话间隙(Session Gap)切分不同的窗口,当一个窗口在大于会话间隙的时间内没有接收到新数据时,窗口将关闭。在这种模式下,窗口的长度是可变的,每个窗口的开始和结束时间并不是确定的。
在这里插入图片描述
接下来再来两个实例
看代码的withgap,中间的gap时间。

val input: DataStream[T] = ...//基于事件时间的会话窗口,会话间隙为10分钟
input.keyBy(...).window(EventTimeSessionWindows.withGap(Time.minutes(10))).<window function>(...)//基于处理时间的会话窗口,会话间隙为10分钟
input.keyBy(...).window(ProcessingTimeSessionWindows.withGap(Time.minutes(10))).<window function>(...)

3.3窗口的计算函数

在Flink的窗口计算程序中,在确定了窗口分配器以后,接下来就要确定窗口计算函数,从而完成对窗口内数据集的计算。

Flink提供了四种类型的窗口计算函数,分别是

1. ReduceFunction、
2. AggregateFunction、
3. FoldFunction
4. ProcessWindowFunction。

根据计算原理,ReduceFunction、AggregateFunction和FlodFunction属于增量聚合函数,而ProcessWindowFunction则属于全量聚合函数(这里处理的是window 那么就是整个窗口了,就是全量了)。

3.3.1 ReduceFunction

ReduceFunction定义了对输入的两个相同类型的数据元素按照指定的计算方法进行聚合计算,然后输出类型相同的一个结果元素。

从这句话可以理解,先keyBy,再将同组的聚合计算。

接下来看代码

import java.util.Calendar
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.source.RichSourceFunction
import org.apache.flink.streaming.api.functions.source.SourceFunction.SourceContext
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time
import scala.util.Randomcase class StockPrice(stockId:String,timeStamp:Long,price:Double)
object ReduceWindowFunctionTest {def main(args: Array[String]) {//设置执行环境val env = StreamExecutionEnvironment.getExecutionEnvironment//设置程序并行度
env.setParallelism(1)//设置为处理时间
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)//创建数据源,股票价格数据流val stockPriceStream: DataStream[StockPrice] = env//该数据流由StockPriceSource类随机生成.addSource(new StockPriceSource)
//确定针对数据集的转换操作逻辑val sumStream = stockPriceStream.keyBy(s => s.stockId).timeWindow(Time.seconds(1)).reduce((s1, s2) => StockPrice(s1.stockId,s1.timeStamp, s1.price + s2.price))//打印输出
sumStream.print()//程序触发执行env.execute("ReduceWindowFunctionTest")}class StockPriceSource extends RichSourceFunction[StockPrice]{var isRunning: Boolean = trueval rand = new Random()//初始化股票价格var priceList: List[Double] = List(10.0d, 20.0d, 30.0d, 40.0d, 50.0d)var stockId = 0var curPrice = 0.0d
override def run(srcCtx: SourceContext[StockPrice]): Unit = {while (isRunning) {//每次从列表中随机选择一只股票stockId = rand.nextInt(priceList.size)val curPrice =  priceList(stockId) + rand.nextGaussian() * 0.05priceList = priceList.updated(stockId, curPrice)val curTime = Calendar.getInstance.getTimeInMillis//将数据源收集写入SourceContextsrcCtx.collect(StockPrice("stock_" + stockId.toString, curTime, curPrice))Thread.sleep(rand.nextInt(1000))}}override def cancel(): Unit = {isRunning = false}}
}

分析代码:
具体看这几行

   val sumStream = stockPriceStream.keyBy(s => s.stockId).timeWindow(Time.seconds(1)).reduce((s1, s2) => StockPrice(s1.stockId,s1.timeStamp, s1.price + s2.price))

现根据ID分组变成Keyedstream,再分窗口,然后reduce聚合同ID的price。

使用Maven工具对程序进行编译打包,然后提交到Flink中运行,在运行日志中可以看到类似如下的输出结果:

StockPrice(stock_1,1602036130952,39.78897954489408)
StockPrice(stock_4,1602036131741,49.950455275162945)
StockPrice(stock_2,1602036132184,30.073529000410154)
StockPrice(stock_3,1602036133154,79.88817093404676)
StockPrice(stock_0,1602036133919,9.957551599687758)
StockPrice(stock_1,1602036134385,39.68343765292602)
……

3.3.2 AggregateFunction

这个单词的意思就是聚合。

Flink的AggregateFunction是一个基于中间计算结果状态进行增量计算的函数。由于是迭代计算方式,所以,在窗口处理过程中,不用缓存整个窗口的数据,所以效率执行比较高。

AggregateFunction比ReduceFunction更加通用,它定义了3个需要复写的方法:

  1. add
  2. getResult
  3. merge

其中,add()定义了数据的添加逻辑,getResult()定义了累加器计算的结果,merge()定义了累加器合并的逻辑。

1.add 函数
功能:该函数用于将输入元素添加到累加器中。在聚合过程中,每当有新的数据元素流入时,Flink 会调用此函数来更新累加器的状态。

参数:add 函数通常接收两个参数,一个是当前的累加器状态,另一个是待聚合的输入元素。

返回值:该函数返回更新后的累加器状态。

2.getResult 函数

功能:该函数用于从累加器中提取聚合结果。在窗口触发或查询结束时,Flink 会调用此函数来获取最终的聚合结果。

参数:getResult 函数通常只接收一个参数,即当前的累加器状态。

返回值:该函数返回聚合后的结果,其类型通常由 AggregateFunction 的输出类型参数指定。

3.merge 函数:

功能:该函数用于在并行执行时合并两个累加器的状态。在分布式计算环境中,同一个窗口的数据可能会分配到不同的节点上进行处理。当这些节点上的聚合操作完成后,需要将它们的累加器状态合并起来以得到全局的聚合结果。

参数:merge 函数通常接收两个参数,即两个待合并的累加器状态。

返回值:该函数返回合并后的累加器状态。

这三个函数共同构成了 Flink 中 AggregateFunction 的核心逻辑。通过实现这三个函数,用户可以定义自定义的聚合操作,以满足各种复杂的数据处理需求。

注意 除了这三个函数外,AggregateFunction 接口通常还包含一个 createAccumulator 方法,用于初始化一个新的累加器实例。该方法在聚合操作开始时被调用,并返回一个空的或初始化的累加器状态。

举例代码:

import java.util.Calendar
import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.source.RichSourceFunction
import org.apache.flink.streaming.api.functions.source.SourceFunction.SourceContext
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time
import scala.util.Randomcase class StockPrice(stockId:String,timeStamp:Long,price:Double) object AggregateWindowFunctionTest {def main(args: Array[String]) { // 设置执行环境val env = StreamExecutionEnvironment.getExecutionEnvironment//设置程序并行度env.setParallelism(1)//设置为处理时间env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime) //创建数据源,股票价格数据流val stockPriceStream: DataStream[StockPrice] = env .addSource(new StockPriceSource)    //该数据流由StockPriceSource类随机生成stockPriceStream.print("input“)    //设定针对数据集的转换操作逻辑val sumStream = stockPriceStream.keyBy(s => s.stockId).timeWindow(Time.seconds(1)).aggregate(new MyAggregateFunction)   //自定义的聚合函数,那么就需要实现三个方法//打印输出sumStream.print("output“) //程序触发执行env.execute("AggregateWindowFunctionTest")}class StockPriceSource extends RichSourceFunction[StockPrice]{var isRunning: Boolean = trueval rand = new Random()// 初始化股票价格var priceList: List[Double] = List(10.0d, 20.0d, 30.0d, 40.0d, 50.0d)var stockId = 0var curPrice = 0.0d override def run(srcCtx: SourceContext[StockPrice]): Unit = {while (isRunning) {// 每次从列表中随机选择一只股票stockId = rand.nextInt(priceList.size)val curPrice =  priceList(stockId) + rand.nextGaussian() * 0.05priceList = priceList.updated(stockId, curPrice)val curTime = Calendar.getInstance.getTimeInMillis// 将数据源收集写入SourceContextsrcCtx.collect(StockPrice("stock_" + stockId.toString, curTime, curPrice))Thread.sleep(rand.nextInt(500))}}override def cancel(): Unit = {isRunning = false}}//自定义函数class MyAggregateFunction extends AggregateFunction[StockPrice,(String,Double,Long),(String,Double)] {//回忆一下:case class StockPrice(stockId:String,timeStamp:Long,price:Double) 
//返回值不要时间了,只要id 和price//创建累加器
override def createAccumulator(): (String,Double, Long) = ("",0D,0L)//定义把输入数据累加到累加器的逻辑override def add(input:StockPrice,acc:(String,Double,Long))={(input.stockId,acc._2+input.price,acc._3+1L)  //平均价格 所以数量  +1L}//根据累加器得出结果
override def getResult(acc:(String,Double,Long)) = (acc._1,acc._2 / acc._3)//定义累加器合并的逻辑override def merge(acc1:(String,Double,Long),acc2:(String,Double,Long)) = {(acc1._1,acc1._2+acc2._2,acc1._3+acc2._3)}}
}

这里我开始理解为了股票的三个属性(String,Double,Long),这里理解错了。

在 Apache Flink 的 AggregateFunction 接口中,当你定义一个自定义的聚合函数时,你需要指定三个类型参数:

1.输入类型(InputType):这是流中元素的类型,例子中为 StockPrice。
2.累加器类型(AccumulatorType):这是用于在聚合过程中存储中间状态的类型。例子中,这是一个三元组 (String, Double, Long),其中 String 表示股票ID(尽管这里的处理可能不是最理想的,因为通常累加器不应该包含像股票ID这样的非聚合字段),Double 表示价格的总和,Long 表示价格的数量(或说是处理了多少个价格数据点)。
3.输出类型(OutputType):这是聚合函数最终产生的结果类型。例子中,这也是一个二元组 (String, Double),其中 String 同样是股票ID(这里同样需要注意可能的逻辑问题),Double 是计算出的平均价格。

我才开始没有理解acc1._3 + acc2._3,现写如下:

acc1._1,acc1._2 + acc2._2, // 合并价格总和acc1._3 + acc2._3  // 合并价格数量

根据代码的先后顺序及运行顺序,最后执行getresult。
先聚合,最后平均。

在大多数情况下,add 方法会首先被调用,用于处理流入的数据并更新累加器状态。然后,根据并行度和数据分布,merge 方法可能会被调用以合并累加器状态。最后,在窗口触发或查询结束时,getResult 方法会被调用以提取最终的聚合结果。

代码分析完后,输出结果:

input> StockPrice(stock_2,1602040572049,29.99367518574229)
input> StockPrice(stock_2,1602040572205,30.03665296896211)
input> StockPrice(stock_2,1602040572601,30.00867347810531)
input> StockPrice(stock_0,1602040572856,9.974154737531954)
input> StockPrice(stock_1,1602040572934,19.997437804748245)
output> (stock_2,30.013000544269904)
output> (stock_1,19.997437804748245)
output> (stock_0,9.974154737531954)

3.3.3 FoldFunction

FoldFunction决定了窗口中的元素如何和一个输出类型的元素进行结合。对于每个进入窗口的元素而言,FoldFunction会被增量调用。窗口中的第一个元素将会和这个输出类型的初始值进行结合。需要注意的是,FoldFunction不能用于会话窗口和那些可合并的窗口。

//前面的代码和ReduceWindowFunctionTest程序中的代码相同,因此省略
val sumStream = stockPriceStream.keyBy(s => s.stockId).timeWindow(Time.seconds(1)).fold("CHINA_"){ (acc, v) => acc + v.stockId }

3.3.4 ProcessWindowFunction

前面提到的ReduceFunction和AggregateFunction都是基于中间状态实现增量计算的窗口函数,虽然已经满足绝大多数场景的需求

但是,在某些情况下,统计更复杂的指标可能需要依赖于窗口中所有的数据元素,或需要操作窗口中的状态数据和窗口元数据,这时就需要使用到 ProcessWindowFunction,因为它能够更加灵活地支持基于窗口全部数据元素的结果计算。

import java.time.Duration
import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collectorcase class StockPrice(stockId:String,timeStamp:Long,price:Double)
object ProcessWindowFunctionTest {def main(args: Array[String]) {//设置执行环境val env = StreamExecutionEnvironment.getExecutionEnvironment//设置程序并行度env.setParallelism(1)//设置为处理时间env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)//创建数据源,股票价格数据流val source = env.socketTextStream("localhost", 9999)//指定针对数据流的转换操作逻辑val stockPriceStream = source.map(s => s.split(",")).map(s=>StockPrice(s(0).toString,s(1).toLong,s(2).toDouble))val sumStream = stockPriceStream.assignTimestampsAndWatermarks(WatermarkStrategy//为了测试方便,这里把水位线设置为0.forBoundedOutOfOrderness[StockPrice](Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner[StockPrice] {override def extractTimestamp(element: StockPrice, recordTimestamp: Long): Long = element.timeStamp})).keyBy(s => s.stockId).timeWindow(Time.seconds(3)).process(new MyProcessWindowFunction())//打印输出sumStream.print()//执行程序env.execute("ProcessWindowFunction Test")}class MyProcessWindowFunction extends ProcessWindowFunction[StockPrice, (String, Double), String, TimeWindow] {//一个窗口结束的时候调用一次(一个分组执行一次),不适合大量数据,全量数据保存在内存中,会造成内存溢出override def process(key: String, context: Context, elements: Iterable[StockPrice], out: Collector[(String, Double)]): Unit = {//聚合,注意:整个窗口的数据保存到Iterable,里面有很多行数据var sumPrice = 0.0;elements.foreach(stock => {sumPrice = sumPrice + stock.price})out.collect(key, sumPrice/elements.size)}}
}

这个代码里需要注意的是 .assignTimestampsAndWatermarks 和MyProcessWindowFunction

.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness[StockPrice](Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner[StockPrice] {override def extractTimestamp(element: StockPrice, recordTimestamp: Long): Long = element.timeStamp})
)

WatermarkStrategy.forBoundedOutOfOrderness 用于创建一个处理有界乱序数据的水位线策略。参数 Duration.ofSeconds(0) 表示没有乱序,即所有数据都是按时序到达的(在实际应用中,这通常是一个简化的假设,实际数据往往会有一定程度的乱序)。withTimestampAssigner 方法用于指定如何从数据元素中提取时间戳,这里是从 StockPrice 对象的 timeStamp 字段中提取。

process 方法用于应用一个自定义的 ProcessWindowFunction。在这个例子中,MyProcessWindowFunction 是一个自定义的窗口函数,它接收一个键(股票ID)、一个上下文对象(包含窗口的元数据,如开始和结束时间)、一个包含窗口内所有元素的迭代器,以及一个用于收集输出结果的收集器。

在 MyProcessWindowFunction 的 process 方法中,代码遍历了窗口内的所有 StockPrice 元素,计算了价格的总和,并计算了平均值(总和除以元素数量)。然后,它将结果(股票ID和平均价格)收集到输出流中。

下一小节该总结触发器啦。

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

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

相关文章

Java代码操作Zookeeper(使用 Apache Curator 库)

1. Zookeeper原生客户端库存在的缺点 复杂性高&#xff1a;原生客户端库提供了底层的 API&#xff0c;需要开发者手动处理很多细节&#xff0c;如连接管理、会话管理、异常处理等。这增加了开发的复杂性&#xff0c;容易出错。连接管理繁琐&#xff1a;使用原生客户端库时&…

linux系统下如何将xz及ISO\img等格式压缩包(系统)烧写到优盘(TF卡)

最近用树莓派做了个NAS&#xff0c;效果一般&#xff0c;缺少监控及UI等&#xff0c;详细见这篇文章&#xff1a; https://blog.csdn.net/bugsycrack/article/details/135344782?spm1001.2014.3001.5501 所以下载了专门的基于树莓派的NAS系统直接使用。这篇文章是顺便复习一…

带有悬浮窗功能的Android应用

android api29 gradle 8.9 要求 布局文件 (floating_window_layout.xml): 增加、删除、关闭按钮默认隐藏。使用“开始”按钮来控制这些按钮的显示和隐藏。 服务类 (FloatingWindowService.kt): 实现“开始”按钮的功能&#xff0c;点击时切换增加、删除、关闭按钮的可见性。处…

MD5算法加密笔记

MD5是常见的摘要算法。 摘要算法&#xff1a; 是指把任意⻓度的输⼊消息数据转化为固定⻓度的输出数据的⼀种密码算法. 摘要算法是 不可逆的, 也就是⽆法解密. 通常⽤来检验数据的完整性的重要技术, 即对数据进⾏哈希计算然后⽐ 较摘要值, 判断是否⼀致. 常⻅的摘要算法有: MD5…

C#变量和函数如何和unity组件绑定

1.Button On_click (1)GameObject通过Add component添加上Script (2)Button选GameObject组件而不是直接选Script,直接选Script出现不了Script中的函数 2.RawImage 上面是错的 3.Text 上面是错的&#xff0c;应该是直接在GameObject里面填上对应的值 总结&#xff1a; …

开源 AI 智能名片 2 + 1 链动模式 S2B2C 商城小程序源码助力品牌共建:价值、策略与实践

摘要&#xff1a;在当今数字化商业环境下&#xff0c;品牌构建已演变为企业与消费者深度共建的过程。本文聚焦于“开源 AI 智能名片 2 1 链动模式 S2B2C 商城小程序源码”&#xff0c;探讨其如何融入品牌建设&#xff0c;通过剖析品牌价值构成&#xff0c;阐述该技术工具在助力…

介绍一下atol(arr);(c基础)

hi , I am 36 适合对象c语言初学者 atol(arr)&#xff1b;是返回整数(long型)&#xff0c;整数是arr数组中字符中数字 格式 #include<stdio.h> atol(arr); 返回值arr数组中的数字 未改变arr数组 #include<stdio.h> //atol(arr); 返 <stdlib> int main…

数据结构C语言描述5(图文结合)--广义表讲解与实现

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;有C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…

鸿蒙学习使用本地真机运行应用/元服务 (开发篇)

文章目录 1、前提条件2、使用USB连接方式3、使用无线调试连接方式4、运行 1、前提条件 在Phone和Tablet中运行HarmonyOS应用/元服务的操作方法一致&#xff0c;可以采用USB连接方式或者无线调试的连接方式。两种连接方式是互斥的&#xff0c;只能使用一种&#xff0c;无法同时…

48-基于单片机的LCD12864时间调控和串口抱站

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机的公交报站系统&#xff0c;可以手动报站&#xff0c;站名十个。 在lcd12864上显示时间&#xff08;年月日时分秒&#xff09;和站名&#xff0c;时间可以设置&#xff0c; 仿真中可以…

【汽车制动】汽车制动相关控制系统

目录 1.ABS (Anti-lock Brake System&#xff0c;防抱死制动系统) 2.EBD&#xff08;Electronic Brake-force Distribution&#xff0c;电子制动力分配系统&#xff09; 3.TCS&#xff08;Traction Control System&#xff0c;牵引力控制系统&#xff09; 4.VDC&#xff08…

DDR3与MIG IP核详解(一)

一、ddr3(全称第三代双倍速率同步动态随机存储器)&#xff1a; 1、特点&#xff1a;1&#xff1a;掉电无法保存数据&#xff0c;需要周期性的刷新。2:时钟上升沿和下降沿都会传输数据。 3&#xff1a;突发传输&#xff0c;突发长度 Burst Length一般为…

【算法 python A*算法的实现】

- 算法实现&#xff1a; import heapqclass Node:def __init__(self, position, g0, h0):self.position position # 节点的位置self.g g # 从起点到当前节点的成本self.h h # 从当前节点到终点的启发式估计成本self.f g h # 总成本self.parent None # 父节点def __…

苹果系统中利用活动监视器来终止进程

前言 苹果系统使用的时候总是感觉不太顺手。特别是转圈的彩虹球出现的时候&#xff0c;就非常令人恼火。如何找到一个像Windows那样任务管理器来终止掉进程呢&#xff1f; 解决办法 Commandspace 弹出搜索框吗&#xff0c;如下图&#xff1a; 输入“活动”进行搜索&#xff…

深度学习—损失函数及BP算法初步学习Day36

损失函数 1.MAE MAE&#xff08;Mean Absolute Error&#xff0c;平均绝对误差&#xff09;通常也被称为 L1-Loss&#xff0c;通过对预测值和真实值之间的绝对差取平均值来衡量他们之间的差异。。 MAE的公式如下&#xff1a; MAE 1 n ∑ i 1 n ∣ y i − y ^ i ∣ \text{…

[UUCTF 2022 新生赛]ez_rce

[UUCTF 2022 新生赛]ez_rce 我们来分析一下这个代码&#xff1a; 首先是isset看我们有没有传一个为空的值&#xff0c;如果为空就输出居然都不输入参数&#xff0c;可恶!!!!!!!!!不为空就GET传参赋值给$code &#xff0c;接着 如果 $code 中不包含这些模式中的任何一个&#x…

Flutter:启动屏逻辑处理02:启动页

启动屏启动之后&#xff0c;制作一个启动页面 新建splash&#xff1a;view 视图中只有一张图片sliding.png就是我们的启动图 import package:flutter/material.dart; import package:get/get.dart; import index.dart; class SplashPage extends GetView<SplashController…

系统思考—共同看见

在一家零售企业的项目中&#xff0c;团队频繁讨论客户流失的严重性&#xff0c;但每次讨论的结果都无法明确找出问题的根源。大家都知道客户流失了&#xff0c;但究竟是什么原因导致的&#xff0c;始终没有一致的答案。市场部认为是客户体验差&#xff0c;客服部门觉得是响应慢…

数据结构(Java版)第四期:ArrayLIst和顺序表(上)

目录 一、顺序表 1.1. 接口的实现 二、ArrayList简介 2.1. ArrayList的构造 2.2. ArrayList的常见操作 2.3. ArrayList的扩容机制 三、ArrayList的具体使用 3.1. 洗牌算法 3.2. 杨辉三角 一、顺序表 上一期我们讲到过&#xff0c;顺序表本质上和数组是差不多的&#…

Python编程语言中的优雅艺术:数值分隔符的巧妙运用

在Python编程的世界里&#xff0c;有许多精巧的设计让代码更优雅、更易读。今天要分享的是一个看似简单却能大幅提升代码可读性的特性 —— 数值分隔符。这个特性从Python 3.6版本开始引入&#xff0c;它用一种极其优雅的方式解决了大数值表示的难题。 数值分隔符的本质与应用…