开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Springboot系列(二) yaml、propertie

发表于 2021-11-21

👨‍🎓作者:bug菌

✏️博客:CSDN、掘金、infoQ、51CTO等

🎉简介:CSDN博客专家,C站历届博客之星Top50,掘金/InfoQ/51CTO等社区优质创作者,全网合计8w粉+,对一切技术感兴趣,重心偏Java方向;硬核公众号「 猿圈奇妙屋」,欢迎小伙伴们的加入,一起秃头,一起变强。

..

✍️温馨提醒:本文字数:1999字, 阅读完需:约 5 分钟

嗨,家人们,我是bug菌呀,我又来啦。今天我们来聊点什么咧,OK,接着为大家更[《springboot零基础入门教学》](https://link.juejin.cn/?target=https%3A%2F%2Fblog.csdn.net%2Fweixin_43970743%2Fcategory_11599389.html)系列文章吧。希望能帮助更多的初学者们快速入门!

小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐+关注👨‍🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻‍♀️,创作不停💕,加油☘️

一、前言

续上一篇咱们已经搭建好了一个springboot框架雏形。但是很多初学的小伙伴私信bug菌说,在开发项目中,为啥.yaml的配置文件也能配置,SpringBoot 是提供了两种2 种全局的配置文件嘛,这两种配置有何区别,能否给大家伙讲讲。一看这位小伙伴就非常好学啊,继续保持。

创建Spring Boot项目时,默认 resources 目录下就有一个application.properties 文件,可以在 application.properties 文件中进行项目配置,但是这个文件并非唯一的配置文件,

1、在 Spring Boot 中,一共有 4 个地方可以存放该配置文件,并按照如下优先级依次降低,如下:

  1. 当前项目根目录的config下
  2. 当前项目根目录下
  3. resource目录的config目录下
  4. resource目录下

2、SpringBoot 默认使用以下 2 种全局的配置文件,其文件名是固定的。

  • application.properties
  • application.yml

application.properties:语法:key=value 或行内写法(k: v;[Set/List/数组];{map,对象类型的属性},并且[]可以省,但是{}不能省)。

比如上一篇提到的配置端口号:

1
2
ini复制代码#指定运行端口
server.port=8080

application.yml : yaml ainot myarkup language ,不是一个标记文档。key: value

注意:yml默认可以不写引号,“”(双引号)会将其中的转义符进行转义,其他的(单引号啥的)不会。

二、application.properties使用

1、公共配置文件:application.properties(注意,此处取名别直接name,会跟系统默认的重名,所以;取名userName即可以示区分。)

1
2
3
ini复制代码user.userName=bug菌
user.sex=男
user.age=18

然后通过@Value 注解将这些属性注入;

1
2
3
4
5
6
7
8
9
10
kotlin复制代码@Value("${user.userName}")
private String userName;
@Value("${user.sex}")
private String sex;
@Value("${user.age}")
private Integer age;

@GetMapping("/hello1")
public String index() {
return "我是"+userName+",性别:"+sex+",我今年"+age+"岁啦!";}

或者创建一个user类;然后属性一一注入;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typescript复制代码@Component
public class User {

@Value("${user.userName}")
private String userName;
@Value("${user.sex}")
private String sex;
@Value("${user.age}")
private Integer age;

public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
} public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
}
}

@Autowired
private User user;

@GetMapping("/hello2")
public String test() {
return "我是"+user.getName()+",性别:"+user.getSex()+",我今年"+user.getAge();
}

然后分别访问一下;

http://localhost:8080/test/hello1

http://localhost:8080/test/hello2

ps:如果遇到打印出的中文乱码,如下所示,不要慌;bug菌教你怎么解决;好伐。

解决办法:应该是编辑器的问题,默认是GBK;你直接左上角File->Settings->File Encodings然后按如下设置即可。

以上就是application.properties配置文件的使用情况;另外有兴趣的同学还可以去试试八大类型的其他几种类型;bug菌此处就不一一赘述啦。提示:都跟上边是一样的玩法,唯独就是写法不太一样。

拓展:

1
2
3
4
5
6
7
8
9
10
11
12
yaml复制代码#多行写法
student:
name: "Li Ming"
age: 18

#多行写法
person:
- man
- women

#单行的行内写法
person: [man,women]

三、application.yaml使用

语法与.properties稍微不太一样;[key: value];

1
2
3
4
yaml复制代码user:
userName: 刘亦菲
sex: 女
age: 20

注意:yml默认可以不写引号,“”(双引号)会将其中的转义符进行转义,其他的(单引号啥的)不会。

测试一下:http://localhost:8080/test/hello1

使用yml需要注意一下几点:

  • 不同“等级” 用冒号隔开
  • 次等级的前面是空格,不能使用制表符(tab)
  • 冒号之后如果有值,那么冒号和值之间至少有一个空格,避免紧贴
  • 要么用application.properties 要么用 application.yml,不要都用使自己混乱

拓展:例如:对象写法+数组写法等复合型语法。感兴趣的同学可以尝试看看。

1
2
3
4
5
6
7
8
9
10
11
12
yaml复制代码person:
name: zhangsan
age: 30
pets:
-dog
-cat
-pig
car:
name: QQ
child:
name: zhangxiaosan
age: 2

四、两种配置文件如何选择

1、当properties和yml同时存在时,properties的优先级会比yml高(记忆技巧:这个世界上,谁长,谁NB)。

2、两种文件都是用#注释。

3、yml的优点在于可以省去一下重复代码。

4、在properties文件里面的 “ .” 连接在yml文件里面全部换成 “:” 进行连接,并且每一级之间必须换行,在第二级开始应该进行一个Tab键的缩进,如果是同级的就不需要进行缩进。

5、在yml文件里面如果是需要进行赋值那么必须是要在 “:” 后面进行一个空格键的缩进。

6、在yml文件里面所有的配置,相同级别只能出现一次,比如我们使用了spring这个级别,那么我们在后边进行spring级别的配置的时候就必须在这个地方进行,不能在写一个spring级别。

… …

至于如何选择,想必你们心中都有答案了吧!

五、热文推荐

