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

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


  • 首页

  • 归档

  • 搜索

Spring Cloud / Alibaba 微服务架构

发表于 2021-11-04

内容暂贴去(28)

本文转载自: 掘金

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

分布式日志系统ELK+skywalking分布式链路完整搭建

发表于 2021-11-04

开头

在分布式系统中,日志跟踪是一件很令程序员头疼的问题,在遇到生产问题时,如果是多节点需要打开多节点服务器去跟踪问题,如果下游也是多节点且调用多个服务,那就更麻烦,再者,如果没有分布式链路,在生产日志飞速滑动的情况下,很难找出问题。

所以,分布式系统中很有必要搭建一套分布式日志系统,笔者采用了市面成熟的解决方案ELK+skywalking解决,本文将从0到1搭建一个分布式日志系统。

效果

先看效果图

1.kibana:在kibana中可直接查看线上错误日志,trace_id表示这次请求的唯一链路id

2.skywalking:通过1中的trace_id在skywalking中搜索,能迅速定位到日志

image.png

image.png

架构图

文档地址: www.processon.com/view/link/6…

image.png

架构说明:

1.skywalking:分布式链路解决方案,可记录整条链路的调用详情,含所有下游服务,TID贯穿整条链路

2.elasticsearch1:用来存储skywalking的链路数据

3.filebeat:见名知意,文件心跳,用来收集springboot的日志文件,原理就是可指定log未知,开启收割机定时收集日志

4.logstash:用来过滤有效的日志信息,比如收集IP、TID等信息,定义索引规范,数据存储对接es

5.elasticsearch2:用来存储logstash过滤后的日志信息,本文主要存储错误日志

6.kibana:读取elasticsearch2的错误日志,UI页面

搭建流程

机器配置:两台32G的服务器,ES没有上集群版,按需扩充

1.elasticsearch和kibana搭建

以前搭建过,这里就不赘述了,具体看这里www.jianshu.com/p/a69f8cefe…

2.skywalking搭建

下载地址:archive.apache.org/dist/skywal…

因为我使用的es是7.6.2版本,所以下载apache-skywalking-apm-es7-8.4.0.tar.gz

①下载后解压,修改webui的端口号:webapp/webapp.yml

修改skywalking oap服务配置文件 conf/application.yml,保存日志到es

配置表示trace记录保持7天,之后自动删除

storage.elasticsearch7配置(省略了${},转义错误)

recordDataTTL: SW_STORAGE_ES_RECORD_DATA_TTL:7

otherMetricsDataTTL: SW_STORAGE_ES_OTHER_METRIC_DATA_TTL:45

monthMetricsDataTTL: SW_STORAGE_ES_MONTH_METRIC_DATA_TTL:18

②启动apache-skywalking-apm-bin/bin/startup.sh

该脚本会启动两个服务,一个是webUI的服务,一个是oap收集日志服务

③在应用机02上传apache-skywalking-apm-es7-8.4.0.tar.gz,解压

④修改springboot启动脚本(java agent探针技术)

java -javaagent:apache-skywalking-apm-bin/agent/skywalking-agent.jar -Dskywalking.agent.service_name=${pro} -Dskywalking.collector.backend_service=xxx:11800 -jar xxx.jar >> logs/catalina.out &

⑤springboot集成skywalking服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xml复制代码<!--skywalking-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.4.0</version>
</dependency>

logback-spring.xml配置
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<!-- 日志格式中添加 %tid 即可输出 trace id -->
<Pattern>${APP_NAME}:${ServerIP}:${ServerPort} %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %tid %t %logger{36}: %msg%n</Pattern>
</layout>
</encoder>
复制代码

3.filebeat搭建

①在JAVA应用服务器上搭建,采用的版本是filebeat-7.6.2-linux-x86_64,和es的版本要保持一致

②新建filebeat-logstash.yml

multiline.pattern: ‘^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}.\d{3}’

image.png

③启动

./filebeat -e -c filebeat-logstash.yml

4.logstash搭建

①在软件机01上传logstash-7.6.2

②在logstash-7.6.2/config新建filebeat-grok.conf

message表示索引 mapping

index 表示索引名称,按天建立,然后写定时任务保留最近X天

image.png

③新建startLogstash.sh nohup bin/logstash -f config/filebeat-grok.conf –config.reload.automatic > logstash.log &

image.png

注意事项:先启动logstash,再启动filebeat

总结

总结几个注意事项

1.需要保证kibana和skywalking web ui的安全性,可以通过nginx设置登录密码

2.oap服务需要根据调用量灵活调整

3.es需要搭建集群,需要设置定期清理,比如保留最近7天日志

4.skywalking增强:有报警服务、grpc日志上传服务等,需要另外配置

5.缺点:filebeat和skywalking的agent服务需要安装在每台应用机器上

本文转载自: 掘金

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

Hive计算最大连续登陆天数,你学废了吗?

发表于 2021-11-04

数据仓库系列文章

  1. 数仓架构发展史
  2. 数仓建模方法论
  3. 数仓建模分层理论
  4. 数仓建模—宽表的设计
  5. 数仓建模—指标体系
  6. 一文搞懂ETL和ELT的区别
  7. 数据湖知识点
  8. 技术选型 | OLAP大数据技术哪家强?
  9. 数仓相关面试题
  10. 从 0 到 1 学习 Presto,这一篇就够了!
  11. 元数据管理在数据仓库的实践应用
  12. 做中台2年多了,中台到底是什么呢?万字长文来聊一聊中台
  13. 数据仓库之拉链表
  14. sqoop用法之mysql与hive数据导入导出

强哥说他发现了财富密码,最近搞了一套股票算法,其中有一点涉及到股票连续涨停天数的计算方法,我们都知道股票周末是不开市的,这里有个断层,需要一点技巧。我问是不是时间序列,他说我瞎扯,我也知道自己是瞎扯。问他方法,他竟然不告诉我,这么多年的兄弟情谊算个屁。真当我没他聪明吗,哼!

靠人不如靠自己,我决定连夜研究一下在Hive里面计算最大连续天数的计算方法。

一、背景

在网站平台类业务需求中用户的「最大登陆天数」,需求比较普遍。

原始数据:

1
2
3
4
5
6
7
8
9
10
yaml复制代码u0001 2019-10-10
u0001 2019-10-11
u0001 2019-10-12
u0001 2019-10-14
u0001 2019-10-15
u0001 2019-10-17
u0001 2019-10-18
u0001 2019-10-19
u0001 2019-10-20
u0002 2019-10-20

说明:数据是简化版,两列分别是user_id,log_in_date。现实情况需要从采集数据经过去重,转换得到以上形式数据。

我们先建表并且将数据导入Hive:

1
2
3
sql复制代码create table test.user_log_1 (user_id string, log_in_date string) row format delimited fields terminated by ' ';

load data local inpath '/var/lib/hadoop-hdfs/data/user_log.txt' into table test.user_log_1 ;

查看一下数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
yaml复制代码hive> select * from test.user_log_1 ;
OK
u0001 2019-10-10
u0001 2019-10-11
u0001 2019-10-12
u0001 2019-10-14
u0001 2019-10-15
u0001 2019-10-17
u0001 2019-10-18
u0001 2019-10-19
u0001 2019-10-20
u0002 2019-10-20
Time taken: 0.076 seconds, Fetched: 10 row(s)

二、算法

核心是按访问时间排序,登陆时间列减去排序后的序列号,得到一个日期值,按这个值分组计数即可。

1. 第一步:排序

按照user_id分组,并且按照日期log_in_date排序:

1
sql复制代码select user_id, log_in_date, row_number() over(partition by user_id order by log_in_date) as rank from test.user_log_1;

结果:

1
2
3
4
5
6
7
8
9
10
11
yaml复制代码u0001	2019-10-10	1
u0001 2019-10-11 2
u0001 2019-10-12 3
u0001 2019-10-14 4
u0001 2019-10-15 5
u0001 2019-10-17 6
u0001 2019-10-18 7
u0001 2019-10-19 8
u0001 2019-10-20 9

u0002 2019-10-20 1

这里可以看出,u0001这个用户最大连续登录天数是4天,使用后面计算方法计算后可以验证。

2. 第二步:第二列与第三列做日期差值

可以看出规律,日期小的,行号也小;如果将日期跟行号做差值,连续登录的差值应该是一样的。

1
sql复制代码select user_id, date_sub(log_in_date, rank) dts from (select user_id, log_in_date, row_number() over(partition by user_id order by log_in_date) as rank from test.user_log_1)m;

结果:

1
2
3
4
5
6
7
8
9
10
yaml复制代码u0001	2019-10-09
u0001 2019-10-09
u0001 2019-10-09
u0001 2019-10-10
u0001 2019-10-10
u0001 2019-10-11
u0001 2019-10-11
u0001 2019-10-11
u0001 2019-10-11
u0002 2019-10-19

显然可以看出,最大连续连续登录是4次。

3. 第三步:按第二列分组求和

1
sql复制代码select user_id, dts, count(1) num from (select user_id, date_sub(log_in_date, rank) dts from (select user_id, log_in_date, row_number() over(partition by user_id order by log_in_date) as rank from test.user_log_1)m)m2 group by user_id, dts;

结果:

1
2
3
4
yaml复制代码u0001	2019-10-09	3
u0001 2019-10-10 2
u0001 2019-10-11 4
u0002 2019-10-19 1

4. 第四步:求最大次数

已经算出了,每个用户连续登录天数序列,接下取每个用户最大登录天数最大值即可:

1
sql复制代码select user_id, max(num) from (select user_id, dts, count(1) num from (select user_id, date_sub(log_in_date, rank) dts from (select user_id, log_in_date, row_number() over(partition by user_id order by log_in_date) as rank from test.user_log_1)m)m2 group by user_id, dts)m3 group by user_id;

结果跟我们的预期是一致的,用户u0001最大登录天数是4。

