Flink--7、窗口(窗口的概念、分类、API、分配器、窗口函数)、触发器、移除器

news2025/1/15 20:44:06

在这里插入图片描述
                       星光下的赶路人star的个人主页

                      内心的平静始于不再让他人掌控你的感情

文章目录

  • 0、前言
  • 1、窗口(Window)
    • 1.1 窗口的概念
    • 1.2 窗口的分类
    • 1.3 窗口API概览
    • 1.4 窗口分配器(Window Assigner)
      • 1.4.1 时间窗口
      • 1.4.2 计数窗口
    • 1.5 窗口函数
      • 1.5.1 增量聚合函数(ReduceFunction/AggregateFunction)
      • 1.5.2 全窗口函数(Full Window Functions)
      • 1.5.3 增量聚合和全窗口函数的结合使用
    • 1.6 其它API
      • 1.6.1 触发器(Trigger)
      • 1.6.2 移除器(Evictor)

0、前言

在批处理统计中,我们可以等待一批数据都到齐后,统一处理。但是在实时处理统计中,我们是来一条就得处理一条,那么我们怎么统计最近一段时间内的数据呢?引入“窗口”。
所谓的“窗口”,一般就是划定的一段时间范围,也就是“时间窗”;对在这范围内的数据进行处理,就是所谓的窗口计算。所以窗口和时间往往是分不开的。接下来我们就深入了解一下Flink中的时间语义和窗口的应用。

1、窗口(Window)

1.1 窗口的概念

Flink是一种流式计算引擎,主要是来处理无界数据流的,数据源源不断、无穷无尽。想要更加方便高效地处理无界流,一种方式就是将无限数据切割成有限的“数据块”进行处理,这就是所谓的“窗口”(Window)。
在这里插入图片描述
注意:Flink中窗口并不是静态准备好的,而是动态创建——当有落在这个窗口区间范围的数据达到时,才创建对应的窗口。另外,这里我们认为到达窗口结束时间时,窗口就触发计算并关闭,事实上“触发计算”和“窗口关闭”两个行为也可以分开.

1.2 窗口的分类

上面的其实是最简单的例子,是最简单的一种时间窗口。在Flink中,窗口的应用非常灵活,我们可以使用各种不同类型的窗口来实现需求。接下来我们就从不同的角度,对Flink中内置的窗口做一个分类说明。

1、按照驱动类型分类
在这里插入图片描述

2、按照窗口分配数据的规则分类
根据分配数据的规则,窗口的具体实现可以分为四类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)、会话窗口(Session Window)以及全局窗口(Global Window)。

(1)滚动窗口(Tumbling Window)
滚动窗口有固定的大小,是一种对数据进行“均匀切片”的划分方式。窗口之间没有重叠,也不会有间隔,是“首尾相接”的状态。这是最简单的窗口形式,每一个数据都会被分配到一个窗口,而且只会属于一个窗口。
在这里插入图片描述
(2)滑动窗口(Sliding Window)

在这里插入图片描述
(3)会话窗口
在这里插入图片描述
(4)全局窗口
就是把所有数据当做在同一个窗口。这种窗口没有结束的时候,默认是不会做触发计算的。如果希望它能对数据进行计算处理,还需要自定义触发器。
在这里插入图片描述

1.3 窗口API概览

1、按键分区(Keyed)和非按键分区(Non-Keyed)
在定义窗口操作之前,首先要确定,到底是基于按键分区(Keyed)的数据流KeyedStream来开窗,还是直接在没有按键分区的DataStream上开窗。也就是说,在调用窗口算子之前,是否有keyBy操作
(1)按键分区窗口(Keyed Window)
经过按键分区KeyBy操作后,数据流会按照key被分为多条逻辑流(Logical Streams),这就是KeyedStream。基于KeyedStream进行窗口操作时,窗口计算会在多个并行子任务上同时执行。相同key的数据会被发送到一个并行子任务,而窗口操作会基于每个key进行单独的处理。所以可以认为,每个key上都定义了一组窗口,各自独立地进行统计计算。
在代码实现上,我们需要先对DataStream调用.keyBy()进行按键分区,然后再调用.window()定义窗口。