OK,以上就是这期所有的内容啦,如果有任何问题欢迎评论区批评指正,咱们下期见。

  • springboot系列(一):如何创建springboot项目及启动
  • springboot系列(二):yaml、properties两配置文件介绍及使用
  • springboot系列(三):多环境切换,实例演示
  • springboot系列(四):stater入门
  • springboot系列(五):史上最最最全springboot常用注解
  • springboot系列(六):mysql配置及数据库查询
  • springboot系列(七):如何通过mybatis-plus实现接口增删改查
  • springboot系列(八):mybatis-plus之条件构造器使用手册
  • springboot系列(九):mybatis-plus之如何自定义sql
  • springboot系列(十):mybatis之xml映射文件>、<=等特殊符号写法
  • springboot系列(十一):实现多数据源配置,开箱即用
  • springboot系列(十二):如何实现邮件发送提醒,你一定得会(准备篇)
  • springboot系列(十三):如何实现发送普通邮件?你一定得会
  • springboot系列(十四):如何实现发送图片、doc文档等附件邮件?你一定得会
  • springboot系列(十五):如何实现静态邮件模板发送?你一定得会
  • springboot系列(十六):如何实现发送邮件提醒,附完整源码
  • springboot系列(十七):集成在线接口文档Swagger2
  • springboot系列(十八):如何Windows安装redis?你玩过么
  • springboot系列(十九):如何集成redis?不会我教你
  • springboot系列(二十):如何通过redis实现手机号验证码功能
  • … …

文末🔥

如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《springboot零基础入门教学》,从无到有,从零到一!希望能帮助到更多小伙伴们。

我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!

感谢认真读完我博客的铁子萌,在这里呢送给大家一句话,不管你是在职还是在读,绝对终身受用。

时刻警醒自己:

抱怨没有用,一切靠自己;

想要过更好的生活,那就要逼着自己变的更强,生活加油!!!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【算法攻坚】快慢指针解法

发表于 2021-11-20

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

今日题目

给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

img

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

进阶:尝试使用一趟扫描实现吗?

思路一

由于链表只有头节点暴露,所以不清楚到底链表长度是多少,也就不清楚第n个节点在哪,

所以我们可以使用可以保留顺序索引的容器存储,这样就可以根据索引直接删掉倒数第n个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode cuur = head;
List<ListNode> nodelist = new ArrayList<>();
while (cuur != null) {
nodelist.add(cuur);
cuur = cuur.next;
}
int len = nodelist.size();
//只有一个元素时,返回空
if (len == 1 && n == 1) {
return null;
}
//删除掉倒数第n个,也就是正数第一个head节点
if (len == n) {
return nodelist.get(1);
}
//其他情况删除节点,返回head
nodelist.get(len - n - 1).next = nodelist.get(len - n).next;
return head;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:37.7 MB, 在所有 Java 提交中击败了5.03%的用户

思路二