1
2
复制代码u0001	4
u0002 1

三、扩展(股票最大涨停天数)

我们知道股票市场,比如咱们的A股,周末是不开盘的,那么一只股票如果上周五涨停,本周一接着涨停,这算是连续2天涨停,使用上面这种方法是不行的,使用lead函数试试:

1
sql复制代码select user_id, log_in_date, lead(log_in_date) over(partition by user_id order by log_in_date) end_date from test.user_log_1;

结果

1
2
3
4
5
6
7
8
9
10
yaml复制代码u0001	2019-10-10	2019-10-11
u0001 2019-10-11 2019-10-12
u0001 2019-10-12 2019-10-14
u0001 2019-10-14 2019-10-15
u0001 2019-10-15 2019-10-17
u0001 2019-10-17 2019-10-18
u0001 2019-10-18 2019-10-19
u0001 2019-10-19 2019-10-20
u0001 2019-10-20 NULL
u0002 2019-10-20 NULL

哈哈,是不是有思路了。

思路:上面结果一共有3列,第一列是uid,通过lead函数,后面两列都是日期,那么两列日期都取值周一到周五之间,也就是说数据里面只有工作日日期,没有周末的数据,可以提前过滤使得数据满足,既然要连续,那么:

  1. 如果第三列的日期,减去第二列的日期,差值等于1,显然是连续的;
  2. 如果第三列的日期,减去第二列的日期,差值等于3,但是第三列日期是星期一,那么也算是连续了;

以上两种条件综合,就能计算出股票的最大连续涨停天数了,你学废了吗。

猜你喜欢

HDFS的快照讲解

Hadoop 数据迁移用法详解

Hbase修复工具Hbck

数仓建模分层理论

一文搞懂Hive的数据存储与压缩

大数据组件重点学习这几个

本文转载自: 掘金

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

使用 OpenCV 和 Python 模糊和匿名化人脸

发表于 2021-11-04

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

🌊 作者主页:海拥

🌊 作者简介:🥇HDZ核心组成员、🏆全栈领域优质创作者、🥈蝉联C站周榜前十

🌊 粉丝福利:进粉丝群每周送四本书(每位都有),每月抽送各种小礼品(掘金搪瓷杯、抱枕、鼠标垫、马克杯等)

在本文中,我们将了解如何使用 OpenCV 和 Python 模糊和匿名化人脸。

为此,我们将使用级联分类器来检测人脸。确保从这个链接下载相同的xml文件:

drive.google.com/file/d/1PPO…

方法

  • 首先,我们使用内置的人脸检测算法,从实时视频或图像中检测人脸。在这里,我们将使用级联分类器方法从实时视频(使用网络摄像头)中检测人脸。
  • 然后,读取来自实时视频的帧。存储最新的帧并转换为灰度,以更好地理解特征。
  • 现在,为了使输出美观,我们将在检测到的人脸周围制作一个彩色边框矩形。但是,我们希望检测到的人脸是模糊的,所以我们使用中值模糊函数来做同样的事情,并提到应该模糊人脸的区域。
  • 而且,现在我们想要显示模糊的脸,使用 imshow 函数读取的帧,我们希望它被显示,直到我们按下一个键。

分步实施:

步骤 1: 导入人脸检测算法,称为级联分类器。

1
2
3
python复制代码import cv2
# 检测人脸
cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")

步骤 2: 从视频中捕获帧,以便从帧中检测人脸

1
2
3
4
5
python复制代码video_capture = cv2.VideoCapture(0)
while True:

# 从视频中捕获最新的帧
check, frame = video_capture.read()

步骤 3: 将捕获的帧更改为灰度。

1
2
3
4
5
python复制代码# 将帧转换为灰度(黑白阴影)
gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
face = cascade.detectMultiScale(gray_image,
scaleFactor=2.0,
minNeighbors=4)

步骤 4: 在检测到的人脸周围绘制一个彩色矩形。

1
2
3
4
5
6
7
python复制代码for x, y, w, h in face:

# 在检测到的人脸周围绘制边框
# (此处边框颜色为绿色,粗细为3)
image = cv2.rectangle(frame, (x, y),
(x+w, y+h),
(0, 255, 0), 3)

步骤 5: 模糊矩形内的部分(包含检测到的人脸)。

1
2
python复制代码# 模糊矩形中的人脸
image[y:y+h, x:x+w] = cv2.medianBlur(image[y:y+h, x:x+w], 35)

步骤 6: 显示最终输出,即检测到的人脸(矩形内)是模糊的。

1
2
3
python复制代码# 在视频中显示模糊的脸
cv2.imshow('face blurred', frame)
key = cv2.waitKey(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
47
48
49
50
51
52
python复制代码import cv2

# 检测人脸
cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")

# VideoCapture 是一个函数,用于捕获来自连接到系统的摄像头的视频
# 你可以传递 0 或 1
# 0 用于笔记本电脑网络摄像头
# 1 用于外部网络摄像头
video_capture = cv2.VideoCapture(0)

# 一个while循环运行无限次,为视频捕获无限数量的帧,因为视频是帧的组合
while True:

# 从视频中捕获最新的帧
check, frame = video_capture.read()

# 将帧转换为灰度(黑白阴影)
gray_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

# 在捕获的帧中检测多个人脸
# scaleFactor:参数指定在每个图像比例下图像尺寸减小多少。
# minNeighbors: 参数指定每个矩形应该有多少个邻居来保留它。
# 矩形包含检测对象。
# 这里的对象是人脸。
face = cascade.detectMultiScale(
gray_image, scaleFactor=2.0, minNeighbors=4)

for x, y, w, h in face:

# 在检测到的人脸周围绘制边框。
#(这里边框颜色为绿色,粗细为3)
image = cv2.rectangle(frame, (x, y), (x+w, y+h),
(0, 255, 0), 3)

# 模糊矩形中的人脸
image[y:y+h, x:x+w] = cv2.medianBlur(image[y:y+h, x:x+w],
35)

# 在视频中显示模糊的脸
cv2.imshow('face blurred', frame)
key = cv2.waitKey(1)

# 该语句每帧仅运行一次。
# 基本上,如果我们得到一个密钥,而那个密钥是一个 q
if key == ord('q'):
break

# 我们将暂停退出 while 循环,
# 然后运行:
video_capture.release()
cv2.destroyAllWindows()

输出:

image.png

写在最后的

我已经写了很长一段时间的技术博客,并且主要通过掘金发表,这是我的一篇使用 OpenCV 和 Python 模糊和匿名化人脸。我喜欢通过文章分享技术与快乐。您可以访问我的博客: juejin.cn/user/204034… 以了解更多信息。希望你们会喜欢!😊

💌 欢迎大家在评论区提出意见和建议!💌

本文转载自: 掘金

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

java8(六)用 Optional 取代 null 一、n

发表于 2021-11-04

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

一、null带来了哪些问题?

1)NullPointerException 是目前Java程序开发中最典型的异常。

2)它会使你的代码膨胀。它让你的代码充斥着深度嵌套的 null 检查。

3)它自身是毫无意义的。

4)它破坏了Java的哲学。Java一直试图避免让程序员意识到指针的存在,唯一的例外是: null 指针。

5)它在Java的类型系统上开了个口子。null 并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。

简单举例,有一辆汽车car,汽车有没有上保险呢?我们通过车的对象获取一下,看看保险的名字,代码如下

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
java复制代码import lombok.Data;

/**
* @description: TestOptional
* @author:weirx
* @date:2021/10/26 14:26
* @version:3.0
*/
public class TestOptional {

@Data
static class Car{

private Insurance insurance;

private String name;
}

@Data
static class Insurance{

private String name;
}

public static void main(String[] args) {
Car car = new Car();
car.getInsurance().getName();
}
}

结果:

1
2
php复制代码Exception in thread "main" java.lang.NullPointerException
at com.cloud.bssp.java8.Optional.TestOptional.main(TestOptional.java:31)

如上所示,报了我们深恶痛绝的空指针异常,怎么防止呢?无非就是增加非空判断:

1
java复制代码car.getInsurance() == null ? null : car.getInsurance().getName();

虽然上面的代码解决了空指针的问题,但是无形中增加了代码的复杂程度,可读性。

二、Optional 类入门

汲取 Haskell 和 Scala 的灵感,Java 8中引入了一个新的类 java.util.Optional 。这是一个封装 Optional 值的类。

如上面的空指针例子中,如果不知道车到底有没有保险,就不应该将保险声明为Insurance,而是应该声明为Optional。

当变量存在时, Optional 类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的 Optional 对象,由方法 Optional.empty() 返回。 Optional.empty() 方法是一个静态工厂方法,它返回 Optional 类的特定单一实例。

使用Optional我们可以像下面这么声明:

1
2
3
4
5
6
java复制代码    static class Car {

private Optional<Insurance> insuranceOptional;

private String name;
}

在语义上来讲,使用Optional来声明你的类,能非常清晰地界定出变量值的缺失是结构上的问题,还是你算法上的缺陷,抑或是你数据中的问题。

三、Optional使用

3.1 创建Optional对象

1
2
3
4
5
6
7
8
9
java复制代码        // 1、声明一个空Optional
Optional<Car> optional1 = Optional.empty();

// 2、依据一个非空值创建Optional,如果传入一个null,则会抛出空指针异常
Optional<Car> optional2 = Optional.of(new Car());

// 3、可接受null的Optional:使用静态工厂方法 Optional.ofNullable ,你可以创建一个允许 null 值的 Optional
// 如果 car 是 null ,那么得到的 Optional 对象就是个空对象。
Optional<Car> optional3 = Optional.ofNullable(null);

3.2 使用 map 从 Optional 对象中提取和转换值

Optional 提供了一个 map 方法,使用方式如下:

1
2
java复制代码        Optional<Car> optionalCar = Optional.ofNullable(car);
Optional<String> optionalName = optionalCar.map(Car::getName);

