Flink Window Function

news2024/11/27 2:25:37

窗口函数定义了要对窗口中收集的数据做的计算操作,根据处理的方式可以分为两类:增量聚合函数和全窗口函数。

文章目录

      • 1.增量聚合函数
        • 1.1 ReduceFunction
        • 1.2 AggregateFunction
      • 2.全窗口函数
        • 2.1 WindowFunction
        • 2.2 ProcessWindowFunction
      • 3.增量聚合和全窗口函数的结合使用

1.增量聚合函数

  • 归约函数: ReduceFunction
  • 聚合函数: AggregateFunction

1.1 ReduceFunction

在这里插入图片描述

public class WindowTest {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
        environment.setParallelism(1);
        environment.getConfig().setAutoWatermarkInterval(100);

        DataStream<Event> dataStreamSource = environment.fromElements(
                new Event("Mary", "./home", 1000L),
                new Event("Bob", "./cart", 2000L),
                new Event("Alice", "./fav", 3000L),
                new Event("Mary", "./fav", 2000L),
                new Event("Bob", "./fav", 3000L),
                new Event("Alice", "./fav", 3000L),
                new Event("Bob", "./prod?id=1", 4000L)
        )
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(5))
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event event, long l) {
                                return event.timestamp;
                            }
                        })
                );

        dataStreamSource.map(new MapFunction<Event, Tuple2<String, Long>>() {
            @Override
            public Tuple2<String, Long> map(Event event) throws Exception {
                return Tuple2.of(event.user, 1L);
            }
        }).
                keyBy(data -> data.f0)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .reduce(new ReduceFunction<Tuple2<String, Long>>() {
                    @Override
                    public Tuple2<String, Long> reduce(Tuple2<String, Long> stringLongTuple2, Tuple2<String, Long> t1) throws Exception {
                        return Tuple2.of(stringLongTuple2.f0, stringLongTuple2.f1 + t1.f1);
                    }
                })
                .print();

        environment.execute();
    }
}

先是基于 WindowedStream 调用.reduce()方法, 然后传入ReduceFunction作为参数, 就是将窗口中收集到的数据两两规约。

(Mary,2)
(Alice,2)
(Bob,3)

每来一条数据,就会调用内部的 reduce 方法,将新数据中的 count值叠加到状态上,并得到新的状态保存起来。等到了 5 秒窗口的结束时间,就把归约好的状态直接输出。

1.2 AggregateFunction

public interface AggregateFunction<IN, ACC, OUT> extends Function, Serializable 
{
 ACC createAccumulator();
 ACC add(IN value, ACC accumulator);
 OUT getResult(ACC accumulator);
 ACC merge(ACC a, ACC b);
}

AggregateFunction 可以看作是 ReduceFunction 的通用版本, 输入类型(IN)、累加器类型(ACC)和输出类型 (OUT), 累加器类型 ACC 则是我们进行聚合的中间状态类型

  • createAccumulator: 创建一个累加器, 为聚合的初始状态
  • add: 将输入的元素添加到累加器, 每条数据到来之后都会调用这个方法。
  • getResult: 从累加器中提取聚合的输出结果。
  • merge: 合并两个累加器

在电商网站中,PV(页面浏览量)和 UV(独立访客数)是非常重要的两个流量指标。

  1. PV 统计的是所有的点击量
  2. UV 是全部的用户id总和
  3. PV/UV 代表的是人均重复访问量