另一种思路就是遍历两遍,第一遍找到总长度,第二遍根据总长度和n找到需要删除的节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码public ListNode removeNthFromEnd2(ListNode head, int n) {
ListNode cuur = head;
int length = 1;
//第一遍计算出总长度
while (cuur.next != null) {
length++;
cuur = cuur.next;
}
if (n == length) {
head = head.next;
} else {
cuur = head;
//第二遍删除第n个节点
for (int i = 1; i < length - n; i++) {
cuur = cuur.next;
}
cuur.next = cuur.next.next;
}
return head;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:36.4 MB, 在所有 Java 提交中击败了58.58%的用户

思路三

利用快慢指针,由于是删除倒数第n个,让快指针先走n - 1步,然后慢指针再从头出发,与快指针同时前进。当快指针走到结尾的时候,slow.next就是要删除的节点。 执行slow.next = slow.next.next即可。小技巧还是通过创建哨兵节点来简化头节点的处理方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public ListNode removeNthFromEnd(ListNode head, int n) {
//新建哨兵节点,方便处理头节点
ListNode pre = new ListNode();
pre.next = head;
ListNode fast = pre, slow = pre;
for (int i = 0; i < n; i++){
fast = fast.next;
}
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
//删除slow.next
slow.next = slow.next.next;
return pre.next;
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:36.3 MB, 在所有 Java 提交中击败了76.05%的用户

小结

这道题重点要学会快慢指针的思想,其他解法相对来说不是重点只作为了解即可,算法考察的更多是快慢指针的思想,需要掌握其原理。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python的线程01 认识线程 什么是线程? 再来解释线程

发表于 2021-11-20

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」

正式的Python专栏第41篇,同学站住,别错过这个从0开始的文章!

前面文章写了很多,都是比较基础的。

这篇开始我们将进入中级编程。处理更加复杂事情。比如本文的线程,咱们先从基础知识入手!

什么是线程?

我们知道工人都是同时在工厂工作,复制各自的工作的。他们就是一个一个独立运行的单位!

线程也是类似这样的一个独立的运行单位,多线程,就是多个独立的运行单位,同时执行同样的事情。

简单这样理解,后面会进行对比。

threading.Thread 类是Python中的线程类,它封装了线程的信息和一些常用的方法,比如启动线程/查看线程状态。

线程有状态,拿工人一天的状态来比喻很合适,早上上班,然后工作,有时候需要停下来休息,最后下班。

复制运行下面的代码看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
python复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/21 12:02 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : __init__.py.py
# @Project : hello

import threading

mythread = threading.Thread()
print("mythread:", mythread)
print("is_alive:", mythread.is_alive())
mythread.start()
print("mythread:", mythread)
print("is_alive:", mythread.is_alive())

下面是运行结果:

屏幕快照 2021-11-21 上午12.09.32.png

建议读者先运行一下。

再来解释线程的代码

上面我们使用了threading这个库,然后创建Thread类的对象实例,赋值给mythread变量。

接着打印了对象和线程对象的一个函数is_alive()是否活跃状态。

两次都是False(这个后面再说)

但是第二次我们看到线程对象打印出来变成‘stopped’.

也就是说我们跑完了start函数(该函数为线程启动函数)之后,线程就进入stopped状态了。

上面那个就是线程,可是貌似啥也没做,我们下面让它做点事情呗。

线程触发业务函数,线程调用业务函数

比如这次的业务是:关注和点赞。

1
2
3
4
5
6
7
8
python复制代码def dianzan_guanzhu():
now = datetime.datetime.now() #初始化时间变量
name = "python萌新"
print("%s name:%s" % (now, name)) #第一次打印时间和粉丝名字
time.sleep(1)
result = "好棒!" + name + " 关注雷学委,学到了好多知识和开发经验!"
print("%s result:%s" % (now, result)) #第二次打印时间和粉丝活动
return result

我们可以使用线程来调用。下面学委写了一个带参数的函数。 通过线程调用业务函数的时候指定:

  • target:设置为即将被调用的函数
  • kwargs: 如果有参数,直接通过传递一个k-v dict即可。
1
2
3
4
python复制代码def dianzan_guanzhu(name):
#省略一些代码

mythread = threading.Thread(target=dianzan_guanzhu, kwargs={"name": "python萌新"})

好下面,编写全部代码,使用线程来点赞,和直接调用点赞。

我们看看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
python复制代码#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/21 12:02 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : __init__.py.py
# @Project : hello

import threading
import datetime
import time

"""学委定义了一个关注函数"""
def dianzan_guanzhu():
now = datetime.datetime.now()
name = "python萌新"
print("%s name:%s" % (now, name))
time.sleep(1)
result = "好棒!" + name + " 关注雷学委,白嫖了好多知识和开发经验!"
print("%s result:%s" % (now, result))
return result


mythread = threading.Thread(target=dianzan_guanzhu)
print("mythread:", mythread)
print("is_alive:", mythread.is_alive())
mythread.start()
print("mythread:", mythread)
print("is_alive:", mythread.is_alive())
dianzan_guanzhu()
print("is_alive:", mythread.is_alive())

直接复制运行,这里我们这个dianzan_guanzhu函数被调用了两次

第一次是mythread.start函数。

第二次是我们直接脱离线程调用dianzan_guanzhu函数。

下面是运行结果:

屏幕快照 2021-11-21 上午11.32.00.png

好像没啥的样子。

再看一次,注意关注每次打印的时间,输入的时间好像错乱了?没错,不是眼花,是正确运行结果。

因为进入dianzan_guanzhu函数之后,初始化了now变量,这个时间固定了。

但是在线程外面也调用dianzan_guanzhu函数,所以这里是:两个线程在同时做同样的事情。

多了一个线程是哪个?

这里补充一下,我们写python脚本,运行代码的时候,本身是在一个主线程中的。

只是之前一直没解除线程概念,没写多线程程序,没有感知到这事情。

从现在开始,你要清楚知道:每个程序运行都有一个主线程。

回到结果,两个线程先后依次调用通过函数:

首先,先后依次打印第一行输出。

分开休眠了一秒(sleep(1))。

最后,先后依次打印第二行输出。

总结

我们先把线程的基础知识搞懂。

  • 每个程序启动至少是有一个主线程
  • 需要启动更多线程使用Thread类来做

下一节再分享线程的更多知识。

对了,喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏

持续学习持续开发,我是雷学委!

编程很有趣,关键是把技术搞透彻讲明白。

欢迎关注微信,点赞支持收藏!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Cloud / Alibaba 微服务架构

发表于 2021-11-20

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战。

上篇文章给大家介绍了SpringCloud Gateway 的工作模型及请求进入到我们的SpringCloud Gateway后,最终到达Proxied Service 目标服务经过的四个阶段,本篇文章将介绍一下Predicate(谓词/断言)的原理与应用。

谓词 Predicate 的原理与应用

之前的文章中有介绍过Predicate,它是Gateway的三大组成部分之一,是对条件匹配的判断。

谓词Predicate是什么

认识Predicate

由Java8引入,位于java.util.function包中,是一个FunctionalInterface,即函数式接口。它只有一个test方法没有实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@FunctionalInterface
public interface Predicate<T> {

/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
...
}

可以看到,test方法需要输入一个参数,返回boolean类型,通常用在stream的filter中,表示是否满足过滤条件。

我们先创建一个网关子模块,在里面写一些例子的测试代码让我们熟悉Predicate的方法,介绍Predicate的应用方法和应用过程。

创建网关子模块(e-commerce-gateway)来编写例子

1、创建子模块、修改pom文件等步骤不再赘述。

此处有一点需要注意,e-commerce-gateway之所以不像其他子模块一样在依赖里引入e-commerce-mvc-config,是因为我们的e-commerce-mvc-config依赖了starter-web,starter-web里依赖了starter-tomcat,它是由web去引用进来的。gateway子模块使用的是WebFlux,默认使用的是Netty,所以我们需要从依赖中排除tomcat相关的依赖,所以不能引入spring-boot-starter-web这个依赖。

2、resources下创建配置类bootstrap.yml

3、创建启动类

1
2
3
4
5
6
7
8
9
10
11
12
less复制代码/**
* <h1>网关启动入口</h1>
* */
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {

public static void main(String[] args) {

SpringApplication.run(GatewayApplication.class, args);
}
}

4、创建测试类验证工程搭建正确性

image.png

Java8 Predicate test方法使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
less复制代码/**
* <h1>Java8 Predicate 使用方法与思想</h1>
* */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class PredicateTest {

public static List<String> MICRO_SERVICE = Arrays.asList(
"nacos", "authority", "gateway", "ribbon", "feign", "hystrix", "e-commerce"
);

/**
* <h2>test 方法主要用于参数符不符合规则, 返回值是 boolean</h2>
* */
@Test
public void testPredicateTest() {

Predicate<String> letterLengthLimit = s -> s.length() > 5;
MICRO_SERVICE.stream().filter(letterLengthLimit).forEach(System.out::println);
}
}

test 方法主要用于判断参数符不符合规则, 返回值是 boolean。

上图代码指的是依次输出MICRO_SERVICE中字符串长度大于5的。

下篇文章我们将介绍一下Predicate中and、negate、or、isEqual方法。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Web打印插件实现思路(C#/Winform)

发表于 2021-11-20

最近,客户有个需求过来,Web端无预览打印,美其名曰:快捷打印。

当时第一反应就是找插件,拿来主义永远不过时。找了一圈发现,免费的有限制,没限制的需要收费(LODOP真的好用)。说来就是一个简单的无预览打印,收费的诸多功能都无用武之地,总的来说性价比很低,所以就打算自己琢磨着写一个算了。刚开始总是纠结在Web端去实现直接打印,打印是实现了,结果是服务端的,客户端只能一脸懵。

后来(准备妥协了),安装了一些收费的插件发现,都需要先安装一个客户端程序,灵光乍现,解决方案这不就出来了。

思路:Web端调用客户端程序,让客户端程序去实现打印。

实现:Web端通过WebSocket将Html发送给客户端,客户端(Winform)监听消息,接收处理后再打印。

客户端(Winform)打印实现方式:

1、Html转PDF,然后打印PDF(暂时需要用到三方包)

2、Html转图片,然后通过PrintDocument打印图片

3、通过WebBrowser实现打印

按照上面的思路再去写代码,就会简单很多。

Web端的代码就不多说了,一个简单的WebSocket通信。

客户端程序(Winform)

1、监听Web端的WebSocket消息,这里有用到三方包:Fleck,开箱即用,非常方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
csharp复制代码 1         /// <summary>
2 /// ConnAndListen
3 /// </summary>
4 public static void ConnAndListen()
5 { 7 // 监听本地 45000端口
8 var server = new WebSocketServer($"ws://127.0.0.1:45000");
9 server.Start(socket =>
10 {
11 socket.OnOpen = () =>
12 {
13 // 连接成功
14 socket.Send("socket通讯已连接");
15 };
16 socket.OnClose = () =>
17 {
18 // 关闭连接
19 socket.Send("socket通讯已关闭连接");
20 socket.Close();
21 };
22 socket.OnMessage = message =>
23 {
24 // TODO...
25 };
26 });
27 }

2、处理Html,将Html转换为图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
csharp复制代码 1         /// <summary>
2 /// GetBitmap
3 /// </summary>
4 /// <returns></returns>
5 public Bitmap GetBitmap()
6 {
7 WebPageBitmap Shot = new WebPageBitmap("html", "页面宽度", "页面高度");
8 Shot.GetIt();
9 Bitmap Pic = Shot.DrawBitmap("图片高度", "图片宽度");
10 // 设置图片分辨率
11 Pic.SetResolution(203.0F, 203.0F);
12 return Pic;
13 }

3、打印图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
ini复制代码  1     /// <summary>
2 /// 图片打印
3 /// </summary>
4 public class PrintDirectClass
5 {
6 private PaperSize paperSize = null;
7 // 多页打印
8 private int printNum = 0;
9 // 单个图片文件
10 private string imageFile = ".....";
11 // 多个图片文件
12 private ArrayList imageList = new ArrayList();
13
14 /// <summary>
15 /// 打印预览
16 /// </summary>
17 public void PrintPreview()
18 {
19 // 打印机对象
20 PrintDocument imgToPrint = new PrintDocument();
21
22 #region 打印机相关设置
23 var pageSize = imgToPrint.PrinterSettings.PaperSizes;
24 paperSize = pageSize[pageSize.Count - 1];
25
26 // 打印方向设置
27 //imgToPrint.DefaultPageSettings.Landscape = false;
28 // 打印纸张大小设置
29 //imgToPrint.DefaultPageSettings.PaperSize = paperSize;
30 // 打印分辨率设置
31 //imgToPrint.DefaultPageSettings.PrinterResolution.Kind = PrinterResolutionKind.High;
32 // 打印边距设置
33 //imgToPrint.DefaultPageSettings.Margins = new Margins(40, 40, 40, 40);
34
35 // 打印开始事件
36 imgToPrint.BeginPrint += new PrintEventHandler(this.imgToPrint_BeginPrint);
37 // 打印结束事件
38 imgToPrint.EndPrint += new PrintEventHandler(this.imgToPrint_EndPrint);
39 // 打印内容设置
40 imgToPrint.PrintPage += new PrintPageEventHandler(this.imgToPrint_PrintPage);
41 #endregion
42
43 // 直接打印
44 //imgToPrint.Print();
45
46 // 打印弹框确认
47 //PrintDialog printDialog = new PrintDialog();
48 //printDialog.AllowSomePages = true;
49 //printDialog.ShowHelp = true;
50 //printDialog.Document = imgToPrint;
51 //if (printDialog.ShowDialog() == DialogResult.OK)
52 //{
53 // imgToPrint.Print();
54 //}
55
56 // 预览打印
57 //PrintPreviewDialog pvDialog = new PrintPreviewDialog();
58 //pvDialog.Document = imgToPrint;
59 //pvDialog.ShowDialog();
60 }
61
62 /// <summary>
63 /// 打印开始事件
64 /// </summary>
65 /// <param name="sender"></param>
66 /// <param name="e"></param>
67 private void imgToPrint_BeginPrint(object sender, PrintEventArgs e)
68 {
69 if (imageList.Count == 0)
70 imageList.Add(imageFile);
71 }
72
73 /// <summary>
74 /// 打印结束事件
75 /// </summary>
76 /// <param name="sender"></param>
77 /// <param name="e"></param>
78 private void imgToPrint_EndPrint(object sender, PrintEventArgs e)
79 {
80
81 }
82
83 /// <summary>
84 /// 设置打印内容
85 /// </summary>
86 /// <param name="sender"></param>
87 /// <param name="e"></param>
88 private void imgToPrint_PrintPage(object sender, PrintPageEventArgs e)
89 {
90 // 图片文本质量
91 e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
92 // 图片插值质量
93 e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
94 // 图片合成质量
95 e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
96 // 图片抗锯齿
97 e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
98 // 设置缩放比例
99 e.Graphics.PageScale = 0.3F;
100 using (Stream fs = new FileStream(imageList[printNum].ToString().Trim(), FileMode.Open, FileAccess.Read))
101 {
102 System.Drawing.Image image = System.Drawing.Image.FromStream(fs);
103 int w = image.Width;
104 int h = image.Height;
105 // 绘制Image
106 e.Graphics.DrawImage(image, 40, 40, 410, 600);
107 if (printNum < imageList.Count - 1)
108 {
109 printNum++;
110 // HasMorePages为true则再次运行PrintPage事件
111 e.HasMorePages = true;
112 return;
113 }
114 e.HasMorePages = false;
115 }
116 // 设置打印内容的边距
117 //e.PageSettings.Margins = new Margins(40, 40, 40, 40);
118 // 设置是否横向打印
119 e.PageSettings.Landscape = false;
120 // 设置纸张大小
121 e.PageSettings.PaperSize = paperSize;
122 // 设置打印分辨率
123 e.PageSettings.PrinterResolution.Kind = PrinterResolutionKind.High;
124 }
125 }

以上就是 Html转图片,然后通过PrintDocument打印图片 的实现代码了。其它两种方式大家有兴趣可以去试试。

WebBrowser 比较简单,但是对打印机控制不是特别友好(也可能是我没有发现,研究不深)

1
2
3
4
5
6
7
scss复制代码1             WebBrowser wb = new WebBrowser();
2 // 直接打印
3 wb.Print();
4 // 确认打印
5 wb.ShowPrintDialog();
6 // 预览打印
7 wb.ShowPrintPreviewDialog();

Html转PDF,然后打印PDF原理于Html转图片,然后通过PrintDocument打印图片一样,不同的是转PDF可能需要用到第三方的包,目前这方便的包还是比较齐全的,NUGET搜索pdf几乎全是。

1539844-20210615141717731-565068138.png

需求总是来得特别突然,下次再有新特别需求时再来和大家分享。

以上就是这篇文章的全部内容了,有帮助的点个赞,有错误的欢迎大家指出来,有更好的解决方案也希望大家不吝赐教。

无绪分享

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

迭代器模式

发表于 2021-11-20

这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

概述

定义:

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

结构

迭代器模式主要包含以下角色:

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

案例实现

【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:

代码如下:

定义迭代器接口,声明hasNext、next方法

1
2
3
4
java复制代码public interface StudentIterator {
boolean hasNext();
Student next();
}

定义具体的迭代器类,重写所有的抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码public class StudentIteratorImpl implements StudentIterator {
private List<Student> list;
private int position = 0;

public StudentIteratorImpl(List<Student> list) {
this.list = list;
}

@Override
public boolean hasNext() {
return position < list.size();
}

@Override
public Student next() {
Student currentStudent = list.get(position);
position ++;
return currentStudent;
}
}

定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法

1
2
3
4
5
6
7
java复制代码public interface StudentAggregate {
void addStudent(Student student);

void removeStudent(Student student);

StudentIterator getStudentIterator();
}

定义具体的容器类,重写所有的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码public class StudentAggregateImpl implements StudentAggregate {

private List<Student> list = new ArrayList<Student>(); // 学生列表

@Override
public void addStudent(Student student) {
this.list.add(student);
}

@Override
public void removeStudent(Student student) {
this.list.remove(student);
}

@Override
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}

优缺点

1,优点:

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。

2,缺点:

增加了类的个数,这在一定程度上增加了系统的复杂性。

使用场景

  • 当需要为聚合对象提供多种遍历方式时。
  • 当需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

JDK源码解析

迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。

1
2
3
4
5
java复制代码List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象
while (iterator.hasNext()) {
System.out.println(iterator.next());
}

看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明

  • List:抽象聚合类
  • ArrayList:具体的聚合类
  • Iterator:抽象迭代器
  • list.iterator():返回的是实现了 Iterator 接口的具体迭代器对象

具体的来看看 ArrayList的代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

public Iterator<E> iterator() {
return new Itr();
}

private class Itr implements Iterator<E> {
int cursor; // 下一个要返回元素的索引
int lastRet = -1; // 上一个返回元素的索引
int expectedModCount = modCount;

Itr() {}

//判断是否还有元素
public boolean hasNext() {
return cursor != size;
}

//获取下一个元素
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
...
}

这部分代码还是比较简单,大致就是在 iterator 方法中返回了一个实例化的 Iterator 对象。Itr是一个内部类,它实现了 Iterator 接口并重写了其中的抽象方法。

注意:

​ 当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable并实现其中的iterator()方法使其返回一个 java.util.Iterator 的实现类就可以了。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Spark Streming】DStream转换操作

发表于 2021-11-20

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

一、概述

DStream 上的操作与 RDD 的类似, 分为 Transformations(转换) 和 Output Operations(输出)两种, 此外转换操作中还有一些比较特殊的方法, 如: updateStateByKey、transform 以及各种 Window 相关的操作。

备注:

  • 在 DStream 与 RDD 上的转换操作非常类似(无状态的操作)
  • DStream 有自己特殊的操作(窗口操作、追踪状态变化操作)
  • 在 DStream 上的转换操作比 RDD 上的转换操作少

DStream 的转化操作可以分为 无状态(stateless) 和 有状态(stateful) 两种:

  • 无状态转化操作。每个批次的处理不依赖于之前批次的数据。常见的 RDD 转化操作, 例如 map、filter、reduceByKey 等
  • 有状态转化操作。需要使用之前批次的数据 或者是 中间结果来计算当前批次的数据。有状态转化操作包括: 基于滑动窗口的转化操作 或 追踪状态变化的转化操作

二、无状态转换

无状态转化操作就是把简单的 RDD 转化操作应用到每个批次上, 也就是转化 DStream 中的每一个 RDD。

常见的无状态转换包括: map、flatMap、filter、repartition、reduceByKey、groupByKey;

直接作用在 DStream 上。

案例:黑名单过滤

假设: arr1 为黑名单数据(自定义), true 表示数据生效, 需要被过滤掉; false 表示数据未生效
val arr1 = Array(("spark", true), ("scala", false))

假设: 流式数据格式为 “time word”, 需要根据黑名单中的数据对流式数据执行过滤操作。如 “2 spark” 要被过滤掉

1
2
3
4
5
6
arduino复制代码1 hadoop
2 spark
3 scala
4 java
5 hive
// 结果:"2 spark" 被过滤

方法有三:

  1. 方法一: 使用外连接
  2. 方法二: 使用 SQL
  3. 方法三: 直接过滤

1) 方法一:使用外链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
scala复制代码import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.ConstantInputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
* @author donald
* @date 2021/02/23
*/
object BlackListFilter1 {
def main(args: Array[String]) {
// 初始化
val conf = new
SparkConf().setAppName(this.getClass.getCanonicalName).setMaster(
"local[2]")
val ssc = new StreamingContext(conf, Seconds(10))
ssc.sparkContext.setLogLevel("WARN")
// 黑名单数据
val blackList = Array(("spark", true), ("scala", true))
val blackListRDD = ssc.sparkContext.makeRDD(blackList)
// 生成测试DStream。使用ConstantInputDStream
val strArray: Array[String] = ("spark java scala hadoop kafka " +
"hive hbase zookeeper")
.split("\\s+")
.zipWithIndex
.map { case (word, idx) => s"$idx $word" }
val rdd = ssc.sparkContext.makeRDD(strArray)
val clickStream = new ConstantInputDStream(ssc, rdd)
// 流式数据的处理
val clickStreamFormatted = clickStream.map(value =>
(value.split(" ")(1), value))
clickStreamFormatted.transform(clickRDD => {
// 通过leftOuterJoin操作既保留了左侧RDD的所有内容,又获得了内容是否在黑名单中
val joinedBlackListRDD: RDD[(String, (String, Option[Boolean]))] = clickRDD.leftOuterJoin(blackListRDD)
joinedBlackListRDD.filter { case (word, (streamingLine, flag)) =>
if (flag.getOrElse(false)) false
else true
}.map { case (word, (streamingLine, flag)) => streamingLine
}
}).print()

// 启动流式作业
ssc.start()
ssc.awaitTermination()
}
}

2)方法二:使用 SQL

3)方法三:直接过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
scala复制代码import org.apache.spark.SparkConf
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.streaming.dstream.ConstantInputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
* @author donald
* @date 2021/02/23
*/
object BlackListFilter3 {
def main(args: Array[String]) {
// 初始化
val conf = new
SparkConf().setAppName(this.getClass.getCanonicalName).setMaster(
"local[2]")
val ssc = new StreamingContext(conf, Seconds(10))
ssc.sparkContext.setLogLevel("WARN")
// 黑名单数据
val blackList = Array(("spark", true), ("scala", true))
val blackListBC: Broadcast[Array[String]] =
ssc.sparkContext.broadcast(blackList.filter(_._2).map(_._1))
// 生成测试DStream。使用ConstantInputDStream
val strArray: Array[String] = "spark java scala hadoop kafka hive hbase zookeeper"
.split("\\s+")
.zipWithIndex
.map { case (word, idx) => s"$idx $word" }
val rdd = ssc.sparkContext.makeRDD(strArray)
val clickStream = new ConstantInputDStream(ssc, rdd)
// 流式数据的处理
clickStream.map(value => (value.split(" ")(1), value))
.filter { case (word, _) =>
!blackListBC.value.contains(word)
}
.map(_._2)
.print()
// 启动流式作业
ssc.start()
ssc.awaitTermination()
}
}