与之前的文章当中我们学习Stream中的map比较相像。

那么如果我们想要获取保险的名称怎么获取呢?

1
2
3
4
java复制代码        Car car = new Car();
car.setInsuranceOptional(Optional.empty());
Optional<Car> optionalCar = Optional.ofNullable(car);
optionalCar.map(Car::getInsuranceOptional).map(Insurance::getName);

如上这个代码是无法通过编译的。getInsuranceOptional返回的是Optional对象,接下来的map操作就成了对Optional对象操作getName命令,这是违法的。

3.3 使用 flatMap 链接 Optional 对象

为了解决前面的问题,此时我们可以使用flatMap方法,与Stream中的flatMap有相同的效果。我们这里要做的是将两层Optional合并成一个Optional:

1
2
3
4
java复制代码        Car car = new Car();
car.setInsuranceOptional(Optional.empty());
Optional<Car> optionalCar = Optional.ofNullable(car);
optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName);

如上所示我们给出的car中Insurance是空Optional,虽然运行上面的代码不会咋抛出空指针异常了,但是我们仍需要对其有个处理的话可以使用orElse(),如下所示:

1
2
3
4
5
6
7
8
java复制代码        Car car = new Car();
car.setInsuranceOptional(Optional.empty());
Optional<Car> optionalCar = Optional.ofNullable(car);
String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElse("unknown");
System.out.println(unknown);

-----------输出---------------
unknown

3.4 读取Optional对象中的值

3.4.1 get()

get() 是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException 异常。

除非你非常确定 Optional变量一定包含值,否则使用这个方法是个相当糟糕的主意。

此外,这种方式即便相对于嵌套式的 null 检查,也并未体现出多大的改进。

1
2
3
4
5
6
7
java复制代码        Car car = new Car();
car.getInsuranceOptional().get();

--------输出-----------
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at com.cloud.bssp.java8.Optional.TestOptional.main(TestOptional.java:36)

3.4.2 orElse(T other)

orElse(T other)是在前面的例子中提到的方法。它允许你在Optional 对象不包含值时提供一个默认值。

1
2
3
4
5
6
7
8
java复制代码        Car car = new Car();
car.setInsuranceOptional(Optional.empty());
Optional<Car> optionalCar = Optional.ofNullable(car);
String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElse("unknown");
System.out.println(unknown);

-----------输出---------------
unknown

3.4.3 orElseGet(Supplier<? extends T> other)

orElse 方法的延迟调用版。

Supplier方法只有在 Optional 对象不含值时才执行调用。

如果创建默认值时需要执行其他的方法做一些操作时,或者你需要非常确定某个方法仅在Optional 为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码    public static String test() {
System.out.println("this car has no insurance");
return "unknown";
}

Car car = new Car();
car.setInsuranceOptional(Optional.empty());
Optional<Car> optionalCar = Optional.ofNullable(car);
String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElseGet(TestOptional::test);
System.out.println(unknown);

-------输出--------
this car has no insurance
unknown

3.4.4 orElseThrow(Supplier<? extends X> exceptionSupplier)

和 orElseGet方法非常类似,它们遭遇 Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow 你可以定制希望抛出的异常类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码    static class MyException extends RuntimeException{

}

Car car = new Car();
car.setInsuranceOptional(Optional.empty());
Optional<Car> optionalCar = Optional.ofNullable(car);
String unknown = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).orElseThrow(MyException::new);
System.out.println(unknown);

-------输出-----------
Exception in thread "main" com.cloud.bssp.java8.Optional.TestOptional$MyException
at java.util.Optional.orElseThrow(Optional.java:290)
at com.cloud.bssp.java8.Optional.TestOptional.main(TestOptional.java:42)

3.4.5 ifPresent(Consumer<? super T>)

能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码    static void addName(String name){
System.out.println("the insurance is : " + name);
}

Car car = new Car();
Insurance insurance = new Insurance();
insurance.setName("车险");
car.setInsuranceOptional(Optional.of(insurance));
Optional<Car> optionalCar = Optional.ofNullable(car);
optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).ifPresent(TestOptional::addName);

--------------输出--------------
the insurance is : 车险

3.4.6 isPresent

如果存在就返回true,不存在就返回false。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码        Car car = new Car();
car.setInsuranceOptional(Optional.empty());
System.out.println(car.getInsuranceOptional().isPresent());

Insurance insurance = new Insurance();
insurance.setName("车险");
car.setInsuranceOptional(Optional.of(insurance));
System.out.println(car.getInsuranceOptional().isPresent());

--------------输出--------------
false
true

3.4.7 filter(Predicate<? super T> predicate)

filter 方法接受一个谓词作为参数。如果 Optional 对象的值存在,并且它符合谓词的条件,filter 方法就返回其值;否则它就返回一个空的 Optional 对象。

1
2
3
4
5
6
7
8
9
10
java复制代码        Car car = new Car();
Insurance insurance = new Insurance();
insurance.setName("车险");
car.setInsuranceOptional(Optional.of(insurance));
Optional<Car> optionalCar = Optional.ofNullable(car);
Optional<String> s = optionalCar.flatMap(Car::getInsuranceOptional).map(Insurance::getName).filter("车险"::equals);
System.out.println(s.get());

------------输出-----------
车险

兄弟们,看完有收获,点个赞吧

本文转载自: 掘金

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

云原生初体验 在k8s上部署springboot应用 执行流

发表于 2021-11-04

cncf.png

一直对“云原生”很有兴趣,却不知道从何入手,最近刚好在研究服务网格,就顺便再夯实一下基础吧。

本文我们会在window环境下,构建一套基于k8s的istio环境,并且通过skaffold完成镜像的构建和项目部署到集群环境。其实对于实验环境有限的朋友们,完全可以在某里云上,按量付费搞3台”突发性能实例“,玩一晚,也就是杯咖啡钱。

执行流程

整体流程的话,如下图所示,通过 Skaffold+jib 将开发的应用打包成镜像,提交到本地仓库,并且将应用部署到集群中。k8s中部署2个pod,模拟应用不同的版本,并且配置访问权重20%:80%。

k8s.png

环境选择

之前的文章,有对 minikube 的介绍,本次实验,开始的时候,我一直沉溺在kind的便捷上,而且直接可以在docker上部署集群,但是由于我对K8S的理解并不足够,导致后面遇到了很多问题,所以,在这里建议新上手的小伙伴,还是使用minikube吧。简单大佬推荐使用RKE来部署,但是碍于机器性能,不能开启太多虚拟机,于是最后又换回了minikube。 k3s和RKE都需要多台虚拟机,这种方案暂时不考虑。

minikube kind k3s
runtime VM container native
supported architectures AMD64 AMD64 AMD64, ARMv7, ARM64
supported container runtimes Docker,CRI-O,containerd,gvisor Docker Docker, containerd
startup time initial/following 5:19 / 3:15 2:48 / 1:06 0:15 / 0:15
memory requirements 2GB 8GB (Windows, MacOS) 512 MB
requires root? no no yes (rootless is experimental)
multi-cluster support yes yes no (can be achieved using containers)
multi-node support no yes yes
project page minikube.sigs.k8s.io/ kind.sigs.k8s.io/ k3s.io/

docker desktop 没有特殊要求。其他的自己用的顺手就好,还是需要特别说一下minikube,别用最新的coredns一直都拉不下来,除非你的魔法,可以完全搞定,否则,还是用阿里编译的minikube版本吧,别跟自己较劲,别问我为什么…

我用的版本罗列在下面了:

1
2
3
4
5
6
7
8
9
10
11
bash复制代码➜  ~ istioctl version
client version: 1.10.2
control plane version: 1.10.2
data plane version: 1.10.2 (10 proxies)

➜ ~ minikube version
minikube version: v1.18.1
commit: 511aca80987826051cf1c6527c3da706925f7909

➜ ~ skaffold version
v1.29.0

环境搭建

使用minikube创建集群

使用 hyperv , 内存 8192 cup 4核, 不能再少了,否则拉不起来 istio

1
bash复制代码➜  ~ minikube start  --image-mirror-country='cn' --registry-mirror=https://hq0igpc0.mirror.aliyuncs.com --vm-driver="hyperv" --memory=8192 --cpus=4 --hyperv-virtual-switch="minikubeSwitch" --image-repository=registry.cn-hangzhou.aliyuncs.com/google_containers

还要在 hyperv里创建一个虚拟路由,这里我构建了一个内部网络,这样可以通过设置网卡的ip,将内部网络的网段固定下来,否则,每次重启都会变化ip

switch.png

配置让内部网络,共享访问互联网

net.png

启动成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bash复制代码➜  istio-1.10.2 minikube start
😄 Microsoft Windows 10 Pro 10.0.19042 Build 19042 上的 minikube v1.18.1
🎉 minikube 1.20.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.20.0

✨ 根据现有的配置文件使用 hyperv 驱动程序
👍 Starting control plane node minikube in cluster minikube
🔄 Restarting existing hyperv VM for "minikube" ...
🐳 正在 Docker 20.10.3 中准备 Kubernetes v1.20.2…
🔎 Verifying Kubernetes components...
▪ Using image registry.cn-hangzhou.aliyuncs.com/google_containers/storage-provisioner:v4 (global image repository)
▪ Using image registry.hub.docker.com/kubernetesui/dashboard:v2.1.0
▪ Using image registry.hub.docker.com/kubernetesui/metrics-scraper:v1.0.4
▪ Using image registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server-amd64:v0.2.1 (global image repository)
🌟 Enabled addons: metrics-server, storage-provisioner, dashboard, default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

部署 istio

创建 istio-system 的命名空间

kubectl create namespace istio-system

安装 istio

istioctl manifest apply --set profile=demo

安装完成后,执行命令 kubectl get svc -n istio-system