public class WindowAggregateTest_PvUv {
    public static void main(String[] args) throws Exception {
        // Pv: +1
        // Uv: 去重
        // Pv/Uv: 平均每一个用户的访问次数, 网站的活跃度
        StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
        environment.setParallelism(1);


        SingleOutputStreamOperator<Event> dataStream = environment.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event event, long l) {
                                return event.timestamp;
                            }
                        })
                );
        dataStream.print("data: ");

        dataStream
                .keyBy(data -> true)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .aggregate(new AvgPv())
                .print();

        environment.execute();
    }

    // 参数1 Pv, 参数2 Uv
    public static class AvgPv implements AggregateFunction<Event, Tuple2<Long, HashSet<String>>, Double> {


        @Override
        public Tuple2<Long, HashSet<String>> createAccumulator() {
            return Tuple2.of(0L, new HashSet<>());
        }

        @Override
        public Tuple2<Long, HashSet<String>> add(Event event, Tuple2<Long, HashSet<String>> longHashSetTuple2) {
            longHashSetTuple2.f1.add(event.user);
            return Tuple2.of(longHashSetTuple2.f0 + 1, longHashSetTuple2.f1);
        }

        @Override
        public Double getResult(Tuple2<Long, HashSet<String>> longHashSetTuple2) {
            return Double.valueOf(longHashSetTuple2.f0/longHashSetTuple2.f1.size());
        }

        @Override
        public Tuple2<Long, HashSet<String>> merge(Tuple2<Long, HashSet<String>> longHashSetTuple2, Tuple2<Long, HashSet<String>> acc1) {
            return null;
        }
    }
}
data: > Event{user='Mary', url='./fav', timestamp=2022-12-13 18:58:58.794}
data: > Event{user='Bob', url='./fav', timestamp=2022-12-13 18:58:59.8}
data: > Event{user='Mary', url='./cart', timestamp=2022-12-13 18:59:00.809}
1.0
data: > Event{user='Bob', url='./home', timestamp=2022-12-13 18:59:01.825}
data: > Event{user='Mary', url='./home', timestamp=2022-12-13 18:59:02.84}
data: > Event{user='Bob', url='./cart', timestamp=2022-12-13 18:59:03.853}
data: > Event{user='Mary', url='./home', timestamp=2022-12-13 18:59:04.865}
data: > Event{user='Cary', url='./cart', timestamp=2022-12-13 18:59:05.914}
data: > Event{user='Mary', url='./home', timestamp=2022-12-13 18:59:06.922}
data: > Event{user='Cary', url='./cart', timestamp=2022-12-13 18:59:07.936}
data: > Event{user='Cary', url='./fav', timestamp=2022-12-13 18:59:08.951}
data: > Event{user='Cary', url='./fav', timestamp=2022-12-13 18:59:09.965}
data: > Event{user='Alice', url='./cart', timestamp=2022-12-13 18:59:10.973}
3.0

通过 ReduceFunction 和 AggregateFunction 我们可以发现,增量聚合函数其实就是在用流处理的思路来处理有界数据集,核心是保持一个聚合状态,当数据到来时不停地更新状态。这就是 Flink 所谓的“有状态的流处理”,通过这种方式可以极大地提高程序运行的效率,所以在实际应用中最为常见。

2.全窗口函数

  • 窗口函数: WindowFunction
  • 处理窗口函数: ProcessWindowFunction

2.1 WindowFunction

WindowedStream 调用.apply()方法,传入一个 WindowFunction 的实现类。

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

WindowFunction可以拿到可迭代集合和窗口本身信息

public interface WindowFunction<IN, OUT, KEY, W extends Window> extends Function, 
Serializable {
	void apply(KEY key, W window, Iterable<IN> input, Collector<OUT> out) throws 
Exception;
}

不过WindowFunction可提供的信息比较少, ProcessWindowFunction可以覆盖信息。

2.2 ProcessWindowFunction

ProcessWindowFunction 可以拿到上下文对象, 就包括了处理时间(processing time)和事件时间水位线(event time watermark)