stream.keyBy(...)
       .window(...)

(2)非按键分区
如果没有进行keyBy,那么原始的DataStream就不会分成多条逻辑流。这时窗口逻辑只能在一个任务(task)上执行,就相当于并行度变成了1。
在代码中,直接基于DataStream调用.windowAll()定义窗口。

stream.windowAll(...)

注意:对于非按键分区的窗口操作,手动调大窗口算子的并行度也是无效的,windowAll本身就是一个非并行的操作。
2、代码中窗口API的调用
窗口操作主要有两个部分:窗口分配器(Window Assigners)和窗口函数(Window Functions)。

stream.keyBy(<key selector>)
       .window(<window assigner>)
       .aggregate(<window function>)

其中.window()方法需要传入一个窗口分配器,它指明了窗口的类型;而后面的.aggregate()方法传入一个窗口函数作为参数,它用来定义窗口具体的处理逻辑。窗口分配器有各种形式,而窗口函数的调用方法也不止.aggregate()一种。

1.4 窗口分配器(Window Assigner)

定义窗口分配器(Window Assaginer)是构建窗口算子的第一步,它的作用就是定义数据应该被分配到哪个窗口。所以可以说,窗口分配器其实就是在指定窗口的类型。
窗口分配器最通用的定义方式,就是调用.window()方法。这个方法需要传入一个WindowAssiger作为参数,返回WindowedStream。如果是非按键分区窗口,那么直接调用.windowAll()方法,同样传入一个WindowAssigner,返回的是AllWindowedStream。
窗口按照驱动类型可以分成时间窗口和计数窗口,而按照具体的分配规则,又有滚动窗口、滑动窗口、会话窗口、全局窗口四种。除去需要自定义的全局窗口外,其他常用的类型Flink中都给出了内置的分配器的实现。

1.4.1 时间窗口

时间窗口是最常用的窗口类型,又可以细分为滚动、滑动和会话三种。
(1)滚动处理时间窗口
窗口分配器由类TumblingProcessingTimeWindows提供,需要调用它的静态方法.of()。

stream.keyBy(...)
       .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
       .aggregate(...)

这里.of()方法需要传入一个Time类型的参数size,表示滚动窗口的大小,我们这里创建了一个长度为5秒的滚动窗口。
另外,.of()还有一个重载方法,可以传入两个Time类型的参数:size和offset。第一个参数当然还是窗口大小,第二个参数则表示窗口起始点的偏移量。
(2)滑动处理时间窗口
窗口分配器由类SlidingProcessingTimeWindows提供,同样需要调用它的静态方法.of()。

stream.keyBy(...)
       .window(SlidingProcessingTimeWindows.of(Time.seconds(10)Time.seconds(5)))
       .aggregate(...)

这里.of()方法需要传入两个Time类型的参数:size和slide,前者表示滑动窗口的大小,后者表示滑动窗口的滑动步长。我们这里创建了一个长度为10秒、滑动步长为5秒的滑动窗口。
滑动窗口同样可以追加第三个参数,用于指定窗口起始点的偏移量,用法与滚动窗口完全一致。
(3)处理时间会话窗口
窗口分配器由类ProcessingTimeSessionWindows提供,需要调用它的静态方法.withGap()或者.withDynamicGap()。

stream.keyBy(...)
       .window(ProcessingTimeSessionWindows.withGap(Time.seconds(10)))
       .aggregate(...)

这里.withGap()方法需要传入一个Time类型的参数size,表示会话的超时时间,也就是最小间隔session gap。我们这里创建了静态会话超时时间为10秒的会话窗口。
另外,还可以调用withDynamicGap()方法定义session gap的动态提取逻辑。
(4)滚动事件时间窗口
窗口分配器由类TumblingEventTimeWindows提供,用法与滚动处理事件窗口完全一致。