1
2
3
4
5
bash复制代码➜  ~ kubectl get svc -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-egressgateway ClusterIP 10.105.31.73 <none> 80/TCP,443/TCP 8d
istio-ingressgateway LoadBalancer 10.103.61.73 <pending> 15021:31031/TCP,80:31769/TCP,443:30373/TCP,31400:31833/TCP,15443:32411/TCP 8d
istiod ClusterIP 10.110.109.205 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 8d

部署 bookinfo

部署 bookinfo demo 验证环境

执行命令

1
2
3
bash复制代码kubectl label namespace default istio-injection=enabled

kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

等待pod都启动起来以后,添加bookinfo网络配置,用于访问 kubectl apply -f .\samples\bookinfo\networking\bookinfo-gateway.yaml

1
2
3
4
5
bash复制代码➜ istio-1.10.2 kubectl apply -f .\samples\bookinfo\networking\bookinfo-gateway.yaml

gateway.networking.istio.io/bookinfo-gateway created

virtualservice.networking.istio.io/bookinfo created

使用命令查看service : kubectl get services

1
2
3
4
5
6
7
8
bash复制代码➜  ~ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
callme-service NodePort 10.106.26.24 <none> 8080:30101/TCP 8d
details ClusterIP 10.110.253.19 <none> 9080/TCP 8d
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 8d
productpage ClusterIP 10.96.246.175 <none> 9080/TCP 8d
ratings ClusterIP 10.99.234.109 <none> 9080/TCP 8d
reviews ClusterIP 10.103.177.123 <none> 9080/TCP 8d

查看pods状态 kubectl get pods

1
2
3
4
5
6
7
8
9
10
bash复制代码➜  ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
callme-service-v1-76dd76ddcc-znb62 2/2 Running 0 4h59m
callme-service-v2-679db76bbc-m4svm 2/2 Running 0 4h59m
details-v1-79f774bdb9-qk9q8 2/2 Running 8 8d
productpage-v1-6b746f74dc-p4xcb 2/2 Running 8 8d
ratings-v1-b6994bb9-dlvjm 2/2 Running 8 8d
reviews-v1-545db77b95-sgdzq 2/2 Running 8 8d
reviews-v2-7bf8c9648f-t6s8z 2/2 Running 8 8d
reviews-v3-84779c7bbc-4p8hv 2/2 Running 8 8d

查看集群ip 以及 端口

1
2
3
4
5
6
7
8
9
10
bash复制代码➜  ~ kubectl get po -l istio=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'
192.168.137.115



➜ istio-1.10.2 kubectl get svc istio-ingressgateway -n istio-system

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

istio-ingressgateway LoadBalancer 10.110.228.32 <pending> 15021:32343/TCP,80:30088/TCP,443:31869/TCP,31400:32308/TCP,15443:32213/TCP 3m17s

于是访问地址: http://192.168.137.115:31769/productpage

bookinfo.png
我们 bookinfo 就部署成功了。接下来我们创建应用

构建应用

project.png

构建一个普通的springboot工程,添加编译插件,这里我们使用了本地的docker仓库存储镜像

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
xml复制代码<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<to>
<image>127.0.0.1:9001/${project.artifactId}:${project.version}</image>
<auth>
<username>
xxx
</username>
<password>
xxx
</password>
</auth>
</to>
<allowInsecureRegistries>true</allowInsecureRegistries>
</configuration>
</plugin>
</plugins>
</build>

构建一个简单的rest,现实一个构建名称,以及配置的一个版本号

1
2
3
4
5
6
7
8
9
10
java复制代码@Autowired
BuildProperties buildProperties;
@Value("${VERSION}")
private String version;

@GetMapping("/ping")
public String ping() {
LOGGER.info("Ping: name={}, version={}", buildProperties.getName(), version);
return "I'm callme-service " + version;
}

创建 skaffold.xml 用于给 skafflod 编译镜像,提交集群使用

1
2
3
4
5
6
7
8
yaml复制代码apiVersion: skaffold/v2alpha1
kind: Config
build:
artifacts:
- image: 127.0.0.1:9001/callme-service
jib: {}
tagPolicy:
gitCommit: {}

创建k8s的部署描述 k8s/deployment.yml,以及service用于访问

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
yaml复制代码apiVersion: apps/v1
kind: Deployment
metadata:
name: callme-service-v1
spec:
replicas: 1
selector:
matchLabels:
app: callme-service
version: v1
template:
metadata:
labels:
app: callme-service
version: v1
spec:
containers:
- name: callme-service
image: 127.0.0.1:9001/callme-service
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env:
- name: VERSION
value: "v1"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: callme-service-v2
spec:
replicas: 1
selector:
matchLabels:
app: callme-service
version: v2
template:
metadata:
labels:
app: callme-service
version: v2
spec:
containers:
- name: callme-service
image: 127.0.0.1:9001/callme-service
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env:
- name: VERSION
value: "v2"
---
apiVersion: v1
kind: Service
metadata:
name: callme-service
labels:
app: callme-service
spec:
type: NodePort
ports:
- port: 8080
name: http
nodePort: 30101
selector:
app: callme-service

创建 istio描述文件 k8s\istio-rules.yaml

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
yaml复制代码apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: callme-service-destination
spec:
host: callme-service
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
# trafficPolicy: # --- enable for adding circuit breaker into DestinationRule
# connectionPool:
# http:
# http1MaxPendingRequests: 1
# maxRequestsPerConnection: 1
# maxRetries: 0
# outlierDetection:
# consecutive5xxErrors: 3
# interval: 30s
# baseEjectionTime: 1m
# maxEjectionPercent: 100
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: callme-service-route
spec:
hosts:
- callme-service
http:
- route:
- destination:
host: callme-service
subset: v2
weight: 80
- destination:
host: callme-service
subset: v1
weight: 20
retries:
attempts: 3
retryOn: gateway-error,connect-failure,refused-stream
timeout: 0.5s
# fault: # --- enable for inject fault into the route
# delay:
# percentage:
# value: 33
# fixedDelay: 3s

运行 skaffold 进行编译,提交镜像,并部署应用 skaffold run --tail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bash复制代码➜  callme-service git:(master) ✗ skaffold run --tail
Generating tags...

- 127.0.0.1:9001/callme-service -> 127.0.0.1:9001/callme-service:e9c731f-dirty
Checking cache...
- 127.0.0.1:9001/callme-service: Found Locally
Starting test...
Tags used in deployment:
- 127.0.0.1:9001/callme-service -> 127.0.0.1:9001/callme-service:60f1bf39367673fd0d30ec1305d8a02cb5a1ed43cf6603e767a98dc0523c65f3
Starting deploy...
- deployment.apps/callme-service-v1 configured
- deployment.apps/callme-service-v2 configured
- service/callme-service configured
- destinationrule.networking.istio.io/callme-service-destination configured
- virtualservice.networking.istio.io/callme-service-route configured
Waiting for deployments to stabilize...
- deployment/callme-service-v1: waiting for init container istio-init to start
- pod/callme-service-v1-76dd76ddcc-znb62: waiting for init container istio-init to start
- deployment/callme-service-v2: waiting for init container istio-init to start
- pod/callme-service-v2-679db76bbc-m4svm: waiting for init container istio-init to start
- deployment/callme-service-v2 is ready. [1/2 deployment(s) still pending]
- deployment/callme-service-v1 is ready.
Deployments stabilized in 45.671 seconds

访问查看结果

res.png

致此,我们初级的环境搭建基本完成了,对应云原生,感觉懂了一点,好像又没有懂,需要理解的东西还有很多,这个系列也会持续下去,希望大家和我交流,也欢迎关注,转发。

参考链接;

piotrminkowski.com/2020/02/14/…

pklinker.medium.com/integrating…

blog.csdn.net/xixingzhe2/…

blog.csdn.net/chenleiking…

本文转载自: 掘金

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

没有性能瓶颈的无限级菜单树应该这样设计

发表于 2021-11-04

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

本文节选自《设计模式就该这样学》

1 使用透明组合模式实现课程目录结构

以一门网络课程为例,我们设计一个课程的关系结构。比如,我们有Java入门课程、人工智能课程、Java设计模式、源码分析、软技能等,而Java设计模式、源码分析、软技能又属于Java架构师系列课程包,每个课程的定价都不一样。但是,这些课程不论怎么组合,都有一些共性,而且是整体和部分的关系,可以用组合模式来设计。首先创建一个顶层的抽象组件CourseComponent类。

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
java复制代码
/**
* Created by Tom.
*/
public abstract class CourseComponent {

public void addChild(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持添加操作");
}

public void removeChild(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持删除操作");
}


public String getName(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取名称操作");
}


public double getPrice(CourseComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取价格操作");
}


public void print(){
throw new UnsupportedOperationException("不支持打印操作");
}

}

把所有可能用到的方法都定义到这个顶层的抽象组件中,但是不写任何逻辑处理的代码,而是直接抛异常。这里,有些小伙伴会有疑惑,为什么不用抽象方法?因为用了抽象方法,其子类就必须实现,这样便体现不出各子类的细微差异。所以子类继承此抽象类后,只需要重写有差异的方法覆盖父类的方法即可。
然后分别创建课程Course类和课程包CoursePackage类。创建Course类的代码如下。

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
java复制代码
/**
* Created by Tom.
*/
public class Course extends CourseComponent {
private String name;
private double price;

public Course(String name, double price) {
this.name = name;
this.price = price;
}

@Override
public String getName(CourseComponent catalogComponent) {
return this.name;
}

@Override
public double getPrice(CourseComponent catalogComponent) {
return this.price;
}

@Override
public void print() {
System.out.println(name + " (¥" + price + "元)");
}

}