三、有状态转换

有状态的转换主要有两种: 窗口操作、状态跟踪操作

(1)窗口操作

Window Operations 可以设置窗口大小和滑动窗口间隔来动态的获取当前 Streaming 的状态。

基于窗口的操作会在一个比 StreamingContext 的 batchDuration(批次间隔)更长的时间范围内, 通过整合多个批次的结果, 计算出整个窗口的结果。

如图:

基于窗口的操作需要两个参数:

  • 窗口长度(windowDuration)。控制每次计算最近的多少个批次的数据
  • 滑动间隔(slideDuration)。用来控制对新的 DStream 进行计算的间隔

两者都必须是 StreamingContext 中批次间隔(batchDuration)的整数倍。

每秒发送1个数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
scala复制代码import java.io.PrintWriter
import java.net.{ServerSocket, Socket}

/**
* @author donald
* @date 2021/02/23
*/
object SocketLikeNCWithWindow {
def main(args: Array[String]): Unit = {
val port = 1521
val ss = new ServerSocket(port)
val socket: Socket = ss.accept()
println("connect to host : " + socket.getInetAddress)
var i = 0
// 每秒发送1个数
while(true) {
i += 1
val out = new PrintWriter(socket.getOutputStream)
out.println(i)
out.flush()
Thread.sleep(1000)
}
}
}