stream.keyBy(...)
       .window(TumblingEventTimeWindows.of(Time.seconds(5)))
       .aggregate(...)

(5)滑动事件窗口
窗口分配器由类SlidingEventTimeWindows提供,用法与滑动处理事件窗口完全一致。

stream.keyBy(...)
       .window(SlidingEventTimeWindows.of(Time.seconds(10)Time.seconds(5)))
       .aggregate(...)

(6)事件时间会话窗口
窗口分配器由类EventTimeSessionWindows提供,用法与处理事件会话窗口完全一致。

stream.keyBy(...)
       .window(EventTimeSessionWindows.withGap(Time.seconds(10)))
       .aggregate(...)

1.4.2 计数窗口

计数窗口概念非常简单,本身底层是基于全局窗口(Global Window)实现的。Flink为我们提供了非常方便的接口:直接调用.countWindow()方法。根据分配规则的不同,又可以分为滚动计数窗口和滑动计数窗口两类,下面我们就来看它们的具体实现。
(1)滚动计数窗口
滚动计数窗口只需要传入一个长整型的参数size,表示窗口的大小。

stream.keyBy(...)
       .countWindow(10)

我们定义了一个长度为10的滚动计数窗口,当窗口中元素数量达到10的时候,就会触发计算执行并关闭窗口。
(2)滚动计数窗口
与滚动计数窗口类似,不过需要在.countWindow()调用时传入两个参数:size和slide,前者表示窗口大小,后者表示滑动步长。

stream.keyBy(...)
       .countWindow(103)

我们定义了一个长度为10、滑动步长为3的滑动计数窗口。每个窗口统计10个数据,每隔3个数据就统计输出一次结果。
(3)全局窗口
全局窗口是计数窗口的底层实现,一般在需要自定义窗口时使用。它的定义同样是直接调用.window(),分配器由GlobalWindows类提供。

stream.keyBy(...)
       .window(GlobalWindows.create());

需要注意使用全局窗口,必须自行定义触发器才能实现窗口计算,否则起不到任何作用。

1.5 窗口函数

定义了窗口分配器,我们只是知道了数据属于哪个窗口,可以将数据收集起来了;至于收集起来到底是要做什么,还得看窗口函数。所以在窗口分配器之后,必须再接上一个定义窗口如何计算的操作,这就是所谓的“窗口函数”(Window Functions)。
在这里插入图片描述
窗口函数定义了要对窗口中收集的数据做的计算操作,根据处理的方式可以分为两类:增量聚合函数和全窗口函数。

1.5.1 增量聚合函数(ReduceFunction/AggregateFunction)

窗口将数据收集起来,最基本的处理操作当然就是进行聚合。我们可以每来一个数据就在之前结果上聚合一次,这就是“增量聚合”。
典型的增量聚合函数有两个:ReduceFunction和AggregateFunction。
1、归约聚合(ReduceFunction)
代码示例(求水位累加值):

/**
 * 特点:
 *      两两聚合
 *      输入和输出的类型一样
 */
public class Demo06_Reduce {
     public static void main(String[] args) throws Exception {
             //创建Flink配置类(空参创建的话都是默认值)
             Configuration configuration = new Configuration();
             //修改配置类中的WebUI端口号
             configuration.setInteger("rest.port",3333);
             //创建Flink环境(并且传入配置对象)
             StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(configuration);

             env.socketTextStream("hadoop102",9999)
                     //映射
                     .map(new WaterSensorFunction())
                     //全局计数滑动窗口
                     .countWindowAll(3)
                     //累加
                     .reduce((waterSensor, t1) -> {
                         t1.setVc(waterSensor.getVc()+t1.getVc());
                         return t1;
                     })
                     .print();

             env.execute();
         }

}



测试截图:
在这里插入图片描述