创建CoursePackage类的代码如下。

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
java复制代码
/**
* Created by Tom.
*/
public class CoursePackage extends CourseComponent {
private List<CourseComponent> items = new ArrayList<CourseComponent>();
private String name;
private Integer level;


public CoursePackage(String name, Integer level) {
this.name = name;
this.level = level;
}

@Override
public void addChild(CourseComponent catalogComponent) {
items.add(catalogComponent);
}

@Override
public String getName(CourseComponent catalogComponent) {
return this.name;
}

@Override
public void removeChild(CourseComponent catalogComponent) {
items.remove(catalogComponent);
}

@Override
public void print() {
System.out.println(this.name);

for(CourseComponent catalogComponent : items){
//控制显示格式
if(this.level != null){
for(int i = 0; i < this.level; i ++){
//打印空格控制格式
System.out.print(" ");
}
for(int i = 0; i < this.level; i ++){
//每一行开始打印一个+号
if(i == 0){ System.out.print("+"); }
System.out.print("-");
}
}
//打印标题
catalogComponent.print();
}
}

}

最后编写客户端测试代码。

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
java复制代码
public static void main(String[] args) {

System.out.println("============透明组合模式===========");

CourseComponent javaBase = new Course("Java入门课程",8280);
CourseComponent ai = new Course("人工智能",5000);

CourseComponent packageCourse = new CoursePackage("Java架构师课程",2);

CourseComponent design = new Course("Java设计模式",1500);
CourseComponent source = new Course("源码分析",2000);
CourseComponent softSkill = new Course("软技能",3000);

packageCourse.addChild(design);
packageCourse.addChild(source);
packageCourse.addChild(softSkill);

CourseComponent catalog = new CoursePackage("课程主目录",1);
catalog.addChild(javaBase);
catalog.addChild(ai);
catalog.addChild(packageCourse);

catalog.print();

}

运行结果如下图所示。

file

透明组合模式把所有公共方法都定义在 Component 中,这样客户端就不需要区分操作对象是叶子节点还是树枝节点;但是,叶子节点会继承一些它不需要(管理子类操作的方法)的方法,这与设计模式的接口隔离原则相违背。

2 使用安全组合模式实现无限级文件系统

再举一个程序员更熟悉的例子。对于程序员来说,电脑是每天都要接触的。电脑的文件系统其实就是一个典型的树形结构,目录包含文件夹和文件,文件夹里面又可以包含文件夹和文件。下面用代码来实现一个目录系统。
文件系统有两个大的层次:文件夹和文件。其中,文件夹能容纳其他层次,为树枝节点;文件是最小单位,为叶子节点。由于目录系统层次较少,且树枝节点(文件夹)结构相对稳定,而文件其实可以有很多类型,所以我们选择使用安全组合模式来实现目录系统,可以避免为叶子节点类型(文件)引入冗余方法。首先创建顶层的抽象组件Directory类。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码
public abstract class Directory {

protected String name;

public Directory(String name) {
this.name = name;
}

public abstract void show();

}

然后分别创建File类和Folder类。创建File类的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码
public class File extends Directory {

public File(String name) {
super(name);
}

@Override
public void show() {
System.out.println(this.name);
}

}

创建Folder类的代码如下。

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
java复制代码
import java.util.ArrayList;
import java.util.List;

public class Folder extends Directory {
private List<Directory> dirs;

private Integer level;

public Folder(String name,Integer level) {
super(name);
this.level = level;
this.dirs = new ArrayList<Directory>();
}

@Override
public void show() {
System.out.println(this.name);
for (Directory dir : this.dirs) {
//控制显示格式
if(this.level != null){
for(int i = 0; i < this.level; i ++){
//打印空格控制格式
System.out.print(" ");
}
for(int i = 0; i < this.level; i ++){
//每一行开始打印一个+号
if(i == 0){ System.out.print("+"); }
System.out.print("-");
}
}
//打印名称
dir.show();
}
}

public boolean add(Directory dir) {
return this.dirs.add(dir);
}

public boolean remove(Directory dir) {
return this.dirs.remove(dir);
}

public Directory get(int index) {
return this.dirs.get(index);
}

public void list(){
for (Directory dir : this.dirs) {
System.out.println(dir.name);
}
}

}

注意,Folder类不仅覆盖了顶层的show()方法,还增加了list()方法。
最后编写客户端测试代码。

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
java复制代码
public static void main(String[] args) {

System.out.println("============安全组合模式===========");

File qq = new File("QQ.exe");
File wx = new File("微信.exe");

Folder office = new Folder("办公软件",2);

File word = new File("Word.exe");
File ppt = new File("PowerPoint.exe");
File excel = new File("Excel.exe");

office.add(word);
office.add(ppt);
office.add(excel);

Folder wps = new Folder("金山软件",3);
wps.add(new File("WPS.exe"));
office.add(wps);

Folder root = new Folder("根目录",1);
root.add(qq);
root.add(wx);
root.add(office);

System.out.println("----------show()方法效果-----------");
root.show();

System.out.println("----------list()方法效果-----------");
root.list();

}

运行结果如下图所示。

file

安全组合模式的好处是接口定义职责清晰,符合设计模式的单一职责原则和接口隔离原则;缺点是客户需要区分树枝节点和叶子节点,这样才能正确处理各个层次的操作,客户端无法依赖抽象接口(Component),违背了设计模式的依赖倒置原则。

关注『 Tom弹架构 』回复“设计模式”可获取完整源码。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注『 Tom弹架构 』可获取更多技术干货!

本文转载自: 掘金

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

高级JAVA开发必备技能:java8 新日期时间API((四

发表于 2021-11-04

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

❤️作者简介:Java领域优质创作者🏆,CSDN博客专家认证🏆,华为云享专家认证🏆

❤️技术活,该赏

❤️点赞 👍 收藏 ⭐再看,养成习惯

大家好,我是小虚竹。之前有粉丝私聊我,问能不能把JAVA8 新的日期时间API(JSR-310)知识点梳理出来。答案是肯定的,谁让我宠粉呢。由于内容偏多,会拆成多篇来写。

闲话就聊到这,请看下面的正文。

常用于计算的类介绍

介绍下java8 中提供了几个常用于计算的类:

  • Duration:表示秒和纳秒的时间量
  • Period:表示年月日的时间量
  • TemporalUnit:日期时间的基本单位
  • TemporalField:日期时间的属性
  • ValueRange:表示取值范围

Duration

Duration类说明

包路径:java.time.Duration

1
2
3
4
5
6
7
java复制代码public final class Duration
implements TemporalAmount, Comparable<Duration>, Serializable {
private final long seconds;

private final int nanos;
...
}

Duration 是TemporalAmount 的实现类,类里包含两个变量seconds 和 nanos ,所以Duration 是由秒和纳秒组成的时间量。

一个Duration实例是不可变的,当创建出对象后就不能改变它的值了。

Duration常用的用法

创建Duration对象

Duration 适合处理较短的时间,需要更高的精确性。我们能使用between()方法比较两个瞬间的差:

1
2
3
4
5
6
7
8
9
java复制代码		Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);

image-20210830213020151

可以通过LocalDateTime 类获取获取Duration对象

1
2
3
4
5
6
java复制代码		LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);

LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);

Duration duration = Duration.between(first, second);
System.out.println(duration);

image-20210830221908291

访问Duration的时间

Duration 对象中可以获取秒和纳秒属性。但没有毫秒属性,跟System.getCurrentTimeMillis()不同。

1
2
3
4
5
6
7
8
9
10
11
java复制代码	Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);
System.out.println("秒:"+duration.getSeconds());
System.out.println("纳秒:"+duration.getNano());

image-20210830213704477

可以转换整个时间成其他单位,如纳秒,毫秒,分钟,小时,天

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码		Instant first = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
System.out.println(duration);
System.out.println("秒:"+duration.getSeconds());
System.out.println("纳秒:"+duration.getNano());
System.out.println("纳秒:"+duration.toNanos());
System.out.println("毫秒:"+duration.toMillis());
System.out.println("分:"+duration.toMinutes());
System.out.println("小时:"+duration.toHours());
System.out.println("天:"+duration.toDays());

image-20210830220300588

由图上可知,getNano 方法和toNanos 方法不太一样,前者是获取这段时间的小于1s的部分,后者是整个时间转化为纳秒。

Duration计算

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码plusNanos()
plusMillis()
plusSeconds()
plusMinutes()
plusHours()
plusDays()
minusNanos()
minusMillis()
minusSeconds()
minusMinutes()
minusHours()
minusDays()

以plusSeconds 和minusSeconds 为例:

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码LocalDateTime first = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime second = LocalDateTime.of(2021, 8, 30, 23, 13, 0);
Duration duration = Duration.between(first, second);
System.out.println(duration);

Duration duration1 = duration.plusSeconds(10);
System.out.println("plusSeconds 后:"+duration);
System.out.println("plusSeconds 后新的Duration对象:"+duration1);

Duration duration2 = duration.minusSeconds(10);
System.out.println("minusSeconds 后:"+duration);
System.out.println("minusSeconds 后新的Duration对象:"+duration2);

image-20210830222707761

由上面的验证可知,这些计算方法执行后,会返回一个新的Duration对象,原先的Duration对象不变。

Period

Period类说明

包路径:java.time.Period

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public final class Period
implements ChronoPeriod, Serializable {
/**
* The number of years.
*/
private final int years;
/**
* The number of months.
*/
private final int months;
/**
* The number of days.
*/
private final int days;
...
}

Period 是ChronoPeriod 的实现类,类里包含两个变量years ,months 和 days ,所以Period 是由年,月和日组成的时间量。

Period常用的用法

创建Period对象

1
2
3
4
java复制代码		LocalDate first = LocalDate.of(2021, 8, 29);
LocalDate second = LocalDate.of(2022, 9, 30);
Period period = Period.between(first, second);
System.out.println(period);

image-20210830224610563

访问Period的时间