1)举个栗子一

  • 观察窗口的数据;
  • 观察 batchDuration、windowDuration、slideDuration 三者之间的关系;
  • 使用窗口相关的操作;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
scala复制代码import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
* @author donald
* @date 2021/02/23
*/
object WindowDemo {
def main(args: Array[String]): Unit = {
val conf = new
SparkConf().setMaster("local[*]").setAppName(this.getClass.getCanonicalName)
// 每 5s 生成一个RDD(mini-batch)
val ssc = new StreamingContext(conf, Seconds(5))
ssc.sparkContext.setLogLevel("error")
val lines: ReceiverInputDStream[String] =
ssc.socketTextStream("localhost", 1521)
lines.foreachRDD{ (rdd, time) =>
println(s"rdd = ${rdd.id}; time = $time")
rdd.foreach(value => println(value))
}
// 20s 窗口长度(ds包含窗口长度范围内的数据);10s 滑动间隔(多次时间处理一次数据)
val res1: DStream[String] = lines.reduceByWindow(_ + " " + _,
Seconds(20), Seconds(10))
res1.print()
val res2: DStream[String] = lines.window(Seconds(20),
Seconds(10))
res2.print()
// 求窗口元素的和
val res3: DStream[Int] =
lines.map(_.toInt).reduceByWindow(_+_, Seconds(20), Seconds(10))
res3.print()
// 求窗口元素的和
val res4 = res2.map(_.toInt).reduce(_+_)
res4.print()
ssc.start()
ssc.awaitTermination()
}
}