2、聚合函数(AggregateFunction)
ReduceFunction可以解决大多数归约聚合的问题,但是这个接口有一个限制,就是聚合状态的类型、数据结果的类型必须和输入数据类型一样。
Flink Window API中的aggragate就突破了这个限制,可以定义更加灵活的窗口聚合操作。这个方法需要传入一个AggregateFunction的实现类作为参数。
AggregateFunction可以看作是ReduceFunction通用版本,这里有三种类型:输入类型(IN)、累加器类型(ACC)和输出类型(OUT)。输入类型IN就是输入流中元素的数据类型;累加器类型ACC则是我们进行聚合的中间状态类型;而数据类型当然就是最终计算结果的类型了。
接口中有四个方法:

  • createAccumulator():创建一个累加器,这就是为聚合创建一个初始状态,每个聚合任务只会调用一次。
  • add():将输入的元素添加到累加器中。
  • getResult():从累加器中提取聚合的输出结果
  • merge():合并两个累加器,并将合并后的状态作为一个累加器返回

所以可以看到,AggregateFunction的工作原理是:先调用createAccumulator()方法为任务初始化一个状态(累加器);而后每来一个数据就调用一次add()方法,对数据进行聚合,得到的结果保存在状态中;等到了窗口需要输出时,再调用getResult方法得到计算结果。很明显,与ReduceFunction相同,AggregateFunction也是增量式的聚合;而由于输入、中间状态、输出的类型可以不同,使得应用更加灵活方便。
代码实现:

/**
 * 输入和输出的类型不一样,sum、max、min、minBy、maxBy、reduce就不行了
 *
 *可以考虑用aggregate
 */
public class Demo07_Aggregate {
     public static void main(String[] args) throws Exception {
             //创建Flink配置类(空参创建的话都是默认值)
             Configuration configuration = new Configuration();
             //修改配置类中的WebUI端口号
             configuration.setInteger("rest.port",3333);
             //创建Flink环境(并且传入配置对象)
             StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(configuration);

             env.socketTextStream("hadoop102",9999)
                     .map(new WaterSensorFunction())
                     //全局计数滚动窗口
                     .countWindowAll(3)
                     /**
                      * 统计窗口中所有数据的vc之和
                      * AggregateFunction<IN,ACC,OUT>
                      *     IN:输入,窗口中元素的类型
                      *     ACC:累加器。聚合中使用的中间的缓存类型
                      *     OUT:输出的类型
                      *
                      *    以上三种类型都可以不一致
                      */
                     //输出: vc之和:xxx
                     .aggregate(new AggregateFunction<WaterSensor, Integer, String>() {

                         //创建一个累加器对象 (在一个窗口创建时执行一次)
                         @Override
                         public Integer createAccumulator() {
                             System.out.println("我是一个累加器");
                             return 0;
                         }
                         //把输入的每个元素累加到累加器上
                         @Override
                         public Integer add(WaterSensor waterSensor, Integer integer) {
                             return integer+waterSensor.getVc();
                         }
                         //输出最终结果(在窗口关闭执行时执行一次)
                         @Override
                         public String getResult(Integer integer) {
                             System.out.println("我是输出最终结果");
                             return "vc之和:"+integer;
                         }
                         //不用写。在DataSetAPI(批处理)中才要实现
                         @Override
                         public Integer merge(Integer integer, Integer acc1) {
                             return null;
                         }
                     })
                     .print();


                     env.execute();
         }
}


测试截图:
在这里插入图片描述
另外,Flink也为窗口的聚合提供了一系列预定义的简单聚合方法,可以直接基于WindowedStream调用。主要包括.sum()/max()/maxBy()/min()/minBy(),与KeyedStream的简单聚合非常相似。它们的底层,其实都是通过AggregateFunction来实现的。

1.5.2 全窗口函数(Full Window Functions)