统计电商网站统计每小时 UV

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

        SingleOutputStreamOperator<Event> dataStream = environment.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event event, long l) {
                                return event.timestamp;
                            }
                        })
                );

        dataStream.print("data: ");

        dataStream.keyBy(data -> true)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .process(new UvCountProcess())
                .print();

        environment.execute();
    }

    public static class UvCountProcess extends ProcessWindowFunction<Event, String, Boolean, TimeWindow> {

        @Override
        public void process(Boolean aBoolean, Context context, java.lang.Iterable<Event> elements, Collector<String> out) throws Exception {
            HashSet<String> hashSet = new HashSet<>();
            for (Event element : elements) {
                hashSet.add(element.user);
            }
            int size = hashSet.size();
            long start = context.window().getStart();
            long end = context.window().getEnd();
            out.collect("窗口 " + new Timestamp(start) + " ~ " + new Timestamp(end) + " Uv: " + size);

        }
    }
}
data: > Event{user='Cary', url='./fav', timestamp=2022-12-13 19:15:45.944}
data: > Event{user='Cary', url='./cart', timestamp=2022-12-13 19:15:46.956}
data: > Event{user='Bob', url='./cart', timestamp=2022-12-13 19:15:47.971}
data: > Event{user='Bob', url='./home', timestamp=2022-12-13 19:15:48.973}
data: > Event{user='Cary', url='./home', timestamp=2022-12-13 19:15:49.986}
data: > Event{user='Mary', url='./cart', timestamp=2022-12-13 19:15:51.001}
窗口 2022-12-13 19:15:40.0 ~ 2022-12-13 19:15:50.0 Uv: 2
data: > Event{user='Mary', url='./cart', timestamp=2022-12-13 19:15:52.014}
data: > Event{user='Cary', url='./home', timestamp=2022-12-13 19:15:53.018}
data: > Event{user='Cary', url='./fav', timestamp=2022-12-13 19:15:54.034}
data: > Event{user='Cary', url='./fav', timestamp=2022-12-13 19:15:55.038}
data: > Event{user='Mary', url='./cart', timestamp=2022-12-13 19:15:56.051}
data: > Event{user='Alice', url='./home', timestamp=2022-12-13 19:15:57.068}
data: > Event{user='Bob', url='./fav', timestamp=2022-12-13 19:15:58.084}
data: > Event{user='Mary', url='./fav', timestamp=2022-12-13 19:15:59.087}
data: > Event{user='Mary', url='./cart', timestamp=2022-12-13 19:16:00.104}
窗口 2022-12-13 19:15:50.0 ~ 2022-12-13 19:16:00.0 Uv: 4

HashSet 的元素个数就是 UV 值

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

增量聚合函数处理计算会更高效, 全窗口函数提供了更多的信息

WindowedStream的.aggregate()方法中, 可以添加两个函数的实现类

在这里插入图片描述

第一个参数为ReduceFunction或AggregateFunction, 第二个参数为WindowFunction或ProcessWindowFunction

基于第一个参数(增量聚合函数)来处理窗口数据,每来一个数据就做一次聚合;等到窗口需要触发计算时,则调用第二个参数(全窗口函数)的处理逻辑输出结果

Uv: 同一个网站的Uv

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

        SingleOutputStreamOperator<Event> dataStream = environment.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event event, long l) {
                                return event.timestamp;
                            }
                        })
                );
        dataStream.print("data: ");

        dataStream.keyBy(data -> true)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .aggregate(new UvAgg(), new UvCountResult())
                .print();

        environment.execute();
    }

    public static class UvAgg implements AggregateFunction<Event, HashSet<String>, Long> {

        @Override
        public HashSet<String> createAccumulator() {
            return new HashSet<>();
        }

        @Override
        public HashSet<String> add(Event event, HashSet<String> hashSet) {
            hashSet.add(event.user);
            return hashSet;
        }

        @Override
        public Long getResult(HashSet<String> events) {
            return Long.valueOf(events.size());
        }

        @Override
        public HashSet<String> merge(HashSet<String> events, HashSet<String> acc1) {
            return null;
        }
    }

    public static class UvCountResult extends ProcessWindowFunction<Long, String, Boolean, TimeWindow> {


        @Override
        public void process(Boolean aBoolean, Context context, Iterable<Long> elements, Collector<String> out) throws Exception {
            Long uv = elements.iterator().next();
            long start = context.window().getStart();
            long end = context.window().getEnd();
            out.collect("窗口: " + new Timestamp(start) + " ~ " + new Timestamp(end) + " Uv: " + uv);

        }
    }
}
data: > Event{user='Alice', url='./home', timestamp=2022-12-13 20:54:39.832}
data: > Event{user='Alice', url='./home', timestamp=2022-12-13 20:54:40.837}
窗口: 2022-12-13 20:54:30.0 ~ 2022-12-13 20:54:40.0 Uv: 1
data: > Event{user='Mary', url='./home', timestamp=2022-12-13 20:54:41.84}
data: > Event{user='Alice', url='./fav', timestamp=2022-12-13 20:54:42.853}
data: > Event{user='Cary', url='./fav', timestamp=2022-12-13 20:54:43.855}
data: > Event{user='Alice', url='./cart', timestamp=2022-12-13 20:54:44.863}
data: > Event{user='Bob', url='./fav', timestamp=2022-12-13 20:54:45.875}
data: > Event{user='Cary', url='./cart', timestamp=2022-12-13 20:54:46.89}
data: > Event{user='Mary', url='./home', timestamp=2022-12-13 20:54:47.891}
data: > Event{user='Mary', url='./fav', timestamp=2022-12-13 20:54:48.905}
data: > Event{user='Bob', url='./home', timestamp=2022-12-13 20:54:49.909}
data: > Event{user='Alice', url='./home', timestamp=2022-12-13 20:54:50.916}
窗口: 2022-12-13 20:54:40.0 ~ 2022-12-13 20:54:50.0 Uv: 4