2)举个栗子二

热点搜索词实时统计:每隔 10 秒, 统计最近20秒的词出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
scala复制代码import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
* @author donald
* @date 2021/02/23
*/
object HotWordStats {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setMaster("local[2]")
.setAppName(this.getClass.getCanonicalName)
val ssc = new StreamingContext(conf, Seconds(2))
ssc.sparkContext.setLogLevel("ERROR")
//设置检查点,检查点具有容错机制。生产环境中应设置到HDFS
ssc.checkpoint("data/checkpoint/")
val lines: ReceiverInputDStream[String] =
ssc.socketTextStream("localhost", 9999)
val words: DStream[String] = lines.flatMap(_.split("\\s+"))
val pairs: DStream[(String, Int)] = words.map(x => (x, 1))
// 通过reduceByKeyAndWindow算子, 每隔10秒统计最近20秒的词出现的次数
// 后 3个参数:窗口时间长度、滑动窗口时间、分区
val wordCounts1: DStream[(String, Int)] =
pairs.reduceByKeyAndWindow(
(a: Int, b: Int) => a + b,
Seconds(20),
Seconds(10), 2)
wordCounts1.print
// 这里需要checkpoint的支持
val wordCounts2: DStream[(String, Int)] =
pairs.reduceByKeyAndWindow(
_ + _,
_ - _,
Seconds(20),
Seconds(10), 2)
wordCounts2.print
ssc.start()
ssc.awaitTermination()
}
}