有些场景下,我们要做的计算必须基于全部的数据才有效,这时做增量聚合就没什么意义了;另外,输出的结果可能要包含上下文中的一些信息(比如窗口的起始时间),这是增量聚合函数做不到的。
所以我们还需要有更丰富的窗口计算方式。窗口操作中的另一大类就是全窗口函数。与增量函数不同,全局窗口函数首先需要收集窗口中的数据,并在内部缓存起来,等到窗口要输出结果的时候再取出数据进行计算。
在Flink中,全局窗口函数也是有两种:WindowFunction和ProcessWindowFunction。
1、窗口函数(WindowFunction)
WindowFunction字面上就是“窗口函数”,它其实是老版本的通用窗口函数接口。我们可以基于WindowedStream调用.apply()方法,传入一个WindowFunction的实现类。

stream
    .keyBy(<key selector>)
    .window(<window assigner>)
    .apply(new MyWindowFunction());

这个类中可以获取到包含窗口所有数据的可迭代集合(Iterable),还可以拿到窗口(Window)本身的信息。
不过WindowFunction能提供的上下文信息比较少,也没有更高级的功能。事实上,它的作用可以被ProcessWindowFunction全覆盖,所以之后可能会逐渐弃用。
2、处理窗口函数(ProcessWindowFunction)
ProcessWindowFunction是Window API中最底层的通用窗口函数接口。之所以说它“最底层”,是因为除了可以拿到窗口中的所有数据之外,ProcessWindowFunction还可以获取到一个“上下文对象”(Context)。这个上下文对象非常强大,不仅能够获取窗口信息,还可以访问当前的时间和状态信息。这里的时间就包括了处理时间(processing time)和事件时间水位线(event time watermark)。这就使得ProcessWindowFunction更加灵活、功能更加丰富,其实就是一个增强版的WindowFunction。

public class WindowProcessDemo {
    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);


        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 9999)
                .map(new WaterSensorFunction());

        KeyedStream<WaterSensor, String> sensorKS = sensorDS.keyBy(sensor -> sensor.getId());

        // 1. 窗口分配器
        WindowedStream<WaterSensor, String, TimeWindow> sensorWS = sensorKS.window(TumblingProcessingTimeWindows.of(Time.seconds(10)));

        SingleOutputStreamOperator<String> process = sensorWS
                .process(
                        new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {
                            @Override
                            public void process(String s, Context context, Iterable<WaterSensor> elements, Collector<String> out) throws Exception {
                                long count = elements.spliterator().estimateSize();
                                long windowStartTs = context.window().getStart();
                                long windowEndTs = context.window().getEnd();
                                String windowStart = DateFormatUtils.format(windowStartTs, "yyyy-MM-dd HH:mm:ss.SSS");
                                String windowEnd = DateFormatUtils.format(windowEndTs, "yyyy-MM-dd HH:mm:ss.SSS");

                                out.collect("key=" + s + "的窗口[" + windowStart + "," + windowEnd + ")包含" + count + "条数据===>" + elements.toString());
                            }
                        }
                );

        process.print();

        env.execute();
    }
}

测试截图:
在这里插入图片描述

1.5.3 增量聚合和全窗口函数的结合使用

在实际应用中,我们往往希望兼具这两者的优点,把它们结合在一起使用。Flink的Window API就给我们实现了这样的用法。
我们之前在调用WindowedStream的.reduce()和.aggregate()方法时,只是简单地直接传入了一个ReduceFunction或AggregateFunction进行增量聚合。除此之外,其实还可以传入第二个参数:一个全窗口函数,可以是WindowFunction或者ProcessWindowFunction。

// ReduceFunction与WindowFunction结合
public <R> SingleOutputStreamOperator<R> reduce(
        ReduceFunction<T> reduceFunction,WindowFunction<TRKW> function) 

// ReduceFunction与ProcessWindowFunction结合
public <R> SingleOutputStreamOperator<R> reduce(
        ReduceFunction<T> reduceFunction,ProcessWindowFunction<TRKW> function)

// AggregateFunction与WindowFunction结合
public <ACCVR> SingleOutputStreamOperator<R> aggregate(
        AggregateFunction<TACCV> aggFunction,WindowFunction<VRKW> windowFunction)