1
2
3
4
5
6
7
java复制代码		LocalDate first = LocalDate.of(2021, 8, 28);
LocalDate second = LocalDate.of(2022, 10, 31);
Period period = Period.between(first, second);
System.out.println(period);
System.out.println("年:"+period.getYears());
System.out.println("月:"+period.getMonths());
System.out.println("日:"+period.getDays());

image-20210830225619062

可以转换整个时间成其他单位,月

1
java复制代码LocalDate first = LocalDate.of(2021, 8, 29);		LocalDate second = LocalDate.of(2022, 9, 30);		Period period = Period.between(first, second);		System.out.println(period);		System.out.println("月:"+period.toTotalMonths());

image-20210830225328558

由图上可知,getMonths 方法和toTotalMonths 方法不太一样,前者是获取这段时间的月的部分,后者是整个时间转化为以月为单位长度。

toTotalMonths 源码:

1
java复制代码public long toTotalMonths() {        return years * 12L + months;  // no overflow    }

Duration计算

1
2
3
4
5
6
7
java复制代码plusDays()
plusMonths()
plusYears()

minusDays()
minusMonths()
minusYears()

以plusMonths 和minusMonths 为例:

1
2
3
4
5
6
7
8
9
10
11
java复制代码		LocalDate first = LocalDate.of(2021, 8, 28);
LocalDate second = LocalDate.of(2022, 10, 31);
Period period = Period.between(first, second);
System.out.println(period);
Period period1 = period.plusMonths(1);
System.out.println("plusMonths 后:"+period);
System.out.println("plusMonths 后新的Period对象:"+period1);

Period period2 = period.minusMonths(1);
System.out.println("minusMonths 后:"+period);
System.out.println("minusMonths 后新的Period对象:"+period2);

image-20210830230345446

由上面的验证可知,这些计算方法执行后,会返回一个新的Period对象,原先的Period对象不变。

TemporalUnit

TemporalUnit类说明

包路径:java.time.temporal.TemporalUnit

1
java复制代码public interface TemporalUnit {...}public enum ChronoUnit implements TemporalUnit {    private final String name;    private final Duration duration;   ...}

TemporalUnit 主要实现类是枚举类型ChronoUnit

一个ChronoUnit成员会维护一个字符串名字属性name和一个Duration类型的实例。

其中ChronoUnit枚举了标准的日期时间单位集合,就是常用的年、月、日、小时、分钟、秒、毫秒、微秒、纳秒,这些时间单位的时间量到底是多少,代表多长的时间,在该枚举类中都有定义。

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
java复制代码public enum ChronoUnit implements TemporalUnit {

NANOS("Nanos", Duration.ofNanos(1)),
MICROS("Micros", Duration.ofNanos(1000)),
MILLIS("Millis", Duration.ofNanos(1000_000)),
SECONDS("Seconds", Duration.ofSeconds(1)),
MINUTES("Minutes", Duration.ofSeconds(60)),
HOURS("Hours", Duration.ofSeconds(3600)),
HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),
DAYS("Days", Duration.ofSeconds(86400)),
WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)),
MONTHS("Months", Duration.ofSeconds(31556952L / 12)),
YEARS("Years", Duration.ofSeconds(31556952L)),
DECADES("Decades", Duration.ofSeconds(31556952L * 10L)),
CENTURIES("Centuries", Duration.ofSeconds(31556952L * 100L)),
MILLENNIA("Millennia", Duration.ofSeconds(31556952L * 1000L)),
ERAS("Eras", Duration.ofSeconds(31556952L * 1000_000_000L)),
FOREVER("Forever", Duration.ofSeconds(Long.MAX_VALUE, 999_999_999));

private final String name;
private final Duration duration;

private ChronoUnit(String name, Duration estimatedDuration) {
this.name = name;
this.duration = estimatedDuration;
}
···
}

ChronoUnit常用的用法

1
2
3
4
5
java复制代码		LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
LocalDateTime offset = localDateTime.plus(1, ChronoUnit.DAYS);
// 非同一对象
Assert.assertNotSame(localDateTime, offset);
System.out.println(offset);

image-20210831233938785

TemporalField

TemporalField类说明

包路径:java.time.temporal.TemporalField

1
2
3
4
5
6
7
8
9
10
11
java复制代码public interface TemporalField {
...
}

public enum ChronoField implements TemporalField {
private final String name;
private final TemporalUnit baseUnit;
private final TemporalUnit rangeUnit;
private final ValueRange range;
...
}

TemporalField 主要实现类是枚举类型ChronoField

一个ChronoField成员会维护一个字符串名字属性name、一个TemporalUnit的基础单位baseUnit、一个TemporalUnit的表示范围的单位rangeUnit和一个ValueRange类型的range用于表示当前属性的范围。

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
java复制代码public enum ChronoField implements TemporalField {
//一秒钟的纳秒数
NANO_OF_SECOND("NanoOfSecond", NANOS, SECONDS, ValueRange.of(0, 999_999_999))
//一分钟的秒数
SECOND_OF_MINUTE("SecondOfMinute", SECONDS, MINUTES, ValueRange.of(0, 59), "second")
//一个小时的分钟数
MINUTE_OF_HOUR("MinuteOfHour", MINUTES, HOURS, ValueRange.of(0, 59), "minute")
//一上午或者一下午有多少个小时
CLOCK_HOUR_OF_AMPM("ClockHourOfAmPm", HOURS, HALF_DAYS, ValueRange.of(1, 12))
//一天的小时数
CLOCK_HOUR_OF_DAY("ClockHourOfDay", HOURS, DAYS, ValueRange.of(1, 24))
//上午还是下午
AMPM_OF_DAY("AmPmOfDay", HALF_DAYS, DAYS, ValueRange.of(0, 1), "dayperiod")
//一周的第几天
DAY_OF_WEEK("DayOfWeek", DAYS, WEEKS, ValueRange.of(1, 7), "weekday")
//当前月的天数
DAY_OF_MONTH("DayOfMonth", DAYS, MONTHS, ValueRange.of(1, 28, 31), "day")
//当前年的天数
DAY_OF_YEAR("DayOfYear", DAYS, YEARS, ValueRange.of(1, 365, 366))
//当前月的周数
ALIGNED_WEEK_OF_MONTH("AlignedWeekOfMonth", WEEKS, MONTHS, ValueRange.of(1, 4, 5))
//当前年的周数
ALIGNED_WEEK_OF_YEAR("AlignedWeekOfYear", WEEKS, YEARS, ValueRange.of(1, 53))
//以每月的第一天为星期一,然后计算当天是一周的第几天
ALIGNED_DAY_OF_WEEK_IN_MONTH("AlignedDayOfWeekInMonth", DAYS, WEEKS, ValueRange.of(1, 7))
//以每月的第一天为星期一,然后计算当天是一周的第几天
ALIGNED_DAY_OF_WEEK_IN_YEAR("AlignedDayOfWeekInYear", DAYS, WEEKS, ValueRange.of(1, 7))
//当前年的月数
MONTH_OF_YEAR("MonthOfYear", MONTHS, YEARS, ValueRange.of(1, 12), "month")

private final TemporalUnit baseUnit;
private final String name;
private final TemporalUnit rangeUnit;
private final ValueRange range;
private final String displayNameKey;
...
}

ChronoField常用的用法

ALIGNED_WEEK_OF_MONTH 和 ALIGNED_DAY_OF_WEEK_IN_MONTH 使用示例

1
2
3
4
5
6
java复制代码		//每七天一周,2021-08-31 是周二,对应的值是3
int num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH);
System.out.println(num);
//这个月的第5周 2021-08-31
num = LocalDate.of(2021, 8, 31).get(ChronoField.ALIGNED_WEEK_OF_MONTH);
System.out.println(num);

image-20210831233408038

ValueRange

ValueRange类说明

ValueRange 表示取值范围。

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

/**
* The smallest minimum value.最小值
*/
private final long minSmallest;
/**
* The largest minimum value.最大可能最小值
*/
private final long minLargest;
/**
* The smallest maximum value.最小可能最大值
*/
private final long maxSmallest;
/**
* The largest maximum value.最大值
*/
private final long maxLargest;
...
}

ValueRange常用的用法

1
2
3
4
ini复制代码ValueRange valueRange = ValueRange.of(1L, 10000L);
System.out.println(valueRange);
valueRange = ValueRange.of(1L, 5L, 10000L, 50000L);
System.out.println(valueRange);

image-20210831234618197

1
2
3
4
5
6
java复制代码		LocalDateTime localDateTime = LocalDateTime.of(2021, 8, 30, 23, 14, 20);
ValueRange valueRange = localDateTime.range(ChronoField.DAY_OF_MONTH);
System.out.println(valueRange.getMinimum());
System.out.println(valueRange.getMaximum());
System.out.println(valueRange.getLargestMinimum());
System.out.println(valueRange.getSmallestMaximum());

image-202109019254578

Chronology 判断是否闰年

判断是否闰年是由年表Chronology 提供的,通常情况下,我们使用ISO下的年表,是IsoChronology 。

看下代码实现