(2)updateStateByKey 状态跟踪操作

UpdateStateByKey 的主要功能:

  • 为 Streaming 中每一个 Key 维护一份 state 状态, state 类型可以是任意类型的, 可以是自定义对象; 更新函数也可以是自定义的。
  • 通过更新函数对该 key 的状态不断更新, 对于每个新的 batch 而言, Spark Streaming 会在使用 updateStateByKey 的时候为已经存在的 key 进行 state 的状态更新
  • 使用 updateStateByKey 时要开启 checkpoint 功能

流式程序启动后计算 wordcount 的累计值, 将每个批次的结果保存到文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
scala复制代码import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
* @author donald
* @date 2021/02/23
*/
object StateTracker1 {
def main(args: Array[String]) {
val conf: SparkConf = new SparkConf().setMaster("local[2]")
.setAppName(this.getClass.getCanonicalName)
val ssc = new StreamingContext(conf, Seconds(5))
ssc.sparkContext.setLogLevel("ERROR")
ssc.checkpoint("data/checkpoint/")
val lines: ReceiverInputDStream[String] =
ssc.socketTextStream("localhost", 9999)
val words: DStream[String] = lines.flatMap(_.split("\\s+"))
val wordDstream: DStream[(String, Int)] = words.map(x => (x,
1))
// 定义状态更新函数
// 函数常量定义,返回类型是Some(Int),表示的含义是最新状态
// 函数的功能是将当前时间间隔内产生的Key的value集合,加到上一个状态中, 得到最新状态
val updateFunc = (currValues: Seq[Int], prevValueState:
Option[Int]) => {
//通过Spark内部的reduceByKey按key规约,然后这里传入某key当前批次的 Seq,再计算当前批次的总和
val currentCount = currValues.sum
// 已累加的值
val previousCount = prevValueState.getOrElse(0)
Some(currentCount + previousCount)
}
val stateDstream: DStream[(String, Int)] =
wordDstream.updateStateByKey[Int](updateFunc)
stateDstream.print()
// 把DStream保存到文本文件中,会生成很多的小文件。一个批次生成一个目录
val outputDir = "data/output1"
stateDstream.repartition(1)
.saveAsTextFiles(outputDir)
ssc.start()
ssc.awaitTermination()
}
}

统计全局的 key 的状态, 但是就算没有数据输入, 也会在每一个批次的时候返回之前的 key 的状态。

这样的缺点: 如果数据量很大的话, checkpoint 数据会占用较大的存储, 而且效率也不高。

mapWithState : 也是用于全局统计 key 的状态。如果没有数据输入, 便不会返回之前的 key 的状态, 有一点增量的感觉。

这样做的好处是, 只关心那些已经发生的变化的 key, 对于没有数据输入, 则不会返回那些没有变化的 key 的数据。即使数据量很大, checkpoint 也不会像 updateStateByKey 那样, 占用太多的存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
scala复制代码import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.{Seconds, State, StateSpec, StreamingContext}