// AggregateFunction与ProcessWindowFunction结合
public <ACCVR> SingleOutputStreamOperator<R> aggregate(
        AggregateFunction<TACCV> aggFunction,
        ProcessWindowFunction<VRKW> windowFunction)

这样调用的处理机制是:基于第一个参数(增量聚合函数)来处理窗口数据,每来一个数据就做一次聚合;等到窗口需要触发计算的时候,就调用第二个参数(全局窗口函数)的处理逻辑输出结果。需要注意的是,这里的全窗口函数就不再缓存所有数据了,而是直接将增量聚合函数的结果拿来当做了Iterable类型的输入。

public class WindowAggregateAndProcessDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);


        SingleOutputStreamOperator<WaterSensor> sensorDS = env
                .socketTextStream("hadoop102", 9999)
                .map(new WaterSensorFunction());


        KeyedStream<WaterSensor, String> sensorKS = sensorDS.keyBy(sensor -> sensor.getId());

        // 1. 窗口分配器
        WindowedStream<WaterSensor, String, TimeWindow> sensorWS = sensorKS.window(TumblingProcessingTimeWindows.of(Time.seconds(10)));

        // 2. 窗口函数:
        /**
         * 增量聚合 Aggregate + 全窗口 process
         * 1、增量聚合函数处理数据: 来一条计算一条
         * 2、窗口触发时, 增量聚合的结果(只有一条) 传递给 全窗口函数
         * 3、经过全窗口函数的处理包装后,输出
         *
         * 结合两者的优点:
         * 1、增量聚合: 来一条计算一条,存储中间的计算结果,占用的空间少
         * 2、全窗口函数: 可以通过 上下文 实现灵活的功能
         */

//        sensorWS.reduce()   //也可以传两个

        SingleOutputStreamOperator<String> result = sensorWS.aggregate(
                new MyAgg(),
                new MyProcess()
        );

        result.print();



        env.execute();
    }

    public static class MyAgg implements AggregateFunction<WaterSensor, Integer, String> {

        @Override
        public Integer createAccumulator() {
            System.out.println("创建累加器");
            return 0;
        }


        @Override
        public Integer add(WaterSensor value, Integer accumulator) {
            System.out.println("调用add方法,value="+value);
            return accumulator + value.getVc();
        }

        @Override
        public String getResult(Integer accumulator) {
            System.out.println("调用getResult方法");
            return accumulator.toString();
        }

        @Override
        public Integer merge(Integer a, Integer b) {
            System.out.println("调用merge方法");
            return null;
        }
    }

	 // 全窗口函数的输入类型 = 增量聚合函数的输出类型
    public static class MyProcess extends ProcessWindowFunction<String,String,String,TimeWindow> {

        @Override
        public void process(String s, Context context, Iterable<String> elements, Collector<String> out) throws Exception {
            long startTs = context.window().getStart();
            long endTs = context.window().getEnd();
            String windowStart = DateFormatUtils.format(startTs, "yyyy-MM-dd HH:mm:ss.SSS");
            String windowEnd = DateFormatUtils.format(endTs, "yyyy-MM-dd HH:mm:ss.SSS");

            long count = elements.spliterator().estimateSize();

            out.collect("key=" + s + "的窗口[" + windowStart + "," + windowEnd + ")包含" + count + "条数据===>" + elements.toString());

        }
    }
}

测试截图:
在这里插入图片描述

1.6 其它API

对于一个窗口算子而言,窗口分配器和窗口函数是必不可少的。除此之外,Flink还提供了其他一些可选的API,让我们可以更加灵活地控制窗口行为。
触发器和移除器在日常使用中很少会用到,这里仅仅简单介绍其语法格式。

1.6.1 触发器(Trigger)

触发器主要是用来控制窗口什么时候触发计算。所谓的“触发计算”,本质上就是执行窗口函数,所以可以认为是计算得到的结果并输出的过程。
基于WindowedStream调用.triggrt()方法,就可以传入一个自定义的窗口触发器(Trigger·)。