1
2
3
4
java复制代码 @Override
public boolean isLeapYear(long prolepticYear) {
return ((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);
}

好精炼的代码,值得我们研究研究

闰年的基本判定方法:
1、非整百年:能被4整除的为闰年。(如2004年就是闰年,2001年不是闰年)
2、整百年:能被400整除的是闰年。(如2000年是闰年,1900年不是闰年)

1
java复制代码((prolepticYear & 3) == 0) && ((prolepticYear % 100) != 0 || (prolepticYear % 400) == 0);

这段代码用了两个条件,这两个条件都符合,才是闰年。

  • (prolepticYear & 3) == 0
  • (prolepticYear % 100) != 0 || (prolepticYear % 400) == 0

(prolepticYear & 3) == 0 用了与运算符“&”,其使用规律如下:
两个操作数中位都为1,结果才为1,否则结果为0。

3 的二进制是011 ,prolepticYear & 3 目的是保留最后2位二进制数,然后判断是否最后两位二进制数等于0。如果等于0,证明能被4整除。闰年一定要满足是4的倍数的条件;

(prolepticYear % 100) != 0 || (prolepticYear % 400) == 0 这个就比较好理解了,看是不是100的倍数或者是不是400 倍数。

而且小虚竹发现java.time.Year#isLeap() 用的实现代码逻辑是一样的

1
2
3
java复制代码public static boolean isLeap(long year) {
return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
}

即使是巨佬写的代码,也存在代码的复用性问题

上面IsoChronology 是对Chronology接口接口的isLeapYear实现,MinguoChronology等实现类的isLeapYear,互用了IsoChronology的isLeapYear方法。

1
2
3
4
java复制代码//MinguoChronology 
public boolean isLeapYear(long prolepticYear) {
return IsoChronology.INSTANCE.isLeapYear(prolepticYear + YEARS_DIFFERENCE);
}

巨佬是有考虑复用的,在MinguoChronology等实现类已经有复用了。

java.time.Year#isLeap() 的优先级高,因为它是静态方法。isoChronology ** 可以引Year.isLeap**
Year ** 不可以引Chronology.isLeapYear** 。

博主发现在IsoChronology ** 的resolveYMD** 中已经存在了对Year.isLeap 的引用。

image-202109089047374

有的工具类会为了减少外部类依赖,重新写一次底层方法,避免外部类(或是不在一个包底下)的类依赖,这个已经用了,说不过去 。所以代码是存在复用性问题的。

实战

1
2
3
4
5
6
7
8
java复制代码		int year = 2020;
System.out.println(Year.isLeap(year));
System.out.println(IsoChronology.INSTANCE.isLeapYear(year));

LocalDate localDate = LocalDate.of(2021,9,7);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate.isLeapYear());
System.out.println(localDateTime.toLocalDate().isLeapYear());

image-2021090733986

比较日期时间的先后

基本上都有这四个比较方法::compareTo()、isBefore()、isAfter()、和equals()

比较-LocalDate

1
2
3
4
5
6
7
8
9
10
css复制代码		LocalDate localDate1 = LocalDate.of(2021, 8, 14);
// 比较指定日期和参数日期,返回正数,那么指定日期时间较晚(数字较大):13
int i = localDate1.compareTo(LocalDate.of(2021, 8, 1));
System.out.println(i);
// 比较指定日期是否比参数日期早(true为早):true
System.out.println(localDate1.isBefore(LocalDate.of(2021,8,31)));
// 比较指定日期是否比参数日期晚(true为晚):false
System.out.println(localDate1.isAfter(LocalDate.of(2021,8,31)));
// 比较两个日期是否相等:true
System.out.println(localDate1.isEqual(LocalDate.of(2021, 8, 14)));

image-202108149597

比较-LocalTime

1
2
3
4
5
6
7
8
9
10
11
java复制代码		LocalTime localTime1 = LocalTime.of(23, 26, 30);
LocalTime localTime2 = LocalTime.of(23, 26, 32);
// 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1
System.out.println(localTime1.compareTo(localTime2));

// 比较指定时间是否比参数时间早(true为早):true
System.out.println(localTime1.isBefore(localTime2));
// 比较指定时间是否比参数时间晚(true为晚):false
System.out.println(localTime1.isAfter(localTime2));
// 比较两个时间是否相等:true
System.out.println(localTime1.equals(LocalTime.of(23, 26, 30)));

image-2021081498214

比较-OffsetDateTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码		LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);
OffsetDateTime offsetDateTime1 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));
OffsetDateTime offsetDateTime3 = OffsetDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);
OffsetDateTime offsetDateTime2 = OffsetDateTime.of(localDateTime2, ZoneOffset.ofHours(8));

// 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1
System.out.println(offsetDateTime1.compareTo(offsetDateTime2));

// 比较指定时间是否比参数时间早(true为早):true
System.out.println(offsetDateTime1.isBefore(offsetDateTime2));
// 比较指定时间是否比参数时间晚(true为晚):false
System.out.println(offsetDateTime1.isAfter(offsetDateTime2));
// 比较两个时间是否相等:true
System.out.println(offsetDateTime1.equals(offsetDateTime3));

image-20210821944542

比较-OffsetTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码		LocalTime localTime1 = LocalTime.of( 13, 14, 20);
OffsetTime offsetTime1 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));
OffsetTime offsetTime3 = OffsetTime.of(localTime1, ZoneOffset.ofHours(8));

LocalTime localTime2 = LocalTime.of(13, 14, 30);
OffsetTime offsetTime2 = OffsetTime.of(localTime2, ZoneOffset.ofHours(8));
// 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1
System.out.println(offsetTime1.compareTo(offsetTime2));

// 比较指定时间是否比参数时间早(true为早):true
System.out.println(offsetTime1.isBefore(offsetTime2));
// 比较指定时间是否比参数时间晚(true为晚):false
System.out.println(offsetTime1.isAfter(offsetTime2));
// 比较两个时间是否相等:true
System.out.println(offsetTime1.equals(offsetTime3));

image-2021089109890

比较-ZonedDateTime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码		LocalDateTime localDateTime1 = LocalDateTime.of(2021, 8, 15, 13, 14, 20);
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

ZonedDateTime zonedDateTime3 = ZonedDateTime.of(localDateTime1, ZoneOffset.ofHours(8));

LocalDateTime localDateTime2 = LocalDateTime.of(2021, 8, 15, 13, 14, 30);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, ZoneOffset.ofHours(8));

// 两个时间进行比较 大返回1,小就返回-1,一样就返回0:-1
System.out.println(zonedDateTime1.compareTo(zonedDateTime2));

// 比较指定时间是否比参数时间早(true为早):true
System.out.println(zonedDateTime1.isBefore(zonedDateTime2));
// 比较指定时间是否比参数时间晚(true为晚):false
System.out.println(zonedDateTime1.isAfter(zonedDateTime2));
// 比较两个时间是否相等:true
System.out.println(zonedDateTime1.equals(zonedDateTime3));

image-20210821907094

计算日期时间的间隔

Duration 和**Period ** 都有 **between ** 方法

这个就不在重复说了,上面Duration 和Period 的常用用法里有介绍到。

TemporalAdjuster 日期校准器