/**
* @author donald
* @date 2021/02/23
*/
object StateTracker2 {
def main(args: Array[String]) {
val conf: SparkConf = new SparkConf()
.setMaster("local[*]")
.setAppName(this.getClass.getCanonicalName)
val ssc = new StreamingContext(conf, Seconds(2))
ssc.sparkContext.setLogLevel("ERROR")
ssc.checkpoint("data/checkpoint/")
val lines: ReceiverInputDStream[String] =
ssc.socketTextStream("localhost", 9999)
val words: DStream[String] = lines.flatMap(_.split("\\s+"))
val wordDStream: DStream[(String, Int)] = words.map(x => (x, 1))
// 函数返回的类型即为 mapWithState 的返回类型
// (KeyType, Option[ValueType], State[StateType]) => MappedType
def mappingFunction(key: String, one: Option[Int], state:
State[Int]): (String, Int) = {
val sum: Int = one.getOrElse(0) +
state.getOption.getOrElse(0)
state.update(sum)
(key, sum)
}
val spec = StateSpec.function(mappingFunction _)
val resultDStream: DStream[(String, Int)] =
wordDStream.mapWithState[Int, (String, Int)](spec)
resultDStream.cache()
// 把DStream保存到文本文件中,会生成很多的小文件。一个批次生成一个目录
val outputDir = "data/output2/"
resultDStream.repartition(1)
.saveAsTextFiles(outputDir)
ssc.start()
ssc.awaitTermination()
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

TypeScript 设计模式15 - 迭代器模式

发表于 2021-11-20

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

迭代器模式

提供一个对象来顺序访问聚合对象中的一序列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式。

模式结构

  • 抽象迭代器:接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。
  • 具体迭代器:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
  • 抽象聚合:接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。
  • 具体聚合: 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
typescript复制代码// main函数
(()=>{
const con = new ConcreteAggregate();
con.add("first");
con.add("second");
con.add("third");
​
const iterator = con.getIterator();
if(iterator.hasNext()) {
  console.log(iterator);
  con.remove(iterator);
}
​
})()
//抽象聚合
interface Aggregate {
    add(obj: any):void;
    remove(obj: any): void;
    getIterator(): Iterator<string>;
}
//具体聚合
class ConcreteAggregate implements Aggregate {
   private list: any[] = [];
     add( obj: any) {
       this.list.push(obj);
  }
    remove( obj: any) {
      let newList:any[] = []
       this.list.forEach((value)=>{
         if(value != obj){
           newList.push(value)
        }
      })
       this.list = newList;
  }
   public  getIterator(): Iterator<string> {
       return (new ConcreteIterator(this.list));
  }
}
//抽象迭代器
interface Iterator<T> {
    first():T;
    next(): T;
    hasNext():boolean;
}
//具体迭代器
class ConcreteIterator implements Iterator<string> {
   private list: any = null;
   private index = -1;
   constructor(list:any){
     this.list = list;
  }
​
   public  hasNext():boolean {
       if (this.index < this.list.length - 1) {
           return true;
      } else {
           return false;
      }
  }
   public  first() {
       this.index = 0;
       const obj = this.list.get(this.index);
      ;
       return obj;
  }
   public  next() {
      let obj:any = null;
       if (this.hasNext()) {
           obj = this.list.get(++this.index);
      }
       return obj;
  }
}

主要优点

  • 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
  • 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  • 封装性良好,为遍历不同的聚合结构提供一个统一的接口。

适用场景

  • 当集合背后为复杂的数据结构,且你希望对客户端隐藏其复杂性时,可使用迭代器模式,即当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
  • 使用该模式可以减少程序中重复的遍历代码,通过此模式可为聚合对象提供多种遍历方式时。
  • 若希望代码能够遍历不同的甚至是无法预知的数据结构,可使用迭代器模式,为遍历不同的聚合结构提供一个统一的接口时。
  • \

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot项目如何打包、部署

发表于 2021-11-20

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

写在前面

最近跟着项目组在开发SpringBoot项目,涉及的了一些打包和部署的工作,交到手里的任务,只能去研究研究了,今天跟大家分享一下学习成果。

SpringBoot项目如何打包jar

SpringBoot项目我们本次使用的是Maven集成部署,其中需要加入相关的plugins信息,如下:

1
2
3
4
5
6
7
8
9
10
11
xml复制代码<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>

加入了上述信息至pom文件中,就可以进行初步打包了,可以在Maven信息框中进行执行命令:

mvn clean package

image.png

点击下图中的按钮:

image.png

我们得到了以下界面:

image.png

让我们执行mvn clean package命令

我们就可以在项目的target文件夹下得到一个jar包了。

SpringBoot项目jar包如何部署

有了SpringBoot打好的jar包,之后我们就需要部署了,部署到服务器上首先要进行后台运行的。

通常我们运行springboot打出的jar包,就可以通过java -jar test.jar命令来运行该jar包程序。

当然我们要想后台运行,还需要依赖nohup命令,其命令使用就是nohup java -jar test.jar &即可。

加入日志输出

如果要加入日志输出的话,我们就需要执行以下命令了:

nohup java -jar test.jar >log.log &

由此就可以将test.jar包中运行的日志打出到log.log文件中了。

这里可能还会存在一个问题,那就是java环境变量可能没有配置好,或者是多个java版本,那就只能指定java版本,如下命令:

nohup /usr/java8/bin/java -jar test.jar >log.log &

总结

今天我们学习了SpringBoot项目如何打成jar包,并且如何部署的过程,希望可以帮到大家。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java反射

发表于 2021-11-20

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

什么是反射

Java中class和interface的数据类型都是class类型,每加载一个class,JVM就为其创建一个class类型的实例,并关联起来。
JVM的每个Class实例都指向一个数据类型(class或者interface)
一个Class实例包含该class的完整信息,就是说每加载一个类的时候都会加载一个class示例,这个class示例包含该类所有的信息,我们通过获取该类的class实例然后获取到class保存的方法信息就叫反射。

获取class实例

3种获取class实例的方法

  • String.class
  • object.getClass()
  • Class.forName()

class在jvm中只会有一个实例,所以可以直接使用==

访问class的字段

通过Class实例获取字段field信息:

  • getField(name):获取某个public的field(包括父类)
  • getDeclaredField(name):获取当前类的某个field(不包括父类)
  • getFields():获取所有public的field(包括父类)
  • getDeclaredFields():获取当前类的所有field(不包括父类)

Field对象包含一个field的所有信息:

  • getName()
  • getType()
  • getModifiers()

获取和设置field的值:
get(Object obj) 获取对象该field的值!
set(Object, Object)

image.png

由代码可以知道;反射的field 相当于一个工具的类,需要作用于所需的对象上去,自己本身不具有某些属性

通过反射访问Field需要通过SecurityManager设置的规则。

通过设置setAccessible(true)来访问非public字段。

image.png

Method

通过Class实例获取方法Method信息:

  • getMethod(name, Class…):获取某个public的method(包括父类) 后面那些class表示方法的参数类型,因为方法存在重载所以要确定参数类型
  • getDeclaredMethod(name, Class…):获取当前类的某个method(不包括父类)
  • getMethods():获取所有public的method(包括父类)
  • getDeclaredMethods():获取当前类的所有method(不包括父类)

Method对象包含一个method的所有信息:

  • getName()
  • getReturnType()
  • getParameterTypes()
  • getModifiers()

调用Method:

  • Object invoke(Object obj, Object… args)

通过设置setAccessible(true)来访问非public方法。 反射调用Method也遵守多态的规则。

Constructor

Class.newInstance() 只能调用public无参数构造方法,但是带参数的不可以这样使用,而是使用Constructor对象

通过Class实例获取Constructor信息:

  • getConstructor(Class…):获取某个public的Constructor
  • getDeclaredConstructor(Class…):获取某个Constructor
  • getConstructors():获取所有public的Constructor
  • getDeclaredConstructors():获取所有Constructor

通过Constructor实例可以创建一个实例对象:

  • newInstance(Object… parameters)

通过设置setAccessible(true)来访问非public构造方法。

获取继承关系

获取父类的Class:

Class getSuperclass()

  • Object的父类是null
  • interface的父类是null

获取当前类直接实现的interface:

  • Class[] getInterfaces()
  • 不包括间接实现的interface
  • 没有interface的class返回空数组
  • interface返回继承的interface

判断一个向上转型是否成立:

  • bool isAssignableFrom(Class)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…259260261…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%