在不同的Url 下的Uv

POJO UrlCountView

public class UrlCountView {
    public String url;
    public Long count;
    public Long start;
    public Long end;

    @Override
    public String toString() {
        return "UrlCountView{" +
                "url='" + url + '\'' +
                ", count=" + count +
                ", start=" + new Timestamp(start) +
                ", end=" + new Timestamp(end) +
                '}';
    }

    public UrlCountView() {
    }

    public UrlCountView(String url, Long count, Long start, Long end) {
        this.url = url;
        this.count = count;
        this.start = start;
        this.end = end;
    }
}
public class UvCountViewExample {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment environment = StreamExecutionEnvironment.getExecutionEnvironment();
        environment.setParallelism(1);

        SingleOutputStreamOperator<Event> dataStream = environment.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event event, long l) {
                                return event.timestamp;
                            }
                        })
                );
        dataStream.print("data: ");

        dataStream.keyBy(data -> data.url)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .aggregate(new UvCountViewAgg(), new UvCountViewResult())
                .print();

        environment.execute();
    }
    public static class UvCountViewAgg implements AggregateFunction<Event, Long, Long>{

        @Override
        public Long createAccumulator() {
            return 0L;
        }

        @Override
        public Long add(Event event, Long aLong) {
            return aLong + 1;
        }

        @Override
        public Long getResult(Long aLong) {
            return aLong;
        }

        @Override
        public Long merge(Long aLong, Long acc1) {
            return null;
        }
    }
    public static class UvCountViewResult extends ProcessWindowFunction<Long, UrlCountView, String, TimeWindow>{
        // key
        @Override
        public void process(String s, Context context, Iterable<Long> elements, Collector<UrlCountView> out) throws Exception {
            Long uv = elements.iterator().next();
            long start = context.window().getStart();
            long end = context.window().getEnd();
            out.collect(new UrlCountView(s, uv, start, end));
        }
    }
}
data: > Event{user='Cary', url='./cart', timestamp=2022-12-13 20:56:18.065}
data: > Event{user='Alice', url='./fav', timestamp=2022-12-13 20:56:19.077}
data: > Event{user='Cary', url='./fav', timestamp=2022-12-13 20:56:20.091}
UrlCountView{url='./cart', count=1, start=2022-12-13 20:56:10.0, end=2022-12-13 20:56:20.0}
UrlCountView{url='./fav', count=1, start=2022-12-13 20:56:10.0, end=2022-12-13 20:56:20.0}
data: > Event{user='Alice', url='./cart', timestamp=2022-12-13 20:56:21.105}
data: > Event{user='Mary', url='./home', timestamp=2022-12-13 20:56:22.111}
data: > Event{user='Alice', url='./cart', timestamp=2022-12-13 20:56:23.115}
data: > Event{user='Alice', url='./home', timestamp=2022-12-13 20:56:24.119}
data: > Event{user='Bob', url='./home', timestamp=2022-12-13 20:56:25.125}
data: > Event{user='Cary', url='./cart', timestamp=2022-12-13 20:56:26.129}
data: > Event{user='Mary', url='./cart', timestamp=2022-12-13 20:56:27.14}
data: > Event{user='Cary', url='./home', timestamp=2022-12-13 20:56:28.145}
data: > Event{user='Bob', url='./home', timestamp=2022-12-13 20:56:29.155}
data: > Event{user='Bob', url='./cart', timestamp=2022-12-13 20:56:30.171}
UrlCountView{url='./fav', count=1, start=2022-12-13 20:56:20.0, end=2022-12-13 20:56:30.0}
UrlCountView{url='./cart', count=4, start=2022-12-13 20:56:20.0, end=2022-12-13 20:56:30.0}
UrlCountView{url='./home', count=5, start=2022-12-13 20:56:20.0, end=2022-12-13 20:56:30.0}