序号 方法 描述
1 dayOfWeekInMonth 返回同一个月中每周的第几天
2 firstDayOfMonth 返回当月的第一天
3 firstDayOfNextMonth 返回下月的第一天
4 firstDayOfNextYear 返回下一年的第一天
5 firstDayOfYear 返回本年的第一天
6 firstInMonth 返回同一个月中第一个星期几
7 lastDayOfMonth 返回当月的最后一天
8 lastDayOfNextMonth 返回下月的最后一天
9 lastDayOfNextYear 返回下一年的最后一天
0 lastDayOfYear 返回本年的最后一天
11 lastInMonth 返回同一个月中最后一个星期几
12 next / previous 返回后一个/前一个给定的星期几
13 nextOrSame / previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
less复制代码LocalDateTime now = LocalDateTime.of(2021,9,8,0,20,13);
System.out.println("当前时间:" + now + "======>" + now.getDayOfWeek());
System.out.println("下一个周一:" + now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)));
System.out.println("上一个周一:" + now.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)));
System.out.println("下一个周五:" + now.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)));
System.out.println("上一个周五:" + now.with(TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY)));
System.out.println("本月最后一个周五:" + now.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)));
System.out.println("本月第一个周五:" + now.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY)));
System.out.println("本月第一天:" + now.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("本月最后一天:" + now.with(TemporalAdjusters.lastDayOfMonth()));
System.out.println("下月的第一天:" + now.with(TemporalAdjusters.firstDayOfNextMonth()));
System.out.println("今年的第一天:" + now.with(TemporalAdjusters.firstDayOfYear()));
System.out.println("今年的最后一天:" + now.with(TemporalAdjusters.lastDayOfYear()));
System.out.println("下一年的第一天:" + now.with(TemporalAdjusters.firstDayOfNextYear()));
System.out.println("本月的第二个周五:" + now.with(TemporalAdjusters.dayOfWeekInMonth(2,DayOfWeek.FRIDAY)));
System.out.println("两周后:" + now.with(TemporalAdjusters.ofDateAdjuster(date -> date.plusWeeks(2))));

image-202109089785

推荐相关文章

hutool日期时间系列文章

1DateUtil(时间工具类)-当前时间和当前时间戳

2DateUtil(时间工具类)-常用的时间类型Date,DateTime,Calendar和TemporalAccessor(LocalDateTime)转换

3DateUtil(时间工具类)-获取日期的各种内容

4DateUtil(时间工具类)-格式化时间

5DateUtil(时间工具类)-解析被格式化的时间

6DateUtil(时间工具类)-时间偏移量获取

7DateUtil(时间工具类)-日期计算

8ChineseDate(农历日期工具类)

9LocalDateTimeUtil(JDK8+中的{@link LocalDateTime} 工具类封装)

10TemporalAccessorUtil{@link TemporalAccessor} 工具类封装

其他

要探索JDK的核心底层源码,那必须掌握native用法

万字博文教你搞懂java源码的日期和时间相关用法

java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案

源码分析:JDK获取默认时区的风险和最佳实践

高级JAVA开发必备技能:时区的规则发生变化时,如何同步JDK的时区规则

本文转载自: 掘金

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

关于php原生开发与主流php框架使用心得 1PHP是世界

发表于 2021-11-04

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

本人是主要做php项目的,以前是从来不屑于用框架的,由于项目原因,这几年对比较流行的框架,都涉猎了一下,毕竟甲方是老大,人家制定让用什么,你必须要用,有时候你可以劝他们改变初衷,但有时候你只能遵从或者放弃,…至于因为这点原因放弃,那是不可能的,不过好在硬着头皮用了一遍之后,发现在开发效率方面确是大大提高。现在就谈一下我的一点感受。

1.PHP是世界上最好的web开发语言

PHP是世界上最好的web开发语言,这个无可辩驳,当然去掉web两个字,这毫无意义,网上很多喷子鼓吹的java,.net,python都差的远。

php有如下优点:

  • 简单易学。正是因为如此,才造就了php开发者人群的庞大和良莠不齐,但是我们不能因为阳光和空气获取起来毫不费力就说它不重要吧?这个是人的问题,不是开发语言的问题。
  • 灵活性高,和html、css高度融合。因为php本就是为web开发而生的,其他的语言都是从桌面程序和控制台程序发展起来的。
  • 服务器配置简单。网上一键配置多如过江之鲫,在没有比它更简单的了。
  • 对服务器要求低,和好兄弟mysql搭配,随便一台办公电脑都能跑的很溜。要是你用java+oracle,得专门买台服务器,你想用办公电脑当服务器,不用一百人访问就卡死你。我以前接手过一个公司的oa,同样功能用php做出来,万八个人访问起来毫无压力。

php的缺点:

  • 缺乏并发和事务处理机制。对于超大型的网站,数千万级访问来说,如果是新闻网站还好,可以采用生成html方式,基本毫无压力,但是对于一些纯纯动态的如oa(千万级访问的oa系统恕我孤陋寡闻,没见过)和复杂的事务流程来说,效率并不高,所以某宝的一些秒杀活动、12306订票、银行atm取款机等都用java或python,因为他们重要的不是浏览内容,而是事务处理,而且通常是多台服务器分布式并发执行。
  • 只支持web开发,不方便做 .exe文件。这个其实也不算缺点,人家本来就不是干这个事的,就像你拿着大刀长矛去厨房切菜非得说不如菜刀好用,让一个程序员去炒菜说不如新东方学徒,你非得这样,谁也没办法。
  • 语法不太严谨。变量不用定义就能用,这个我觉得很方便,另外php有严格模式,启用后变量必须声明才能用,你愿意你可以设置。另外说起这个我就特不习惯Python没有大括号的做法,说是优美,我去,如果要复制黏贴一段代码,光调整格式就费老大的劲,而且代码一长或者把代码拷贝的别的机器,很容易出错。

什么时候使用PHP

项目不大,如何判断这个呢,就看你需要几台服务器运行这个程序,如果有几十、几百台的话那就是大项目了。10台以内php都可以轻松应付。

  1. 中型项目减少PHP压力的方法

对于千万级的中型项目,可以采用这些方法减少压力:

  • 数据库单独一台机器,或者几台机器(关键词:主从模式、数据复制、数据同步),记住把数据库放在几个地方比把网站代码放在几个地方要容易。
  • 采用二级域名,把几个功能分别放在几台服务器。你可以把session和公共变量保存在数据库中,保证无缝隙登录整合。
  1. 原生开发VS框架

原生开发的优点:

  • 学习成本低
  • 雍余代码少,原本一个几百k搞定的小项目,用thinkphp得10M多
  • 执行效率高,毕竟框架要耗费很多资源
  • 安全性能高(如果你是高手),框架有时候存在漏洞,会让你的程序天然带入漏洞。
  • 程序配置开发灵活,不用遵从框架的路由规则

框架开发的优点:

  • 开发效率高,代码量少(这里指的是你自己写的代码)
  • 集成功能多,调用比较简单
  • 安全性能高(如果你是新手),毕竟写框架的人水平比你高,想的比你周到。
  • 代码打包后较大,因为包含了框架的代码,特别对于外包,你收了人家三万块,给你300k代码,人家总感觉不合算,用了框架,再加上一些图片,起码30M,当然你无耻点可以搞个几百M。这里你要说原生开发不会搞图片吗,可是人家一看php文件没多少,每个人文件打开才几十行代码,人家傻吗?

个人总结,对于业务逻辑不是很复杂,能用框架的还是用框架吧,确是省事很多。如果你很多功能用框架不好完成,那就用原生,没必要纠结太多。新手如果自己不能判断可以加老刘微信(jsjlaoliu),把你的功能发给我,我可以帮你判断下用什么框架合适。

  1. 常见的php框架

4.1 Thinkphp(国产)

这是我目前使用最多的框架,也是国内使用人数最多的框架,日常开发中的大多数功能基本都包含了,如果没有的可以找一下扩展插件,强烈推荐。

支持php7、php8,对我这样的版本强迫症来说,对那些不支持php7以上的框架我都不戏使它(山大方言,不屑于使用它)。

另外从下一篇开始,我准备写个简单的Thinkphp6使用教程,有需要的可以关注我。

4.2 CodeIgniter

怎么说呢,CodeIgniter3.0我用着挺好的,使用频率超过Thinkphp,结果到了4.0,好像完全变了一个软件,易用性大大降低。而且这个框架更新比较慢。对于使用php7以下的(不含)建议使用CodeIgniter3.0,php7以上的还是用Thinkphp吧。

4.3 Laravel

传说中Laravel是一个简单优雅的PHPWeb开发框架,可惜本人不怎么优雅,感受不到,反而是被那些繁琐的配置搞的好乱。其优点是大量的第三方开源库,可以快速方便的实现模块功能,安全机制非常齐全,提交表单的数据验证(验证有差不多80种,能想到的基本都有),提交数据时产生随机_token验证,避免非法提交,能避免跨域攻击;继承了登录验证、权限验证的,这个的确很方便,有兴趣的朋友可以研究一下。

4.4 Yii

Yii采用严格的OOP编写,这个是它的主要特点,奈何我这个人不太喜欢严格,Yii的组件非常多,学习成本也有点高,据说适合用于开发大型Web应用。

4.5 Yaf

Yaf,全称YetAnotherFramework,是一个C语言编写的PHP框架,是一个以PHP扩展形式提供的PHP开发框架,相比于一般的PHP框架,它更快,更轻便,据说性能很高,不过我做的小项目,感受不出来,这个框架学习起来难度有点大,使用人数也不多,但是用它的几乎都是大公司,新浪好像就用它,百度以前也用过。做小项目就不要去了解了。想要进大厂的可以研究一下。

4.6 Cakephp

z这个和thinkphp很类似,据说think开始的时候就是参考的它,既然如此,我们还是用think吧,毕竟国产的,大家看得懂,遇到问题也容易找人请教。

4.7 Zend

ZendFramework(简写ZF)是开源的,主要用于Web应用程序的开发和服务,ZF采用MVC(Model–View-Controller)架构模式来分离应用程序中不同的部分方便程序的开发和维护。框架包很大,功能很多,学习起来成本有点大,性能并不是很突出,国内用的也很少,我没怎么研究过。

好了,本文就介绍到这里,php框架有不下千种,我这里都是介绍目前比较主流的php框架,对于疏漏的,大家可以留言补充,另外国产的框架其实也不少,但是大多个人开发的,所以没多做介绍。从下一篇开始,我将带领部分菜鸟开启Thinkphp6的学习之旅。

本文转载自: 掘金

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

【算法学习】1108 IP 地址无效化(java / c

发表于 2021-11-04

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

非常感谢你阅读本文~
欢迎【👍点赞】【⭐收藏】【📝评论】~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子:https://juejin.cn/user/2771185768884824/posts 博客原创~


  1. IP 地址无效化:

给你一个有效的 IPv4 地址 address,返回这个 IP 地址的无效化版本。

所谓无效化 IP 地址,其实就是用 "[.]" 代替了每个 "."。

样例 1

1
2
3
4
5
ini复制代码输入:
address = "1.1.1.1"

输出:
"1[.]1[.]1[.]1"

样例 2

1
2
3
4
5
ini复制代码输入:
address = "255.100.50.0"

输出:
"255[.]100[.]50[.]0"

提示

  • 给出的 address 是一个有效的 IPv4 地址

分析

  • 这道算法题二当家的相信大家都能做出来,我好像也没有什么可说的。
  • 题意翻译过来其实就是把 . 全都替换成 [.] 。
  • 除了 C 和 C++ 的题解可以重点看下之外,其他的基本都是用了语言自带的API。

题解

java

1
2
3
4
5
java复制代码class Solution {
public String defangIPaddr(String address) {
return address.replace(".","[.]");
}
}

c

提示中说输入的 address 是一个有效的 IPv4 地址,这就意味着一定有三个 . 需要替换成 [.] ,可以知道返回结果比输入参数多6个字符,strlen 返回的长度不含字符串末尾隐藏字符 '\0'。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c复制代码char * defangIPaddr(char * address){
int n = strlen(address);
char *ans = malloc(n + 7);
for (int i = 0, j = 0; i < n; ++i) {
if (address[i] == '.') {
ans[j++] = '[';
ans[j++] = '.';
ans[j++] = ']';
} else {
ans[j++] = address[i];
}
}
ans[n + 6] = '\0';
return ans;
}

c++

没找到一次性替换全部的API,这里是逆序去替换的,为什么呢?如果是正序,替换以后 . 的位置会向后移动,就需要移动下标,否则会死循环。

1
2
3
4
5
6
7
8
9
10
11
cpp复制代码class Solution {
public:
string defangIPaddr(string address) {
for (int i = address.size(); i >= 0; --i) {
if (address[i] == '.') {
address.replace(i, 1, "[.]");
}
}
return address;
}
};

python

1
2
3
python复制代码class Solution:
def defangIPaddr(self, address: str) -> str:
return address.replace('.', '[.]')

go

1
2
3
go复制代码func defangIPaddr(address string) string {
return strings.ReplaceAll(address, ".", "[.]")
}

rust

1
2
3
4
5
rust复制代码impl Solution {
pub fn defang_i_paddr(address: String) -> String {
address.replace(".", "[.]")
}
}

在这里插入图片描述


原题传送门:https://leetcode-cn.com/problems/defanging-an-ip-address/


本文转载自: 掘金

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

1…421422423…956

开发者博客

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