stream.keyBy(...)
       .window(...)
       .trigger(new MyTrigger())

1.6.2 移除器(Evictor)

移除器主要用来定义移除某些数据的逻辑。基于WindowedStream调用.evictor()方法,就可以传人一个自定义的移除器(Evictor)。Evictor是一个接口,不同的窗口类型都有各自预实现的移除器。

stream.keyBy(...)
       .window(...)
       .evictor(new MyEvictor())

在这里插入图片描述
                      您的支持是我创作的无限动力

在这里插入图片描述
                      希望我能为您的未来尽绵薄之力

在这里插入图片描述
                      如有错误,谢谢指正;若有收获,谢谢赞美

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

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

相关文章

什么是Vue的JSX语法?如何使用JSX语法

Vue的JSX语法&#xff1a;更接近JavaScript的模板语言 Vue.js是一个流行的JavaScript框架&#xff0c;用于构建交互式的Web应用程序。虽然Vue通常使用模板语法来构建用户界面&#xff0c;但它也提供了JSX语法的支持&#xff0c;使开发人员能够更接近JavaScript的表达方式来构建…

Eclipse MAT解析headp dump,total size小于file size

1. 问题描述 使用Eclipse MAT分析20GB的heap dump文件 最后解析出来dump size只有1GB 2. 原因&#xff1a;heap dump中包含许多unreachable objects Eclipse MAT的官方文档&#xff0c;《Basic Tutorial》章节&#xff0c;有对上图的Overview page做介绍 针对total size小…

Open Cascade旋转变换平行线

在本人开发的弯管自动CAM软件中&#xff0c;有一个问题一直没有解决&#xff0c;就是180度平行管路需要做角度微调&#xff0c;以便进行YBC预览。研究了一番后&#xff0c;搞定了这个问题&#xff0c;关键在于采用OCC库实现拓扑变换。 本文将介绍如何使用OpenCASCADE库来实现平…

微信公众号如何修改主体?

公众号账号迁移的作用是什么&#xff1f;只能变更主体吗&#xff1f;1.可合并多个公众号的粉丝、文章&#xff0c;打造超级大V2.可变更公众号主体&#xff0c;更改公众号名称&#xff0c;变更公众号类型——订阅号、服务号随意切换3.可以增加留言功能4.个人订阅号可迁移到企业名…

牛客题霸 -- 【模板】完全背包

参考代码&#xff1a; 未优化的代码&#xff1a; int n; int V; const int N1010; int v[N]; int w[N]; int dp[N][N];int main() {cin>>n>>V;for(int i1;i<n;i){cin>>v[i]>>w[i];}//第一问&#xff1a;//dp表中的第一行全是0&#xff0c;无需初始…

【初识Linux】上

初识Linux上 一、Linux背景1.1 UNIX发展的历史1.2 UNIX发展的历史 二、开源三、官网Linux官网 四、企业应用现状五、发行版本六、 os概念&#xff0c;定位 本博客简介 初始Linux操作系统初识shell命令 ,了解若干背景知识。使用常用Linux命令了解Linux权限概念与思想,能深度理解…

Centos7配置NAT网络

1、在网上查了好多内容&#xff0c;配置始终不能ping www.baidu.com&#xff0c;搞了一下午还是一样。 2、晚上查看DHCP配置&#xff0c;看到子网ip是192.168.70.0&#xff0c;但是起始ip为128起&#xff0c;于是将/etc/sysconfig/network-scripts/ifcfg-ens33 文件的ip换成13…

凉鞋的 Godot 笔记 104. 测试所涉及的窗口

104. 测试所涉及的窗口 在上一篇&#xff0c;笔者简单介绍了检视器窗口&#xff0c;如图所示&#xff1a; 我们接着介绍上图中的最后一个部分内容&#xff0c;测试部分。 测试部分我们只做了一件非常简单的操作&#xff0c;就是点击了一下运行当前场景按钮&#xff0c;查看结…

多目标黏菌算法(MOSMA)附带多个多目标性能指标