窗口处理的主体还是增量聚合,而引入全窗口函数又可以获取到更多的信息包装输出,这样的结合兼具了两种窗口函数的优势,在保证处理性能和实时性的同时支持了更加丰富的应用场景。

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

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

相关文章

【MySQL】用户管理

文章目录用户管理用户信息创建用户删除用户修改用户密码用户权限用户赋权回收权限用户管理 如果我们只能使用root用户,这样存在安全隐患,因为root可以访问所有的数据库和表,这时,就需要使用MySQL的用户管理,从而限制某个特定的用户只能访问特定的数据库和表,并且对其权限作出一…

【51-订单模块-资源整合-整合SpringSession-订单中心核心逻辑-认证拦截-订单提交-订单生成逻辑-接口幂等性处理-接口幂等性解决方案】

一.知识回顾 【0.三高商城系统的专题专栏都帮你整理好了&#xff0c;请点击这里&#xff01;】 【1-系统架构演进过程】 【2-微服务系统架构需求】 【3-高性能、高并发、高可用的三高商城系统项目介绍】 【4-Linux云服务器上安装Docker】 【5-Docker安装部署MySQL和Redis服务】…

若依上面添加layui组件

1.因为若依本身有layui&#xff0c;所以不需要在引layui的css与js 2.下面是需要添加的 <div className"layui-btn-container" style"margin-top: 30px;"><button data-method"setTop" οnclick"sjhdekjshd()">多窗口模式…

基于vite搭建的vue3项目中如何引用环境变量

目录 回顾一下vue-cli2/cli3中环境变量使用方法 环境变量定义&#xff1a; 环境变量使用&#xff1a; vite中环境变量使用方法 环境变量定义&#xff1a; 环境变量使用&#xff1a; 回顾一下vue-cli2/cli3中环境变量使用方法 在vue-cli2/cli3中使用环境变量时 环境变量定…

Python Flask 路由配置

有关更多的Python 开发内容,可访问:《 Python Flask开发指南》 Flask中通过使用route装饰器实现路由访问功能,其路由匹配URL规则基于Werkzeug的路由模块。该模块基于Apache及更早的HTTP服务器主张,希望保证优雅且唯一的URL。其使用格式如下: from flask import Flask app …

突然 Java 倒下了......

TIOBE 公布了 2022 年 12 月的编程语言排行榜。 Java 首次跌出前 3 名。除此之外&#xff0c;Kotlin 和 Julia 也越来越接近 Top 20。 TIOBE 将于下个月揭晓其 2022 年度编程语言&#xff0c;目前共有 3 个候选者&#xff1a;Python、C 和 C。TIOBE CEO Paul Jansen指出&#…

如何实现工具无关化?——关于自动化测试脚本的设计

问题的提出 最近几年来&#xff0c;我的自动化测试工具之旅大致是这样的&#xff0c;最早用的是QTP&#xff0c;然后是RFT&#xff08;IBM的功能测试自动化产品&#xff09;&#xff0c;之后也经历了Selenium&#xff0c;Watir等。再后来还是一些商业工具&#xff0c;主要是偏…

KubeClipper 1.3.1 正式发布

2022 年 12 月 12 日&#xff0c;KubeClipper 1.3.1 版本正式发布&#xff01; 开源大事记 2022 年 08 月&#xff0c; KubeClipper 项目正式开源到 https://github.com/KubeClipper 项目。 2022 年 08 月&#xff0c;在由 OpenInfra 基金会举办的 2022 OpenInfra Days China…

nacos--基础--4.1--集成--SpringBoot--配置管理、服务发现、服务注册

nacos–基础–4.1–集成–SpringBoot–配置管理、服务发现、服务注册 代码位置 https://gitee.com/DanShenGuiZu/learnDemo/tree/master/nacos-learn/nacos-spring-boot1、介绍 主要面向 SpringBoot 的使用者通过2个实例&#xff0c;来介绍nacos和SpringBoot的集成 配置管理服…

【案例教程】Python气象海洋数据可视化到常见数据分析方法(折线图、柱状图、errorbar图、流场矢量、散点图、风玫瑰图、流场矢量、填色及等值线+地图)

【查看原文】Python在气象与海洋中的实践技术应用 Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;能够在不同操作系统和平台使用&#xff0c;简洁的语法和解释性语言使其成为理想的脚本语言。除了标准库&#xff0c;还有丰富的第三方库&#xf…

点线面数据处理(wkt,geojson互转)

点线面数据点&#xff1a;[103.8810, 31.0896] 线&#xff1a;[[103.7767, 30.8424],[104.2546, 30.8150],[104.3068, 30.4552]] 面&#xff1a;[[[103.8810, 31.0896],[104.0129, 30.8891],[103.7520, 30.8809],[103.8810, 31.0896]]] wkt数据点&#xff1a;POINT(103.365926 …

对Dueling DQN理论的深度分析。

强化学习中Agent与环境的交互过程是由马尔可夫决策过程(Markov Decision Process, MDP)描述的。MDP对环境做了一个假设&#xff0c;称作马尔可夫性质&#xff0c;即下一时刻的状态只由上一时刻的状态和动作决定。 马尔可夫性质决定了值函数(状态值与动作值函数)可以写成递归的形…

【项目总结】医疗化验单的OCR识别

项目总结 医疗化验单OCR 文章目录项目总结前言一、项目要求二、解决思路1.模型1.扶正2.裁剪3.pipeline三、总结前言 课题组项目的总结。 一、项目要求 课题组和广州的一家药企有合作&#xff0c;甲方要求把一张医疗化验单内的表格内容整体识别出来&#xff0c;特别是化验的数…

测开- Junit 单元测试框架

文章目录前言了解 Junit准备工作 - 在 pom.xml 文件中引入 Junit 相关依赖1、Junit注解TestBeforeEach、BeforeAllAfterEach && AfterAll2、断言1、Assertions - assertEquals 方法2、Assertions - assertNotEquals 方法3、Assertions - assertTrue && assertF…

一个关于React数据不可变的无聊问题

对于一个React的开发者来说不知道你有没有想过为什么React追求数据不可变这个范式&#xff1b; 一个月前我想过一个问题如果我在使用useState这个hooks的时候传入的是一个改变后的引用类型对象会发生什么&#xff1f; 例如&#xff1a; import {useState} from "react&…

css之@media网页适配

原因&#xff1a; 原本是想用 iview Grid 栅格,但以下响应并不符合我的需求 【我需要的分辨率是1920和1536】&#xff0c;所以需要手动修改 解决方案&#xff1a; html <Row> <i-col :xs"6" :sm"6" :md"6" :lg"8"><d…

IB经济试卷实用指南分享

明明每个经济理论都知道&#xff0c;然而在实际答题过程中就是用不出来&#xff1f; 看到答案后却是恍然大悟&#xff0c;就像对着数学答案&#xff0c;大体还能想明白“原来是这样的”&#xff0c;但考试的时候就是想不到用这个理论、这个模型&#xff1f; 出现这种现象&#…

左值和右值

左值和右值 按字面意思&#xff0c;通俗地说。以赋值符号 为界&#xff0c; 左边的就是左值&#xff0c; 右边就是右值。 更深一层&#xff0c;可以将 L-value 的 L, 理解成 Location&#xff0c;表示定位&#xff0c;地址。将 R-value 的 R 理解成 Read&#xff0c;表示读取…

[附源码]Python计算机毕业设计电影售票管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

什么是位移电流?位移电流密度计算公式详解

位移电流变化着的电场可以存在于真空、导体、电介质中&#xff0c;本质是变化着的电场&#xff0c;不会产生化学效应&#xff0c;也不会产生焦耳热。下面一起随着小编一起了解一下什么是位移电流以及位移电流密度计算公式。 什么是位移电流&#xff1f; 1861年&#xff0c;詹姆…