1 黏菌算法 http://t.csdnimg.cn/yArV5 2 多目标黏菌算法 %% Multiple Objective Slime Mould Algorithm (MOSMA) clc clear all D 30; % Number of decision variables M 2; % Number of objective functions KMD; LB ones(1, D).*0; % LB - A vector of decimal value…

PAT 1048 数字加密

PAT 1048 数字加密 题目描述思路讲解代码展示 题目描述 思路讲解 分析&#xff1a;首先将a和b倒置&#xff0c;将字符串a和b中较短的那个末尾添加0直到两个字符串长度相等&#xff0c;然后从0开始依次处理每一位&#xff0c;如果当前位是奇数位&#xff08;i % 2 0&#xff0…

力扣第 365 场周赛虚拟参赛

有序三元组中的最大值 I class Solution { public:long long maximumTripletValue(vector<int>& nums) {vector<long long> num;for (auto &item:nums) {num.push_back(item*1ll);}long long z 0,f 1000000;long long ans 0;long long maxx num[0],mi…

凉鞋的 Godot 笔记 105. 第一个通识:编辑-测试 循环

105. 第一个通识&#xff1a;编辑-测试 循环 在这一篇&#xff0c;我们简单聊聊此教程中所涉及的一个非常重要的概念&#xff1a;循环。 我们在做任何事情都离不开某种循环&#xff0c;比如每天的 24 小时循环&#xff0c;一日三餐循环&#xff0c;清醒-睡觉循环。 在学习一…

【SLAM数学基础】李群与李代数 BCH近似公式

三维旋转构成了三维旋转群 SO(3)&#xff0c;其对应的李代数为 s o \mathfrak{so} so(3)&#xff1b;三维变换构成了三维变换群 SE(3)&#xff0c;其对应的李代数为 s e \mathfrak{se} se(3)。 1.指数映射 李代数元素到李群元素的映射为指数映射&#xff0c;其中 s o \mat…

代码随想录算法训练营第五十五天 | 动态规划 part 12 | 300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

目录 300.最长递增子序列思路代码 674. 最长连续递增序列思路代码 718. 最长重复子数组思路代码 300.最长递增子序列 Leetcode 思路 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度递推公式&#xff1a;if (nums[i] > nums[j]) dp[i] max(dp[i], dp[j] 1)初…

力扣:118. 杨辉三角(Python3)

题目&#xff1a; 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官…

linux 笔记:远程服务器登录jupyter notebook

1 生成jupyter notebook 配置文件&#xff08;服务器端&#xff09; jupyter notebook --generate-config #Writing default config to: /home/shuailiu/.jupyter/jupyter_notebook_config.py2 Ipython中设置密码&#xff08;服务器端&#xff09; 3 修改jupyter 配置文件&…

QT实现TCP服务器客户端搭建的代码,现象

1.效果 2.服务器&#xff1a; 2.1&#xff1a;ui界面 2.2&#xff1a;头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QList> #include <QMessageBox> #include <QDeb…

pip常用操作

目录 1. 下载库1.1. 常规安装1.1.1. 不指定版本(默认为最新)1.1.2. 指定版本 1.2. 配置镜像1.2.1. 使用临时镜像1.2.2. 使用永久镜像1.2.2.1. 命令行配置1.2.2.2. 配置文件配置 2. 删除库3. 删除缓存4. 更新4.1. 更新pip4.2. 更新某个包 5. 项目依赖文件5.1. 给项目添加 requir…

(高阶) Redis 7 第18讲 RedLock 分布式锁

🌹 以下分享 RedLock 分布式锁,如有问题请指教。🌹🌹 如你对技术也感兴趣,欢迎交流。🌹🌹🌹 如有对阁下帮助,请👍点赞💖收藏🐱‍🏍分享😀 问题 分布式锁问题从(高阶) Redis 7 第17讲 分布式锁 实战篇_PJ码匠人的博客-CSDN博客 这篇文章来看,…

基于Java的宠物用品商城设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…