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

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


  • 首页

  • 归档

  • 搜索

小技巧分享:在 Go 如何实现枚举?

发表于 2021-11-08

大家好,我是煎鱼。

在日常的业务工程开发中,我们常常会有使用枚举值的诉求,枚举控的好,测试值边界一遍过…

有的小伙伴会说,在 Go 语言不是有 iota 类型做枚举吗,那煎鱼你这篇文章还讲什么?

讲道理,Go 语言并没有 enum 关键字,有用过 Protobuf 等的小伙伴知道,Go 语言只是 ”有限的枚举“ 支持,我们也会用常量来定义,枚举值也需要有字面意思的映射。

示例

在一些业务场景下,是没法达到我们的诉求的。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
go复制代码type FishType int

const (
 A FishType = iota
 B
 C
 D
)

func main() {
 fmt.Println(A, B, C, D)
}

输出结果为:“0 1 2 3”。这时候就一脸懵逼了…枚举值,应该除了键以外,还得有个对应的值。也就是这个 “0 1 2 3” 分别对应着什么含义,是不是应该输出 ”A B C D“

但 Go 语言这块就没有直接的支撑了,因此这不是一个完整的枚举类型的实现。

同时假设我们传入超过 FishType 类型声明范围的枚举值,在 Go 语言中默认也不会有任何控制,是正常输出的。

上述这种 Go 枚举实现,在某种情况下是不完全的,严格意义上不能成为 enum(枚举)。

使用 String 做枚举

如果要支持枚举值的对应输出的话,我们可以通过如下方式:

1
2
3
4
5
6
7
8
9
10
11
12
go复制代码type FishType int

const (
 A FishType = iota
 B
 C
 D
)

func (f FishType) String() string {
 return [...]string{"A", "B", "C", "D"}[f]
}

运行程序:

1
2
3
4
5
6
7
8
9
10
11
12
css复制代码func main() {
 var f FishType = A
 fmt.Println(f)
 switch f {
 case A:
  fmt.Println("脑子进煎鱼了")
 case B:
  fmt.Println("记得点赞")
 default:
  fmt.Println("别别别...")
 }
}

输出结果:

1
2
css复制代码A
脑子进煎鱼了

我们可以借助 Go 中 String 方法的默认约定,针对于定义了 String 方法的类型,默认输出的时候会调用该方法。

这样就可以达到获得枚举值的同时,也能拿到其映射的字面意思。

自动生成 String

但每次手动编写还是比较麻烦的。在这一块,我们可以利用官方提供的 cmd/string 来快速实现。

我们安装如下命令:

1
bash复制代码go install golang.org/x/tools/cmd/stringer

在所需枚举值上设置 go:generate 指令:

1
2
bash复制代码//go:generate stringer -type=FishType
type FishType int

在项目根目录执行:

1
bash复制代码go generate ./...

会在根目录生成 fishtype_string.go 文件:

1
2
3
4
5
go复制代码.
├── fishtype_string.go
├── go.mod
├── go.sum
└── main.go

fishtype_string 文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码package main

import "strconv"

const _FishType_name = "ABCD"

var _FishType_index = [...]uint8{0, 1, 2, 3, 4}

func (i FishType) String() string {
 if i < 0 || i >= FishType(len(_FishType_index)-1) {
  return "FishType(" + strconv.FormatInt(int64(i), 10) + ")"
 }
 return _FishType_name[_FishType_index[i]:_FishType_index[i+1]]
}

所生成出来的文件,主要是根据枚举值和映射值做了个映射,且针对超出枚举值的场景进行了判断:

1
2
3
4
5
6
java复制代码func main() {
 var f1 FishType = A
 fmt.Println(f1)
 var f2 FishType = E
 fmt.Println(f2)
}

执行 go run . 查看程序运行结果:

1
2
3
scss复制代码$ go run .
A
FishType(4)

总结

在今天这篇文章中,我们介绍了如何在 Go 语言实现标准的枚举值,虽然有些繁琐,但整体不会太难。

也有小伙伴已经在社区中提出了 ”proposal: spec: add typed enum support“ 的提案,相信未来有机会能看到 Go 自身支持 enum(枚举)的那一天。

你平时会怎么在业务代码中实现枚举呢,欢迎大家一起留言交流:)

欢迎大家在评论区留言和交流 :)

若有任何疑问欢迎评论区反馈和交流,最好的关系是互相成就,各位的点赞就是煎鱼创作的最大动力,感谢支持。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo… 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

本文转载自: 掘金

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

3hutool源码分析:DateUtil(时间工具类)-获取

发表于 2021-11-08

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

❤️作者简介:大家好,我是小虚竹。Java领域优质创作者🏆,CSDN博客专家认证🏆,华为云享专家认证🏆

❤️技术活,该赏

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

看本篇文章前,建议先对java源码的日期和时间有一定的了解,如果不了解的话,可以先看这篇文章:

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

关联文章:

hutool实战(带你掌握里面的各种工具)目录

3hutool实战:DateUtil-获取日期的各种内容


源码分析目的

知其然,知其所以然

项目引用

此博文的依据:hutool-5.6.5版本源码

1
2
3
4
5
xml复制代码        <dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.6.5</version>
</dependency>

方法名称:DateUtil.year(java.util.Date)

方法描述

获得年的部分

源码分析一

1
2
3
4
5
6
7
8
9
java复制代码	/**
* 获得年的部分
*
* @param date 日期
* @return 年的部分
*/
public static int year(Date date) {
return DateTime.of(date).year();
}

源码的**DateTime.of(date).year()**可拆解成两部分

  • DateTime.of(date)
  • DateTime.year()第一部分,看代码很好理解,就是类型的转化
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码/**
* 转换JDK date为 DateTime
*
* @param date JDK Date
* @return DateTime
*/
public static DateTime of(Date date) {
if (date instanceof DateTime) {
return (DateTime) date;
}
return new DateTime(date);
}

第二部分,

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
java复制代码/**
* 获得年的部分
*
* @return 年的部分
*/
public int year() {
return getField(DateField.YEAR);
}

/**
* 获得日期的某个部分<br>
* 例如获得年的部分,则使用 getField(DatePart.YEAR)
*
* @param field 表示日期的哪个部分的枚举 {@link DateField}
* @return 某个部分的值
*/
public int getField(DateField field) {
return getField(field.getValue());
}

/**
* 获得日期的某个部分<br>
* 例如获得年的部分,则使用 getField(Calendar.YEAR)
*
* @param field 表示日期的哪个部分的int值 {@link Calendar}
* @return 某个部分的值
*/
public int getField(int field) {
return toCalendar().get(field);
}

```从代码跟下来,可以发现会走到**toCalendar().get(field)**

java复制代码/**

  • 转换为Calendar, 默认 {@link Locale}
  • @return {@link Calendar}
  • /
    public Calendar toCalendar() {
    return toCalendar(Locale.getDefault(Locale.Category.FORMAT));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

\*\*toCalendar()\*\*会得到Calendar,这会就好理解可以获取年了。不止可以获取年,还可以获取年,月,日,时,分,秒。详细介绍可以看这里:[万字博文教你搞懂java源码的日期和时间相关用法](https://xiaoxuzhu.blog.csdn.net/article/details/118947669)


方法名称:DateUtil.quarter(java.util.Date)
=====================================


方法描述
----


获得指定日期所属季度,从1开始计数


源码分析一
-----

java复制代码/**

  • 获得指定日期所属季度,从1开始计数
  • @param date 日期
  • @return 第几个季度
  • @since 4.1.0
  • /
    public static int quarter(Date date) {
    return DateTime.of(date).quarter();
    }
1
2
3
4
5
6
7
8
9
10
11
12

源码的\*\*DateTime.of(date).quarter()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.quarter()


[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,month()获取DateTime对应的月份,然后再做了一个简单计算。

java复制代码/**

  • 获得当前日期所属季度,从1开始计数
  • @return 第几个季度 {@link Quarter}
  • /
    public int quarter() {
    return month() / 3 + 1;
    }

/**
* 获得月份,从0开始计数
*
* @return 月份
/
public int month() {
return getField(DateField.MONTH);
}
/*

* 获得日期的某个部分

* 例如获得年的部分,则使用 getField(DatePart.YEAR)
*
* @param field 表示日期的哪个部分的枚举 {@link DateField}
* @return 某个部分的值
*/
public int getField(DateField field) {
return getField(field.getValue());
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

[getField](#getField)方法上面有介绍了,这里就不水字了。


方法名称:DateUtil.quarterEnum(java.util.Date)
=========================================


方法描述
----


获得指定日期所属季度,返回的是季度枚举对象Quarter


源码分析一
-----

java复制代码/**

  • 获得指定日期所属季度
  • @param date 日期
  • @return 第几个季度枚举
  • @since 4.1.0
  • /
    public static Quarter quarterEnum(Date date) {
    return DateTime.of(date).quarterEnum();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

源码的\*\*DateTime.of(date).quarterEnum()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.quarterEnum()


[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[quarter()](#quarter)上面有源码分析,结论是获取**获得当前日期所属季度,从1开始计数**


\*\*Quarter.of(int)\*\*就是把对应的数值转为对应的枚举Quarter

java复制代码/**

  • 获得当前日期所属季度
  • @return 第几个季度 {@link Quarter}
  • /
    public Quarter quarterEnum() {
    return Quarter.of(quarter());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.month(java.util.Date)
===================================


方法描述
----


获得月份,从0开始计数

源码分析一
-----

java复制代码/**

  • 获得月份,从0开始计数
  • @param date 日期
  • @return 月份,从0开始计数
  • /
    public static int month(Date date) {
    return DateTime.of(date).month();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).month()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.month()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得月份,从0开始计数
  • @return 月份
  • /
    public int month() {
    return getField(DateField.MONTH);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.monthEnum(java.util.Date)
=======================================


方法描述
----


获得月份,返回月份枚举对象Month

源码分析一
-----

java复制代码/**

  • 获得月份
  • @param date 日期
  • @return {@link Month}
  • /
    public static Month monthEnum(Date date) {
    return DateTime.of(date).monthEnum();
    }
1
2
3
4
5
6
7
8
9
10
11
12

源码的\*\*DateTime.of(date).monthEnum()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.monthEnum()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[month()](#DateUtil_month)是获得月份,从0开始计数


\*\*Month.of(int)\*\*就是把对应的数值转为对应的枚举Month

java复制代码/**

  • 获得月份
  • @return {@link Month}
  • /
    public Month monthEnum() {
    return Month.of(month());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

方法名称:DateUtil.weekOfYear(java.util.Date)
========================================


方法描述
----


获得指定日期是所在年份的第几周<br>
此方法返回值与一周的第一天有关,比如:<br>
2016年1月3日为周日,如果一周的第一天为周日,那这天是第二周(返回2)<br>
如果一周的第一天为周一,那这天是第一周(返回1)<br>
跨年的那个星期得到的结果总是1

源码分析一
-----

java复制代码/**

  • 获得指定日期是所在年份的第几周
  • 此方法返回值与一周的第一天有关,比如:
  • 2016年1月3日为周日,如果一周的第一天为周日,那这天是第二周(返回2)
  • 如果一周的第一天为周一,那这天是第一周(返回1)
  • 跨年的那个星期得到的结果总是1
  • @param date 日期
  • @return 周
  • @see DateTime#setFirstDayOfWeek(Week)
  • /
    public static int weekOfYear(Date date) {
    return DateTime.of(date).weekOfYear();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).weekOfYear()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.weekOfYear()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期是所在年份的第几周
  • 此方法返回值与一周的第一天有关,比如:
  • 2016年1月3日为周日,如果一周的第一天为周日,那这天是第二周(返回2)
  • 如果一周的第一天为周一,那这天是第一周(返回1)
  • 跨年的那个星期得到的结果总是1
  • @return 周
  • @see #setFirstDayOfWeek(Week)
  • /
    public int weekOfYear() {
    return getField(DateField.WEEK_OF_YEAR);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.weekOfMonth(java.util.Date)
=========================================


方法描述
----


获得指定日期是所在月份的第几周<br>

源码分析一
-----

java复制代码/**

  • 获得指定日期是这个日期所在月份的第几天
  • @param date 日期
  • @return 天
  • /
    public static int dayOfMonth(Date date) {
    return DateTime.of(date).dayOfMonth();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).year()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.monthEnum()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期是这个日期所在月份的第几天,从1开始
  • @return 天,1表示第一天
  • /
    public int dayOfMonth() {
    return getField(DateField.DAY_OF_MONTH);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.dayOfMonth(java.util.Date)
========================================


方法描述
----


获得指定日期是这个日期所在月份的第几天<br>

源码分析一
-----

java复制代码/**

  • 获得指定日期是这个日期所在月份的第几天
  • @param date 日期
  • @return 天
  • /
    public static int dayOfMonth(Date date) {
    return DateTime.of(date).dayOfMonth();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).dayOfMonth()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.dayOfMonth()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期是这个日期所在月份的第几天,从1开始
  • @return 天,1表示第一天
  • /
    public int dayOfMonth() {
    return getField(DateField.DAY_OF_MONTH);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.dayOfYear(java.util.Date)
=======================================


方法描述
----


获得指定日期是这个日期所在年的第几天

源码分析一
-----

java复制代码/**

  • 获得指定日期是这个日期所在年的第几天
  • @param date 日期
  • @return 天
  • @since 5.3.6
  • /
    public static int dayOfYear(Date date) {
    return DateTime.of(date).dayOfYear();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).dayOfYear()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.dayOfYear()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期是这个日期所在年份的第几天,从1开始
  • @return 天,1表示第一天
  • @since 5.3.6
  • /
    public int dayOfYear() {
    return getField(DateField.DAY_OF_YEAR);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.dayOfWeek(java.util.Date)
=======================================


方法描述
----


获得指定日期是星期几,1表示周日,2表示周一

源码分析一
-----

java复制代码/**

  • 获得指定日期是星期几,1表示周日,2表示周一
  • @param date 日期
  • @return 天
  • /
    public static int dayOfWeek(Date date) {
    return DateTime.of(date).dayOfWeek();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).dayOfWeek()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.dayOfWeek()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期是星期几,1表示周日,2表示周一
  • @return 星期几
  • /
    public int dayOfWeek() {
    return getField(DateField.DAY_OF_WEEK);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.dayOfWeekEnum(java.util.Date)
===========================================


方法描述
----


获得指定日期是星期几

源码分析一
-----

java复制代码/**

  • 获得指定日期是星期几
  • @param date 日期
  • @return {@link Week}
  • /
    public static Week dayOfWeekEnum(Date date) {
    return DateTime.of(date).dayOfWeekEnum();
    }
1
2
3
4
5
6
7
8
9
10
11
12

源码的\*\*DateTime.of(date).dayOfWeekEnum()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.dayOfWeekEnum()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[dayOfWeek()](#DateUtil_dayOfWeek)是获得指定日期是星期几,1表示周日,2表示周一


\*\*Week.of(int)\*\*就是把对应的数值转为对应的枚举Week

java复制代码/**

  • 获得指定日期是星期几
  • @return {@link Week}
  • /
    public Week dayOfWeekEnum() {
    return Week.of(dayOfWeek());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.hour(java.util.Date, boolean)
===========================================


方法描述
----


获得指定日期的小时数部分<br>

源码分析一
-----

java复制代码/**

  • 获得指定日期的小时数部分
  • @param date 日期
  • @param is24HourClock 是否24小时制
  • @return 小时数
  • /
    public static int hour(Date date, boolean is24HourClock) {
    return DateTime.of(date).hour(is24HourClock);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

源码的\*\*DateTime.of(date).hour(is24HourClock)\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.hour(is24HourClock)[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。


**is24HourClock ? DateField.HOUR\_OF\_DAY : DateField.HOUR**:is24HourClock 是否24小时制


**DateField.HOUR\_OF\_DAY**:小时,用于24小时制


**DateField.HOUR**:小时,用于12小时制

java复制代码/**

  • 获得指定日期的小时数部分
  • @param is24HourClock 是否24小时制
  • @return 小时数
  • /
    public int hour(boolean is24HourClock) {
    return getField(is24HourClock ? DateField.HOUR_OF_DAY : DateField.HOUR);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.minute(java.util.Date)
====================================


方法描述
----


获得指定日期的分钟数部分<br>
例如:10:04:15.250 =》 4

源码分析一
-----

java复制代码/**

  • 获得指定日期的分钟数部分
  • 例如:10:04:15.250 =》 4
  • @param date 日期
  • @return 分钟数
  • /
    public static int minute(Date date) {
    return DateTime.of(date).minute();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).minute()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.minute()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期的分钟数部分
  • 例如:10:04:15.250 =》 4
  • @return 分钟数
  • /
    public int minute() {
    return getField(DateField.MINUTE);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.second(java.util.Date)
====================================


方法描述
----


获得指定日期的秒数部分<br>

源码分析一
-----

java复制代码/**

  • 获得指定日期的秒数部分
  • @param date 日期
  • @return 秒数
  • /
    public static int second(Date date) {
    return DateTime.of(date).second();
    }
1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).second()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.second()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期的秒数部分
  • @return 秒数
  • /
    public int second() {
    return getField(DateField.SECOND);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

方法名称:DateUtil.millisecond(java.util.Date)
=========================================


方法描述
----


获得指定日期的毫秒数部分<br>

源码分析一
-----

java复制代码 /**
* 获得指定日期的毫秒数部分

*
* @param date 日期
* @return 毫秒数
*/
public static int millisecond(Date date) {
return DateTime.of(date).millisecond();
}

1
2
3
4
5
6
7
8
9

源码的\*\*DateTime.of(date).millisecond()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.millisecond()[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。

java复制代码/**

  • 获得指定日期的毫秒数部分
  • @return 毫秒数
  • /
    public int millisecond() {
    return getField(DateField.MILLISECOND);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.isAM(java.util.Date)
==================================


方法描述
----


是否为上午


源码分析一
-----

java复制代码/**

  • 是否为上午
  • @param date 日期
  • @return 是否为上午
  • /
    public static boolean isAM(Date date) {
    return DateTime.of(date).isAM();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

源码的\*\*DateTime.of(date).isAM()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.isAM()


[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。


做了一个判断**Calendar.AM == getField(DateField.AM\_PM)**

java复制代码/**

  • 是否为上午
  • @return 是否为上午
  • /
    public boolean isAM() {
    return Calendar.AM == getField(DateField.AM_PM);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.isPM(java.util.Date)
==================================


方法描述
----


是否为下午


源码分析一
-----

java复制代码/**

  • 是否为下午
  • @param date 日期
  • @return 是否为下午
  • /
    public static boolean isPM(Date date) {
    return DateTime.of(date).isPM();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

源码的\*\*DateTime.of(date).isPM()\*\*可拆解成两部分


* DateTime.of(date)
* DateTime.isPM()


[DateTime.of(date)](#DateTime_of)上面有介绍了,这里就不水字了。


第二部分,[getField](#getField)方法上面有介绍了,这里就不水字了。


做了一个判断**Calendar.PM== getField(DateField.AM\_PM)**

typescript复制代码/**

  • 是否为下午
  • @return 是否为下午
  • /
    public boolean isPM() {
    return Calendar.PM == getField(DateField.AM_PM);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisYear()
========================


方法描述
----


返回今年


源码分析一
-----

java复制代码/**

  • @return 今年
  • /
    public static int thisYear() {
    return year(date());
    }
1
2
3
4
5
6
7
8
9

源码的\*\*year(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.year() 获得年的部分 [源码分析](#DateTime_year)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisMonth()
=========================


方法描述
----


返回当前月份


源码分析一
-----

java复制代码/**

  • @return 当前月份
  • /
    public static int thisMonth() {
    return month(date());
    }
1
2
3
4
5
6
7
8
9

源码的\*\*month(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.month() 获得年的部分 [源码分析](#DateTime_month)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisMonthEnum()
=============================


方法描述
----


返回当前月份


源码分析一
-----

java复制代码/**

  • @return 当前月份 {@link Month}
  • /
    public static Month thisMonthEnum() {
    return monthEnum(date());
    }
1
2
3
4
5
6
7
8
9

源码的\*\*monthEnum(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.monthEnum() 获得年的部分 [源码分析](#DateTime_monthEnum)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisWeekOfYear()
==============================


方法描述
----


返回当前日期所在年份的第几周


源码分析一
-----

java复制代码/**

  • @return 当前日期所在年份的第几周
  • /
    public static int thisWeekOfYear() {
    return weekOfYear(date());
    }
1
2
3
4
5
6
7
8
9

源码的\*\*weekOfYear(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.weekOfYear() 获得年的部分 [源码分析](#DateTime_weekOfYear)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisWeekOfMonth()
===============================


方法描述
----


返回当前日期所在月份的第几周


源码分析一
-----

java复制代码 /**
* @return 当前日期所在月份的第几周
*/
public static int thisWeekOfMonth() {
return weekOfMonth(date());
}

1
2
3
4
5
6
7
8
9

源码的\*\*weekOfMonth(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.weekOfMonth() 获得年的部分 [源码分析](#DateTime_weekOfMonth)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisDayOfMonth()
==============================


方法描述
----


返回当前日期是这个日期所在月份的第几天


源码分析一
-----

java复制代码 /**
* @return 当前日期是这个日期所在月份的第几天
*/
public static int thisDayOfMonth() {
return dayOfMonth(date());
}

1
2
3
4
5
6
7
8
9

源码的\*\*dayOfMonth(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.dayOfMonth() 获得年的部分 [源码分析](#DateTime_dayOfMonth)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisDayOfWeek()
=============================


方法描述
----


返回当前日期是星期几


源码分析一
-----

java复制代码 /**
* @return 当前日期是星期几
*/
public static int thisDayOfWeek() {
return dayOfWeek(date());
}

1
2
3
4
5
6
7
8
9

源码的\*\*dayOfWeek(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.dayOfWeek() 获得年的部分 [源码分析](#DateTime_dayOfWeek)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisDayOfWeekEnum()
=================================


方法描述
----


返回当前日期是星期几


源码分析一
-----

java复制代码 /**
* @return 当前日期是星期几 {@link Week}
*/
public static Week thisDayOfWeekEnum() {
return dayOfWeekEnum(date());
}

1
2
3
4
5
6
7
8
9

源码的\*\*dayOfWeekEnum(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.dayOfWeekEnum() 获得年的部分 [源码分析](#DateTime_dayOfWeekEnum)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisHour(boolean)
===============================


方法描述
----


返回当前日期的小时数部分


源码分析一
-----

java复制代码 /**
* @param is24HourClock 是否24小时制
* @return 当前日期的小时数部分

*/
public static int thisHour(boolean is24HourClock) {
return hour(date(), is24HourClock);
}

1
2
3
4
5
6
7
8
9

源码的\*\*hour(date(), is24HourClock)\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.hour() 获得年的部分 [源码分析](#DateTime_hour)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisMinute()
==========================


方法描述
----


返回当前日期的分钟数部分


源码分析一
-----

java复制代码 /**
* @return 当前日期的分钟数部分

*/
public static int thisMinute() {
return minute(date());
}

1
2
3
4
5
6
7
8
9

源码的\*\*minute(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.minute() 获得年的部分 [源码分析](#DateTime_minute)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisSecond()
==========================


方法描述
----


返回当前日期的秒数部分


源码分析一
-----

java复制代码 /**
* @return 当前日期的秒数部分

*/
public static int thisSecond() {
return second(date());
}

1
2
3
4
5
6
7
8
9

源码的\*\*second(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.second() 获得年的部分 [源码分析](#DateTime_second)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.thisMillisecond()
===============================


方法描述
----


返回当前日期的毫秒数部分


源码分析一
-----

java复制代码 /**
* @return 当前日期的毫秒数部分

*/
public static int thisMillisecond() {
return millisecond(date());
}

1
2
3
4
5
6
7
8
9

源码的\*\*millisecond(date())\*\*可拆解成两部分


* DateTime.date() 获取当前时间,返回DateTime类型
* DateTime.millisecond() 获得年的部分 [源码分析](#DateTime_millisecond)


**DateTime.date()**

java复制代码/**

  • 当前时间,转换为{@link DateTime}对象
  • @return 当前时间
  • /
    public static DateTime date() {
    return new DateTime();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

方法名称:DateUtil.yearAndQuarter(java.util.Date)
============================================


方法描述
----


获得指定日期年份和季节<br>
格式:[20131]表示2013年第一季度


源码分析一
-----

java复制代码/**

  • 获得指定日期年份和季节
  • 格式:[20131]表示2013年第一季度
  • @param date 日期
  • @return Quarter ,类似于 20132
  • /
    public static String yearAndQuarter(Date date) {
    return yearAndQuarter(calendar(date));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

源码的\*\*yearAndQuarter(calendar(date))\*\*可拆解成两部分


* calendar(date):Date对象转换为Calendar对象
* yearAndQuarter(Calendar)


yearAndQuarter方法代码分析:


​ 获取年份**cal.get(Calendar.YEAR)**


​ 获取季度**cal.get(Calendar.MONTH) / 3 + 1**


​ 然后通过StringBuilder拼接字符串

java复制代码/**

  • 获得指定日期年份和季度
  • 格式:[20131]表示2013年第一季度
  • @param cal 日期
  • @return 年和季度,格式类似于20131
  • /
    public static String yearAndQuarter(Calendar cal) {
    return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方法名称:DateUtil.yearAndQuarter(java.util.Date, java.util.Date)
============================================================


方法描述
----


获得指定日期区间内的年份和季节<br>


源码分析一
-----

java复制代码/**

  • 获得指定日期区间内的年份和季节
  • @param startDate 起始日期(包含)
  • @param endDate 结束日期(包含)
  • @return 季度列表 ,元素类似于 20132
  • /
    public static LinkedHashSet yearAndQuarter(Date startDate, Date endDate) {
    if (startDate == null || endDate == null) {
    return new LinkedHashSet<>(0);
    }
    return yearAndQuarter(startDate.getTime(), endDate.getTime());
    }
1
2
3
4
5

如上面代码所示,有个判空处理


然后,写了一个while循环,把符合条件的年份和季度存到LinkedHashSet里,存完一个年份和季度的字符串后,会给开始时间增加3个月,如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环

java复制代码/**

  • 获得指定日期区间内的年份和季度

  • @param startDate 起始日期(包含)

  • @param endDate 结束日期(包含)

  • @return 季度列表 ,元素类似于 20132

  • @since 4.1.15

  • /
    public static LinkedHashSet yearAndQuarter(long startDate, long endDate) {
    LinkedHashSet quarters = new LinkedHashSet<>();
    final Calendar cal = calendar(startDate);
    while (startDate <= endDate) {

    // 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环
    quarters.add(yearAndQuarter(cal));
    
    cal.add(Calendar.MONTH, 3);
    startDate = cal.getTimeInMillis();

    }

    return quarters;
    }



**本文转载自:** [掘金](https://juejin.cn/post/7028050356995096584)

*[开发者博客 – 和开发相关的 这里全都有](https://dev.newban.cn/)*

系统架构面临的三大挑战,看 Kubernetes 监控如何解

发表于 2021-11-08

简介: 随着 Kubernetes 的不断实践落地,我们经常会遇到负载均衡、集群调度、水平扩展等问题。归根到底,这些问题背后都暴露出流量分布不均的问题。那么,我们该如何发现资源使用,解决流量分布不均问题呢?今天,我们就借助三个具体场景聊聊这一问题以及相应的解决方案。

作者|炎寻

大家好,我是阿里云云原生应用平台的炎寻,很高兴能与大家继续分享 Kubernetes 监控系列公开课。前两期公开课我们讲到了 Vol.1《通过 Kubernetes 监控探索应用架构,发现预期外的流量》、Vol.2《如何发现 Kubernetes 中服务和工作负载的异常》。

如何使用 Kubernetes 监控的拓扑来探索应用架构,使用产品采集的监控数据配置告警来发现服务性能问题。今天我们将进行第三讲《使用 Kubernetes 监控发现资源使用,流量分布不均匀的问题》,大家可以钉钉搜索钉群 31588365,加入 Kubernetes 监控答疑群进行交流。

随着 Kubernetes 的不断实践落地,我们经常会遇到越来越多问题,诸如负载均衡、集群调度、水平扩展等问题。归根到底,这些问题背后都暴露出流量分布不均的问题。那么,我们该如何发现资源使用,解决流量分布不均问题呢?今天,我们就借助三个具体场景聊聊这一问题以及相应的解决方案。

系统架构面临的挑战一:负载均衡

通常来说,对于一个业务系统,架构会有很多层,每层包含很多组件,比如服务接入、中间件、存储,我们希望每个组件的负载都是均衡的,这样性能和稳定性都是最高的,但在多语言多通信协议场景下,快速发现以下问题具备一定难度,比如:

  • 应用服务器处理的请求是否均匀?
  • 应用服务器对中间件服务实例的访问流量是否均匀?
  • 数据库各个分库分表实例读写流量是否均匀?
  • …

我们在实际工作实践中会遇到的典型场景就是负载不均衡,线上的流量转发策略或者流量转发组件自身有问题,导致应用服务各个实例接收到的请求量不均衡,部分实例处理的流量显著高于其他节点,导致这部分实例的性能相对于其他实例来说显著恶化,那么路由到这部分实例上的请求无法得到及时的响应,造成系统整体的性能和稳定性降低。

除了服务端不均匀场景之外,云上用户大多使用云服务实例,在实践中会出现应用服务各个实例处理的流量均匀,但访问云服务实例的节点出现流量不均匀,导致云服务实例整体性能和稳定性下降。通常在应用运行时整体链路梳理和特定问题节点上下游分析时,会进入该场景。

那么,我们如何快速发现问题、解决问题呢? 针对这一问题,我们可以从服务负载、请求负载这两个方面对客户端、服务端进行问题发现,判断各个组件实例服务负载和对外请求负载是否均衡。

(1)服务端负载

对于服务端负载均衡问题排查,我们需要了解服务详情,对任意特定的 Service,Deployment,DaemonSet,StatefulSet 进行更具针对性的排查。通过 Kubernetes 监控服务详情功能,我们可以看到 Pod 列表部分会列出后端的所有 Pod,在表格中我们列出了每个 Pod 在选择时间段内的请求数聚合值和请求数时序,通过对请求数一列进行排序,我们可以清楚地看到后端的流量是否均匀。

(2)客户端负载

对于客户端负载均衡问题排查,Kubernetes 监控提供集群拓扑功能,对于任意特定的 Service,Deployment,DaemonSet,StatefulSet,我们都可以查看其关联的拓扑,当选定关联关系之后,点击表格化会列出所有与问题实体关联的网络拓扑,表格每一项都是应用服务节点对外请求的拓扑关系,在表格中我们会展示每一对拓扑关系在选择时间段内的请求数聚合值和请求数时序,通过对请求数一列进行排序,可以清楚地看到特定节点作为客户端对特定的服务端访问是否流量均匀。

系统架构面临的挑战二:集群调度

在 Kubernetes 集群部署场景下,将 Pod 分发到某个节点的过程称之为调度,对于每个 Pod 来说,其调度过程包含了“根据过滤条件找候选节点”以及“找最好的节点”两个步骤,“根据过滤条件找候选节点”除了根据 Pod 和 node 的污点,忍受关系来过滤节点,还有一点非常重要的就是根据资源预留的量来过滤,比如节点的 CPU 只有 1 核的预留,那么对于一个请求 2 核的 Pod 来说该节点将被过滤。“找最好的节点”除了根据 Pod 和 node 的亲和性来选择,一般是在过滤出来的节点里面选择最空闲的。

基于上面的理论,我们在实践过程中经常会遇到一些问题:

  • 为什么集群资源使用率很低却无法调度 Pod?
  • 为什么部分节点资源使用率显著高于其他节点?
  • 为什么只有部分节点资源无法调度?
  • …

我们在实际工作实践中会遇到的典型场景就是资源热点问题,特定节点频繁发生 Pod 调度问题,整个集群资源利用率极低但是无法调度 Pod。如图,我们可以看到 Node1、Node2 已经调度满了 Pod,Node3 没有任何 Pod 调度上去,这个问题对跨 region 容灾高可用,整体的性能都有影响。我们通常在 Pod 调度失败会进入到该场景。

那么,我们该如何处理呢?

对于 Pod 无法调度的问题排查,我们通常应该关注到下面三个要点:

  • 节点有 Pod 数量调度上限
  • 节点有 CPU 请求调度上限
  • 节点有内存请求调度上限

Kubernetes 监控提供的集群节点列表展示以上三个要点。通过排序去查看各个节点是否均匀来查看资源热点问题。比如,某个节点 CPU 请求率接近 100%,那么就意味着任何对 CPU 有请求的 Pod 都无法调度到该节点上,如果说只有个别节点的 CPU 请求率接近 100%,其他节点都十分空闲,就需要检查一下该节点的资源容量和 Pod 分布,进一步排查问题。

除了节点有资源热点问题之外,容器也有资源热点问题。如图,对于一个多副本服务来说,其容器的资源使用分布也可能有资源热点问题,主要体现在 CPU 和内存使用上,CPU 在容器环境中是可压缩资源,达到上限之后只会限制,不会对容器本身生命周期造成影响,而内存在容器环境中是不可压缩资源,达到上限之后会出现 OOM,由于每个节点运行的时候虽然处理的请求量一致,但是不同请求不同参数导致的 CPU 和内存消耗可能不一样,那么这样会导致部分容器的资源出现热点,对生命周期和自动扩缩容都会造成影响。

针对容器的资源热点问题,通过理论分析,我们需要关注的要点如下:

  • CPU 是可压缩资源
  • 内存是不可压缩资源
  • Requests 用于调度
  • Limits 用于运行时资源限制隔离

Kubernetes 监控在服务详情的 Pod 列表展示以上四个要点,支持排序,通过查看各个 Pod 是否均匀来查看资源热点问题,比如某个 Pod CPU 使用/请求率接近 100%,那么就意味着可能触发自动扩缩容,如果说只有个别 Pod 的 CPU 使用/请求率接近 100%,其他节点都十分空闲,就需要检查处理逻辑,进一步排查问题。

系统架构面临的挑战三:单点问题

对于单点问题而言,其本质就是高可用问题。高可用问题解法只有一个,就是冗余,多节点,多 region,多 zone,多机房,越分散越好,越冗余越好。除此之外,在流量增长,组件压力增大的情况下,系统各组件是否可以水平扩展也成为一个重要的议题。

单点问题,应用服务只有最多 1 个节点,当该节点因为网络或者其他问题中断,无法通过重启解决时,系统崩溃,与此同时,因为只有一个节点,当流量增长超过一个节点的处理能力时,系统整体的性能表现会严重恶化,单点问题会影响系统的性能和高可用能力,针对该问题,Kubernetes监控支持查看 Service,Daemonset,StatefulSet,Deployment 的副本数,快速定位单点问题。

通过上面的介绍我们可以看到 Kubernetes 监控可以从服务端,客户端多视角支持多语言多通信协议场景下的负载均衡问题排查,与此同时容器,节点,服务的资源热点问题排查,最后通过副本数检查和流量分析支持单点问题排查。在后续的迭代过程中,我们会将这些检查点作为场景开关,一键开启之后自动检查,报警。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

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

Java高并发解决方案最全最详细总结(三)

发表于 2021-11-08

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

  1. 队列

• 采用队列是解决高并发大流量的利器

• 队列的作用就是:异步处理/流量削峰/系统解耦

• 异步处理是使用队列的一个主要原因,比如注册成功了,发优惠券/送积分/送红包/发短信/发邮件等操作都可以异步处理

• 使用队列流量削峰,比如并发下单、秒杀等,可以考虑使用队列将请求暂时入队,通过队列的方式将流量削平,变成平缓请求进行处理,避免应用系统因瞬间的巨大压力而压垮

• 使用队列实现系统解耦,比如支付成功了,发消息通知物流系统,发票系统,库存系统等,而无需直接调用这些系统;

• 队列应用场景

不是所有的处理都必须要实时处理;

不是所有的请求都必须要实时告诉用户结果;

不是所有的请求都必须100%一次性处理成功;

不知道哪个系统需要我的协助来实现它的业务处理,保证最终一致性,不需要强一致性。

常见的消息队列产品:ActiveMQ/RabbitMQ/RocketMQ/kafka

• ActiveMQ是jms规范下的一个老牌的成熟的消息中间件/消息服务器

• RabbitMQ/RocketMQ 数据可靠性极好,性能也非常优秀,在一些金融领域、电商领域使用很广泛;RocketMQ是阿里巴巴的;

• kafka主要运用在大数据领域,用于对数据的分析,日志的分析等处理,它有可能产生消息的丢失问题,它追求性能,性能极好,不追求数据的可靠性

  1. 池化

在实际开发中,我们经常会采用一些池化技术,减少资源消耗,提升系统性能。

⑴ 对象池

通过复用对象,减少对象创建和垃圾收集器回收对象的资源开销;

可以采用commons-pool2实现;

实际项目采用对象池并不常见,主要在开发框架或组件的时候会采用。

⑵ 数据库连接池

Druid/DBCP/C3P0/BoneCP

⑶ Redis连接池

JedisPool(内部基于commons-pool2 实现)

⑷ HttpClient连接池

核心实现类:PoolingClientConnectionManager

hc.apache.org/httpcompone…

⑸ 线程池

Java提供java.util.concurrent包可以实现线程池

Executors.newFixedThreadPool(8);线程数量固定

Executors.newSingleThreadExecutor();只有一个线程,避免关闭情况

Executors.newCachedThreadPool();可以自动扩容

Executors.newScheduledThreadPool(10);每隔多久执行

  1. 优化

⑴ JVM优化

设置JVM参数

-server -Xmx4g -Xms4g -Xmn256m

-XX:PermSize=128m

-Xss256k

-XX:+DisableExplicitGC

-XX:+UseConcMarkSweepGC

-XX:+CMSParallelRemarkEnabled

-XX:+UseCMSCompactAtFullCollection

-XX:LargePageSizeInBytes=128m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ruby复制代码-server VM有两种运行模式Server与Client,两种模式的区别在于,Client模式启动速度较快,Server模式启动较慢;但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多;
-Xmx2g 最大堆大小
-Xms2g 初始堆大小
-Xmn256m 堆中年轻代大小;
-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4.
-Xss 每个线程的Stack大小
-XX:+DisableExplicitGC,这个参数作用是禁止代码中显示调用GC。代码如何显示调用GC呢,通过System.gc()函数调用。如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果,相当于是没有这行代码一样。
-XX:+UseConcMarkSweepGC 并发标记清除(CMS)收集器,CMS收集器也被称为短暂停顿并发收集器;
-XX:+CMSParallelRemarkEnabled 降低标记停顿;
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩.
-XX:LargePageSizeInBytes 指定 Java heap 的分页页面大小
-XX:+UseFastAccessorMethods 原始类型的快速优化
-XX:+UseCMSInitiatingOccupancyOnly 使用手动定义的初始化定义开始CMS收集
-XX:CMSInitiatingOccupancyFraction 使用cms作为垃圾回收使用70%后开始CMS收集;

⑵ Tomcat优化

• 设置JVM参数,可以参考JVM优化参数

在tomcat的bin目录下的catalina.sh中设置jvm参数:

JAVA_OPTS=”-server -XX:+PrintGCDetails -Xmx4g -Xms4g -Xmn256m

-XX:PermSize=128m

-Xss256k

-XX:+DisableExplicitGC

-XX:+UseConcMarkSweepGC

-XX:+CMSParallelRemarkEnabled

-XX:+UseCMSCompactAtFullCollection

-XX:LargePageSizeInBytes=128m

-XX:+UseFastAccessorMethods

-XX:+UseCMSInitiatingOccupancyOnly

-XX:CMSInitiatingOccupancyFraction=70”

• 设置tomcat的线程池大小

• 设置 IO 模式

• 配置 APR

本文转载自: 掘金

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

SQL 中的行转列和列转行

发表于 2021-11-08

行转列,列转行是我们在开发过程中经常碰到的问题。行转列一般通过CASE WHEN 语句来实现,也可以通过 SQL SERVER 的运算符PIVOT来实现。用传统的方法,比较好理解。层次清晰,而且比较习惯。 但是PIVOT 、UNPIVOT提供的语法比一系列复杂的SELECT…CASE 语句中所指定的语法更简单、更具可读性。下面我们通过几个简单的例子来介绍一下列转行、行转列问题。

我们首先先通过一个老生常谈的例子,学生成绩表(下面简化了些)来形象了解下行转列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sql复制代码CREATE  TABLE [StudentScores]
(
[UserName] NVARCHAR(20), --学生姓名
[Subject] NVARCHAR(30), --科目
[Score] FLOAT, --成绩
)

INSERT INTO [StudentScores] SELECT 'Nick', '语文', 80
INSERT INTO [StudentScores] SELECT 'Nick', '数学', 90
INSERT INTO [StudentScores] SELECT 'Nick', '英语', 70
INSERT INTO [StudentScores] SELECT 'Nick', '生物', 85
INSERT INTO [StudentScores] SELECT 'Kent', '语文', 80
INSERT INTO [StudentScores] SELECT 'Kent', '数学', 90
INSERT INTO [StudentScores] SELECT 'Kent', '英语', 70
INSERT INTO [StudentScores] SELECT 'Kent', '生物', 85

如果我想知道每位学生的每科成绩,而且每个学生的全部成绩排成一行,这样方便我查看、统计,导出数据

1
2
3
4
5
6
7
8
sql复制代码SELECT
UserName,
MAX(CASE Subject WHEN '语文' THEN Score ELSE 0 END) AS '语文',
MAX(CASE Subject WHEN '数学' THEN Score ELSE 0 END) AS '数学',
MAX(CASE Subject WHEN '英语' THEN Score ELSE 0 END) AS '英语',
MAX(CASE Subject WHEN '生物' THEN Score ELSE 0 END) AS '生物'
FROM dbo.[StudentScores]
GROUP BY UserName

查询结果如图所示,这样我们就能很清楚的了解每位学生所有的成绩了

)

接下来我们来看看第二个小列子。有一个游戏玩家充值表(仅仅为了说明,举的一个小例子),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sql复制代码CREATE TABLE [Inpours]
(
[ID] INT IDENTITY(1,1),
[UserName] NVARCHAR(20), --游戏玩家
[CreateTime] DATETIME, --充值时间
[PayType] NVARCHAR(20), --充值类型
[Money] DECIMAL, --充值金额
[IsSuccess] BIT, --是否成功 1表示成功, 0表示失败
CONSTRAINT [PK_Inpours_ID] PRIMARY KEY(ID)
)
INSERT INTO Inpours SELECT '张三', '2010-05-01', '支付宝', 50, 1
INSERT INTO Inpours SELECT '张三', '2010-06-14', '支付宝', 50, 1
INSERT INTO Inpours SELECT '张三', '2010-06-14', '手机短信', 100, 1
INSERT INTO Inpours SELECT '李四', '2010-06-14', '手机短信', 100, 1
INSERT INTO Inpours SELECT '李四', '2010-07-14', '支付宝', 100, 1
INSERT INTO Inpours SELECT '王五', '2010-07-14', '工商银行卡', 100, 1
INSERT INTO Inpours SELECT '赵六', '2010-07-14', '建设银行卡', 100, 1

下面来了一个统计数据的需求,要求按日期、支付方式来统计充值金额信息。这也是一个典型的行转列的例子。我们可以通过下面的脚本来达到目的

1
2
3
4
5
6
7
8
sql复制代码SELECT
CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
CASE PayType WHEN '支付宝' THEN SUM(Money) ELSE 0 END AS '支付宝',
CASE PayType WHEN '手机短信' THEN SUM(Money) ELSE 0 END AS '手机短信',
CASE PayType WHEN '工商银行卡' THEN SUM(Money) ELSE 0 END AS '工商银行卡',
CASE PayType WHEN '建设银行卡' THEN SUM(Money) ELSE 0 END AS '建设银行卡'
FROM Inpours
GROUP BY CreateTime, PayType

如图所示,我们这样只是得到了这样的输出结果,还需进一步处理,才能得到想要的结果

)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sql复制代码SELECT
CreateTime,
ISNULL(SUM([支付宝]) , 0) AS [支付宝],
ISNULL(SUM([手机短信]) , 0) AS [手机短信],
ISNULL(SUM([工商银行卡]), 0) AS [工商银行卡],
ISNULL(SUM([建设银行卡]), 0) AS [建设银行卡]
FROM
(
SELECT
CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
CASE PayType WHEN '支付宝' THEN SUM(Money) ELSE 0 END AS '支付宝' ,
CASE PayType WHEN '手机短信' THEN SUM(Money) ELSE 0 END AS '手机短信',
CASE PayType WHEN '工商银行卡' THEN SUM(Money) ELSE 0 END AS '工商银行卡',
CASE PayType WHEN '建设银行卡' THEN SUM(Money) ELSE 0 END AS '建设银行卡'
FROM Inpours
GROUP BY CreateTime, PayType
) T
GROUP BY CreateTime

其实行转列,关键是要理清逻辑,而且对分组(Group by)概念比较清晰。上面两个列子基本上就是行转列的类型了。但是有个问题来了,上面是我为了说明弄的一个简单列子。实际中,可能支付方式特别多,而且逻辑也复杂很多,可能涉及汇率、手续费等等(曾经做个这样一个),如果支付方式特别多,我们的CASE WHEN 会弄出一大堆,确实比较恼火,而且新增一种支付方式,我们还得修改脚本如果把上面的脚本用动态SQL改写一下,我们就能轻松解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
less复制代码DECLARE @cmdText    VARCHAR(8000);
DECLARE @tmpSql VARCHAR(8000);
SET @cmdText = 'SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,' + CHAR(10);
SELECT @cmdText = @cmdText + ' CASE PayType WHEN ''' +
PayType + ''' THEN SUM(Money) ELSE 0 END AS ''' + PayType
+ ''',' + CHAR(10) FROM (SELECT DISTINCT PayType FROM Inpours ) T

SET @cmdText = LEFT(@cmdText, LEN(@cmdText) -2) --注意这里,如果没有加CHAR(10) 则用LEFT(@cmdText, LEN(@cmdText) -1)

SET @cmdText = @cmdText + ' FROM Inpours
GROUP BY CreateTime, PayType ';

SET @tmpSql ='SELECT CreateTime,' + CHAR(10);
SELECT @tmpSql = @tmpSql + ' ISNULL(SUM(' + PayType + '), 0) AS ''' +
PayType + ''',' + CHAR(10)
FROM (SELECT DISTINCT PayType FROM Inpours ) T

SET @tmpSql = LEFT(@tmpSql, LEN(@tmpSql) -2) + ' FROM (' + CHAR(10);

SET @cmdText = @tmpSql + @cmdText + ') T GROUP BY CreateTime ';
PRINT @cmdText
EXECUTE (@cmdText);

下面是通过PIVOT来进行行转列的用法,大家可以对比一下,确实要简单、更具可读性

1
2
3
4
5
6
7
8
9
10
11
12
css复制代码SELECT CreateTime, [支付宝] , [手机短信],[工商银行卡] , [建设银行卡]
FROM
(
SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,PayType, Money
FROM Inpours
) P
PIVOT (
SUM(Money)
FOR PayType IN
([支付宝], [手机短信], [工商银行卡], [建设银行卡])
) AS T
ORDER BY CreateTime

有时可能会出现这样的错误:

消息 325,级别 15,状态 1,第 9 行

‘PIVOT’ 附近有语法错误。您可能需要将当前数据库的兼容级别设置为更高的值,以启用此功能。有关存储过程 sp_dbcmptlevel 的信息,请参见帮助。

这个是因为:对升级到 SQL Server 2005 或更高版本的数据库使用 PIVOT 和 UNPIVOT 时,必须将数据库的兼容级别设置为 90 或更高。有关如何设置数据库兼容级别的信息,请参阅 sp_dbcmptlevel (Transact-SQL)。 例如,只需在执行上面脚本前加上 EXEC sp_dbcmptlevel Test, 90; 就OK了, Test 是所在数据库的名称。

下面我们来看看列转行,主要是通过UNION ALL ,MAX来实现。假如有下面这么一个表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sql复制代码Create Table ProgrectDetail
(
ProgrectName NVARCHAR(20), --工程名称
OverseaSupply INT, --海外供应商供给数量
NativeSupply INT, --国内供应商供给数量
SouthSupply INT, --南方供应商供给数量
NorthSupply INT --北方供应商供给数量
)

INSERT INTO ProgrectDetail
SELECT 'A', 100, 200, 50, 50
UNION ALL
SELECT 'B', 200, 300, 150, 150
UNION ALL
SELECT 'C', 159, 400, 20, 320
UNION ALL
SELECT 'D', 250, 30, 15, 15

我们可以通过下面的脚本来实现,查询结果如下图所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sql复制代码SELECT  ProgrectName, 'OverseaSupply' AS Supplier,
MAX(OverseaSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'NativeSupply' AS Supplier,
MAX(NativeSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'SouthSupply' AS Supplier,
MAX(SouthSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'NorthSupply' AS Supplier,
MAX(NorthSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName

)

用UNPIVOT 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码SELECT ProgrectName,Supplier,SupplyNum
FROM
(
SELECT ProgrectName, OverseaSupply, NativeSupply,
SouthSupply, NorthSupply
FROM ProgrectDetail
)T
UNPIVOT
(
SupplyNum FOR Supplier IN
(OverseaSupply, NativeSupply, SouthSupply, NorthSupply )
) P

本文转载自: 掘金

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

剑指 Offer II 054 所有大于等于节点的值之和|

发表于 2021-11-08

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

非常感谢你阅读本文~

欢迎【👍点赞】【⭐收藏】【📝评论】~

放弃不难,但坚持一定很酷~

希望我们大家都能每天进步一点点~

本文由 二当家的白帽子:https://juejin.cn/user/2771185768884824/posts 博客原创~


剑指 Offer II 054. 所有大于等于节点的值之和|538. 把二叉搜索树转换为累加树|1038. 把二叉搜索树转换为累加树:

给定一个二叉搜索树,请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

样例 1

在这里插入图片描述

1
2
3
4
5
csharp复制代码输入:
root = [4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]

输出:
[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]

样例 2

1
2
3
4
5
ini复制代码输入:
root = [0,null,1]

输出:
[1,null,1]

样例 3

1
2
3
4
5
ini复制代码输入:
root = [1,0,2]

输出:
[3,3,2]

样例 4

1
2
3
4
5
ini复制代码输入:
root = [3,2,4,1]

输出:
[7,9,4,10]

提示

  • 树中的节点数介于 0 和 104 之间。
  • 每个节点的值介于 -104 和 104 之间。
  • 树中的所有值 互不相同 。
  • 给定的树为二叉搜索树。

分析

  • 这道算法题需要翻译一下,通俗易懂一点。
  • 二叉树,大家应该比较熟悉,一般就是正序遍历,中序遍历,后序遍历,然后遍历的过程中进行一些简单的逻辑。
  • 二叉搜索树是一种特殊的二叉树,题目已经做了解释。
  • 由于要把每个节点的值替换成树中大于或者等于该节点值的所有节点值之和,根据二叉搜索树的特点,其实就是将每个节点的值替换成自己的值累加右子树的值之和。
  • 首先这一样需要遍历每个节点,但是按照什么顺序是关键,你会发现常规正序遍历,中序遍历,后序遍历都不顺畅。
  • 按照题意来看,显然最好的顺序就是先遍历右子树,然后根节点,接着左子树这样是最顺畅的,仅需要一个变量不断累加节点值,遍历到哪个节点就累加哪个节点的值,然后赋值给这个节点,这正好和中序遍历是反正的,也就是逆中序遍历。
  • 遍历这种套娃结构,一般就是递归或者循环(利用栈数据结构),递归相对比较简单,但是受调用栈限制,循环其实就是用栈这样的数据结构模拟了调用栈,本质上差不多,但是调用次数可以更多,因为调用栈的空间一般比较有限,堆空间比起来要大的多,但是实现同样的功能,逻辑上也复杂一些。
  • 无论用递归还是用栈数据结构去做循环,都需要与树结构对应的额外内存空间,有一种非常巧妙的遍历方法叫做 Morris遍历 ,可以将非递归遍历中的空间复杂度降为O (1),具体的实现二当家的已经加了详细注释,大家也可以去百科一下,做深入了解。

题解

java

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
java复制代码class Solution {
public TreeNode convertBST(TreeNode root) {
int sum = 0;
TreeNode cur = root;

while (cur != null) {
if (cur.right == null) {
// 没有比自己值大的孩子
// 轮到处理自己
sum += cur.val;
cur.val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur.left;
} else {
// 右孩子的最左边,比自己大的最小的孩子
// 也就是自己前边最后一个遍历的节点,下一个就该是自己
TreeNode leftmostOfRight = cur.right;
while (leftmostOfRight.left != null
&& leftmostOfRight.left != cur) {
leftmostOfRight = leftmostOfRight.left;
}
if (leftmostOfRight.left == null) {
// 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
leftmostOfRight.left = cur;
cur = cur.right;
} else {
// 第二次遍历到这里,说明比自己大的都遍历完了
// 解除关系,还原树结构
leftmostOfRight.left = null;
// 轮到处理自己
sum += cur.val;
cur.val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur.left;
}
}
}

return root;
}
}

c

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
c复制代码struct TreeNode* convertBST(struct TreeNode* root){
int sum = 0;
struct TreeNode *cur = root;

while (cur != NULL) {
if (cur->right == NULL) {
// 没有比自己值大的孩子
// 轮到处理自己
sum += cur->val;
cur->val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur->left;
} else {
// 右孩子的最左边,比自己大的最小的孩子
// 也就是自己前边最后一个遍历的节点,下一个就该是自己
struct TreeNode *leftmostOfRight = cur->right;
while (leftmostOfRight->left != NULL
&& leftmostOfRight->left != cur) {
leftmostOfRight = leftmostOfRight->left;
}
if (leftmostOfRight->left == NULL) {
// 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
leftmostOfRight->left = cur;
cur = cur->right;
} else {
// 第二次遍历到这里,说明比自己大的都遍历完了
// 解除关系,还原树结构
leftmostOfRight->left = NULL;
// 轮到处理自己
sum += cur->val;
cur->val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur->left;
}
}
}

return root;
}

c++

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
cpp复制代码class Solution {
public:
TreeNode* convertBST(TreeNode* root) {
int sum = 0;
TreeNode *cur = root;

while (cur != nullptr) {
if (cur->right == nullptr) {
// 没有比自己值大的孩子
// 轮到处理自己
sum += cur->val;
cur->val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur->left;
} else {
// 右孩子的最左边,比自己大的最小的孩子
// 也就是自己前边最后一个遍历的节点,下一个就该是自己
TreeNode *leftmostOfRight = cur->right;
while (leftmostOfRight->left != nullptr
&& leftmostOfRight->left != cur) {
leftmostOfRight = leftmostOfRight->left;
}
if (leftmostOfRight->left == nullptr) {
// 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
leftmostOfRight->left = cur;
cur = cur->right;
} else {
// 第二次遍历到这里,说明比自己大的都遍历完了
// 解除关系,还原树结构
leftmostOfRight->left = nullptr;
// 轮到处理自己
sum += cur->val;
cur->val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur->left;
}
}
}

return root;
}
};

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
33
34
35
python复制代码class Solution:
def convertBST(self, root: TreeNode) -> TreeNode:
total = 0
cur = root

while cur:
if not cur.right:
# 没有比自己值大的孩子
# 轮到处理自己
total += cur.val
cur.val = total
# 自己也遍历完了,所以该遍历比自己小的
cur = cur.left
else:
# 右孩子的最左边,比自己大的最小的孩子
# 也就是自己前边最后一个遍历的节点,下一个就该是自己
leftmostOfRight = cur.right
while leftmostOfRight.left and leftmostOfRight.left != cur:
leftmostOfRight = leftmostOfRight.left

if not leftmostOfRight.left:
# 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
leftmostOfRight.left = cur
cur = cur.right
else:
# 第二次遍历到这里,说明比自己大的都遍历完了
# 解除关系,还原树结构
leftmostOfRight.left = None
# 轮到处理自己
total += cur.val
cur.val = total
# 自己也遍历完了,所以该遍历比自己小的
cur = cur.left

return root

go

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
go复制代码func convertBST(root *TreeNode) *TreeNode {
sum := 0
cur := root

for cur != nil {
if cur.Right == nil {
// 没有比自己值大的孩子
// 轮到处理自己
sum += cur.Val
cur.Val = sum
// 自己也遍历完了,所以该遍历比自己小的
cur = cur.Left
} else {
// 右孩子的最左边,比自己大的最小的孩子
// 也就是自己前边最后一个遍历的节点,下一个就该是自己
leftmostOfRight := cur.Right
for leftmostOfRight.Left != nil && leftmostOfRight.Left != cur {
leftmostOfRight = leftmostOfRight.Left
}
if leftmostOfRight.Left == nil {
// 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
leftmostOfRight.Left = cur
cur = cur.Right
} else {
// 第二次遍历到这里,说明比自己大的都遍历完了
// 解除关系,还原树结构
leftmostOfRight.Left = nil
// 轮到处理自己
sum += cur.Val
cur.Val = sum
// 自己也遍历完了,所以该遍历比自己小的
cur = cur.Left
}
}
}

return root
}

rust

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
rust复制代码use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
pub fn convert_bst(root: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {
let mut sum = 0;
let mut cur = root.clone();

while cur.is_some() {
if cur.as_ref().unwrap().borrow().right.is_none() {
// 没有比自己值大的孩子
// 轮到处理自己
sum += cur.as_ref().unwrap().borrow().val;
cur.as_ref().unwrap().borrow_mut().val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur.unwrap().borrow().left.clone();
} else {
// 右孩子的最左边,比自己大的最小的孩子
// 也就是自己前边最后一个遍历的节点,下一个就该是自己
let mut leftmostOfRight = cur.as_ref().unwrap().borrow().right.clone();
while leftmostOfRight.as_ref().unwrap().borrow().left.is_some()
&& leftmostOfRight.as_ref().unwrap().borrow().left != cur {
leftmostOfRight = leftmostOfRight.unwrap().borrow().left.clone();
}
if leftmostOfRight.as_ref().unwrap().borrow().left.is_none() {
// 没有做过关联,说明第一次到这里,关联当前节点,保证遍历完比自己大的最小节点之后可以轮到自己
leftmostOfRight.unwrap().borrow_mut().left = cur.clone();
cur = cur.unwrap().borrow().right.clone();
} else {
// 第二次遍历到这里,说明比自己大的都遍历完了
// 解除关系,还原树结构
leftmostOfRight.unwrap().borrow_mut().left = Option::None;
// 轮到处理自己
sum += cur.as_ref().unwrap().borrow().val;
cur.as_ref().unwrap().borrow_mut().val = sum;
// 自己也遍历完了,所以该遍历比自己小的
cur = cur.unwrap().borrow().left.clone();
}
}
}

root
}
}

在这里插入图片描述


原题传送门:https://leetcode-cn.com/problems/w6cpku/

原题传送门:https://leetcode-cn.com/problems/convert-bst-to-greater-tree/

原题传送门:https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/


本文转载自: 掘金

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

掘力计划月度榜单|2021年10月Top作者榜公布

发表于 2021-11-08

hi,掘友们,大家好呀~

掘力计划月度榜单第十期来了~

本期榜单公布了 2021 年 10 月前端、后端、移动端三个分类下掘金社区优秀的创作者,为社区内更广大的掘友们树立榜样,期待掘友们在技术领域内更好更多地钻研技术、输出更多的优质内容,在帮助自己、他人提升的同时,也可以收获多多。

image.png

榜单的数据依据

在掘金里我们会使用掘力值来计算一个掘友在掘金社区的累计贡献值,累计贡献值的目的是为了更好地回馈优质的用户,同时也借由他们生产的优质内容帮助到更多的掘友们。掘力值也叫 Juejin Power 或者可以简写为 JP,您可以在您的个人页面上看到自己的掘力值。

掘力值的价值

  1. 掘力值会影响到你发的文章的排序,掘力值高且活跃的用户所生产的内容会被推荐给更多的用户
  2. 掘力值会影响你是否被推荐,越来越多的掘友通过关注来消费内容,因而被推荐并获得关注可以有效地提高你的影响力
  3. 掘力值会有不同的权限,随着掘力值的不断提高,越来越多的功能和权限会解锁,让你在掘金里畅行无阻

掘力值如何计算

掘力值的目的就是为了计算一名掘友的累计贡献,而掘金社区的核心目的是去分享和学习有价值的内容,因而他与您分享的内容本身的价值直接相关:

JP=likesCount+viewsCount/100

即一个用户的掘力值是他生产的专栏文章的「阅读数 除以 100」 再与「点赞数」相加的和。

需要注意的是

1
2
3
4
5
复制代码1.掘力值只计算用户在掘金内的专栏文章,即写作在掘金里的内容数据

2.掘力值每天晚上更新计算一次

3.如果作者删除了自己的专栏文章,相应的掘力值会被扣除,但是不会撤销已经获得的权限

因此 2021 年 10 月掘力值排行榜,主要从掘力值增加幅度来进行排序,这个纬度更能综合的反应作者在社区内的活跃情况和对社区建设的贡献度。

本期榜单奖励

各分类榜单

  1. 第 1 名,每人一个字节跳动咖啡杯 + 实体上榜证书 + 专属上榜海报
  2. 第2-10名,每人一个掘金抱枕 + 马克杯 + 实体上榜证书 + 专属上榜海报
  3. 第11-20名,每人一个掘金马克杯 + 实体上榜证书 + 专属上榜海报

上榜规则

1、评选范围

当月所有在掘金发布原创文章且无违规的创作者。

2、榜单评选周期

以自然月为统计周期,每月 10 日前公布上月榜单(节假日顺延)

数据统计时间:上月 1 日 00:00~上月月末 24:00

3、榜单评选维度

榜单评选由内容质量、本月发文数量、本月因创作增加的掘力值三大维度综合考量。

其中内容质量主要测评指标为点赞数量和阅读量,也就是本篇文章所增加的掘力值;本月发文数量=本月被推荐文章数量。

PS:

  • 月榜评选创作者需满足统计周期内有被推荐的原创文章此条件方可参与。
  • 上榜名单均会进行人工查验核对,最终结果以页面展示为准。

榜单公布

前端作者榜单

frontend.png

后端作者榜单

backend.png

移动端作者榜单

mobile.png

作者主页展示

前端作者展示

名次 用户昵称 个人主页链接
1 红尘炼心 juejin.cn/user/254742…
2 Sunshine_Lin juejin.cn/user/129268…
3 前端胖头鱼 juejin.cn/user/343892…
4 yck juejin.cn/user/712139…
5 阿Tya juejin.cn/user/598591…
6 若川 juejin.cn/user/141582…
7 CUGGZ juejin.cn/user/354448…
8 _Battle juejin.cn/user/870468…
9 Mancuoj juejin.cn/user/346610…
10 前端 picker juejin.cn/user/278877…
11 战场小包 juejin.cn/user/442409…
12 快跑啊小卢_ juejin.cn/user/336052…
13 黄轶 juejin.cn/user/213710…
14 前端小智 juejin.cn/user/233062…
15 陈_杨 juejin.cn/user/208432…
16 谭光志 juejin.cn/user/143341…
17 小浪努力学前端 juejin.cn/user/409861…
18 耗子君QAQ juejin.cn/user/104639…
19 云的世界 juejin.cn/user/131597…
20 海的对岸 juejin.cn/user/332452…

后端作者展示

名次 用户昵称 个人主页链接
1 李子捌 juejin.cn/user/428412…
2 一条coding juejin.cn/user/295594…
3 Java3y juejin.cn/user/343892…
4 老郑_ juejin.cn/user/330696…
5 Code皮皮虾 juejin.cn/user/144215…
6 梦想橡皮擦 juejin.cn/user/396669…
7 代码迷途 juejin.cn/user/252413…
8 海拥 juejin.cn/user/204034…
9 孤寒者 juejin.cn/user/409944…
10 王大呀呀 juejin.cn/user/536217…
11 MacroZheng juejin.cn/user/958429…
12 二当家的白帽子 juejin.cn/user/277118…
13 IT学习日记v juejin.cn/user/686575…
14 小魔童哪吒 juejin.cn/user/346527…
15 不思量自难忘 juejin.cn/user/739339…
16 Sunny_Chen juejin.cn/user/862483…
17 盆友圈的小可爱 juejin.cn/user/211521…
18 雷学委 juejin.cn/user/230419…
19 假装懂编程 juejin.cn/user/905653…
20 捡田螺的小男孩 juejin.cn/user/145101…

移动端作者主页展示

名次 用户昵称 个人主页链接
1 Android帅次 juejin.cn/user/390593…
2 岛上码农 juejin.cn/user/707878…
3 season_zhu juejin.cn/user/435372…
4 公众号iOS逆向 juejin.cn/user/356207…
5 ZJPRENO juejin.cn/user/598590…
6 weak_PG juejin.cn/user/269208…
7 TechMerger juejin.cn/user/401143…
8 凌大哥 juejin.cn/user/254742…
9 程序员喵大人 juejin.cn/user/430968…
10 小Fuคิดถึง juejin.cn/user/157415…
11 奔波儿灞取经 juejin.cn/user/140702…
12 Halifax juejin.cn/user/845182…
13 九狼 juejin.cn/user/194359…
14 小楠总 juejin.cn/user/782508…
15 fundroid juejin.cn/user/393150…
16 业志陈 juejin.cn/user/923245…
17 鸿洋 juejin.cn/user/169730…
18 轻口味 juejin.cn/user/113435…
19 小松漫步 juejin.cn/user/139823…
20 阿策小和尚 juejin.cn/user/281520…

奖品领取方式

1.请上榜作者按分类添加运营同学 V 信获取相关证书、电子海报等奖励,添加时备注「10月榜单作者」。

  • 前端:C1216296279
  • 后端:Deborah0929
  • 移动端: happyzoe0910

2.所有上榜作者请在 2021年11月17日24点前 填写问卷登记奖品收货地址,过期视为自动放弃,不补发。

3.本榜单最终解释权归掘金社区所有。

本文转载自: 掘金

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

go-zero基础组件-分布式限流tokenLimit

发表于 2021-11-08

上篇文章提到固定时间窗口限流无法处理突然请求洪峰情况,本文讲述的令牌桶线路算法则可以比较好的处理此场景。

工作原理

  1. 单位时间按照一定速率匀速的生产 token 放入桶内,直到达到桶容量上限。
  2. 处理请求,每次尝试获取一个或多个令牌,如果拿到则处理请求,失败则拒绝请求。

优缺点

优点

可以有效处理瞬间的突发流量,桶内存量 token 即可作为流量缓冲区平滑处理突发流量。

缺点

实现较为复杂

代码实现

core/limit/tokenlimit.go

分布式环境下考虑使用 redis 作为桶和令牌的存储容器,采用 lua 脚本实现整个算法流程。

redis lua 脚本

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
lua复制代码--每秒生成token数量即token生成速度
local rate = tonumber(ARGV[1])
--桶容量
local capacity = tonumber(ARGV[2])
--当前时间戳
local now = tonumber(ARGV[3])
--当前请求token数量
local requested = tonumber(ARGV[4])
--需要多少秒才能填满桶
local fill_time = capacity/rate
--向下取整,ttl为填满时间的2倍
local ttl = math.floor(fill_time*2)
--当前时间桶容量
local last_tokens = tonumber(redis.call("get", KEYS[1]))
--如果当前桶容量为0,说明是第一次进入,则默认容量为桶的最大容量
if last_tokens == nil then
last_tokens = capacity
end
--上一次刷新的时间
local last_refreshed = tonumber(redis.call("get", KEYS[2]))
--第一次进入则设置刷新时间为0
if last_refreshed == nil then
last_refreshed = 0
end
--距离上次请求的时间跨度
local delta = math.max(0, now-last_refreshed)
--距离上次请求的时间跨度,总共能生产token的数量,如果超多最大容量则丢弃多余的token
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
--本次请求token数量是否足够
local allowed = filled_tokens >= requested
--桶剩余数量
local new_tokens = filled_tokens
--允许本次token申请,计算剩余数量
if allowed then
new_tokens = filled_tokens - requested
end
--设置剩余token数量
redis.call("setex", KEYS[1], ttl, new_tokens)
--设置刷新时间
redis.call("setex", KEYS[2], ttl, now)

return allowed

令牌桶限流器定义

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
go复制代码type TokenLimiter struct {
//每秒生产速率
rate int
//桶容量
burst int
//存储容器
store *redis.Redis
//redis key
tokenKey string
//桶刷新时间key
timestampKey string
//lock
rescueLock sync.Mutex
//redis健康标识
redisAlive uint32
//redis故障时采用进程内 令牌桶限流器
rescueLimiter *xrate.Limiter
//redis监控探测任务标识
monitorStarted bool
}


func NewTokenLimiter(rate, burst int, store *redis.Redis, key string) *TokenLimiter {
tokenKey := fmt.Sprintf(tokenFormat, key)
timestampKey := fmt.Sprintf(timestampFormat, key)

return &TokenLimiter{
rate: rate,
burst: burst,
store: store,
tokenKey: tokenKey,
timestampKey: timestampKey,
redisAlive: 1,
rescueLimiter: xrate.NewLimiter(xrate.Every(time.Second/time.Duration(rate)), burst),
}
}

获取令牌

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
go复制代码func (lim *TokenLimiter) reserveN(now time.Time, n int) bool {
//判断redis是否健康
//redis故障时采用进程内限流器
//兜底保障
if atomic.LoadUint32(&lim.redisAlive) == 0 {
return lim.rescueLimiter.AllowN(now, n)
}
//执行脚本获取令牌
resp, err := lim.store.Eval(
script,
[]string{
lim.tokenKey,
lim.timestampKey,
},
[]string{
strconv.Itoa(lim.rate),
strconv.Itoa(lim.burst),
strconv.FormatInt(now.Unix(), 10),
strconv.Itoa(n),
})
// redis allowed == false
// Lua boolean false -> r Nil bulk reply
//特殊处理key不存在的情况
if err == redis.Nil {
return false
} else if err != nil {
logx.Errorf("fail to use rate limiter: %s, use in-process limiter for rescue", err)
//执行异常,开启redis健康探测任务
//同时采用进程内限流器作为兜底
lim.startMonitor()
return lim.rescueLimiter.AllowN(now, n)
}

code, ok := resp.(int64)
if !ok {
logx.Errorf("fail to eval redis script: %v, use in-process limiter for rescue", resp)
lim.startMonitor()
return lim.rescueLimiter.AllowN(now, n)
}

// redis allowed == true
// Lua boolean true -> r integer reply with value of 1
return code == 1
}

redis 故障时兜底策略

兜底策略的设计考虑得非常细节

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
go复制代码//开启redis健康探测
func (lim *TokenLimiter) startMonitor() {
lim.rescueLock.Lock()
defer lim.rescueLock.Unlock()
//防止重复开启
if lim.monitorStarted {
return
}

//设置任务和健康标识
lim.monitorStarted = true
atomic.StoreUint32(&lim.redisAlive, 0)
//健康探测
go lim.waitForRedis()
}

//redis健康探测定时任务
func (lim *TokenLimiter) waitForRedis() {
ticker := time.NewTicker(pingInterval)
//健康探测成功时回调此函数
defer func() {
ticker.Stop()
lim.rescueLock.Lock()
lim.monitorStarted = false
lim.rescueLock.Unlock()
}()

for range ticker.C {
//ping属于redis内置健康探测命令
if lim.store.Ping() {
//健康探测成功,设置健康标识
atomic.StoreUint32(&lim.redisAlive, 1)
return
}
}
}

本文转载自: 掘金

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

Springboot事件机制整合EventBus应用(事件驱

发表于 2021-11-08

事件监听机制

熟悉Spring的同学,spring提供了一套完整的事件监听机制。要了解spring不妨先熟悉一下,观察者模式。Java 从1.0版本,已经有了观察者模式的设计。下面通过一个案例来了解一下Java所提供的观察者模式。

观察者模式

观察者模式为,一个被观察者Observable(被观察主题)和多个观察者Observer,被观察者中存储了所有的观察者对象,当被观察者接收到一个外界的消息,会通知其所有的观察者。其实就是一个主题改变,订阅它的观察者都会收到相关的消息。(也可以叫做,发布订阅模式)
JDK 观察者模式
JDK 以上是观察者模式的类图。 观察者(Obsever)、被观察者(Observable)。通过源码可以看出被观察者中会维护观察者对象,观察者注册到被观察者列表后,被观察者发出的通知观察者都将收到改变。而观察者中的unpdate(Observable,Object)方法。就是通过监听被观察者发生的改变,来触发观察者的响应。
模拟老师和学生的一个场景:被观察者(老师)、观察者(学生)。

案例1.

观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码public class MyObserver implements Observer {
/**
* 观察者(学生)name
*/
private String name;
public MyObserver(Observable o, String name) {
o.addObserver(this);
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("观察者(学生)" + name + "收到作业!《" + arg + "》"+"目标的观察者数量=" + o.countObservers());
}
}

被观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class MyObserverable extends Observable {
//被观察者数据
private String data;
public String getData() {
return data;
}
/**
* 如果有如果改变
* @param data
*/
public void setData(String data) {
//更改变化状态
setChanged();
//通知注册的观察者
notifyObservers(data);
}
}

测试

1
2
3
4
5
6
7
8
9
java复制代码public static void main(String[] args) {
//1.构造被观察目标。(假如现实场景中的老师)
MyObserverable observerable = new MyObserverable();
//2.构造2个观察者实现类:添加观察者进观察目标 (现实场景中的学生,每来一个新学生,要加入老师的观察者名录中)
MyObserver observer1 = new MyObserver(observerable, "tom");
MyObserver observer2 = new MyObserver(observerable, "jerry");
//3.被观察者(老师)发布今天的作业任务。其注册的观察者们(学生们)响应。
observerable.setData("Java从入门到放弃");
}

结果:

1
2
3
java复制代码被观察者已上线....
观察者(学生)jerry收到作业!《Java从入门到放弃》目标的观察者数量=2
观察者(学生)tom收到作业!《Java从入门到放弃》目标的观察者数量=2

通过代码看到,被观察者的 ==通知注册的观察者 notifyObservers(data);==,触发消息的通知给观察者们。观测者观察到有变化后,做出改变。==update(Observable o, Object arg) {}==.

Spring事件机制(事件监听机制)

通过上面的观察者模式,我们能够了解到,其中的设计模式及思想。监听者模式有异曲同工之处,监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后做出相应的处理。事件监听机制,其实从JDK 1.1开始有的设计模式,其主要的几个基类为 事件源EventObject 、监听者EventListener、发布者(Spring)ApplicationEventPublisher
spring事件驱动模型
下面通过案例演示事件发布订阅。

案例2.

事件源:
Spring的事件源为ApplicationEvent,继承至JDK提供的EventObject 基类。

1
2
3
4
5
6
java复制代码public class MyContextEvent extends ApplicationEvent {
public MyContextEvent(Object source) {
super(source);
System.out.println("source message->"+source.toString());
}
}

监听者:
Spring的监听者为ApplicationListener,继承至JDK提供的EventListener 接口。其实EventListener 中没有任何方法定义,只是作为监听者标识。

1
2
3
4
5
6
java复制代码public class MyContextListener implements ApplicationListener<MyContextEvent> {
@Override
public void onApplicationEvent(MyContextEvent myContextEvent) {
System.out.println("listener this MyContextEvent....");
}
}

这里我们通过Spring容器的事件发布功能来实现,自动以事件的注册发布及监听。
在spring容器事件中AbstractApplicationContext继承至ConfigurableApplicationContext,
ConfigurableApplicationContext类继承至ApplicationContext。IOC容器的核心接口ApplicationContext中继承了,事件发布ApplicationEventPublisher. 其子类AbstractApplicationContext中实现了父接口ApplicationEventPublisher中的publishEvent(ApplicationEvent event)方法。

1
2
3
4
csharp复制代码//AbstractApplicationContext类中的publishEvent方法。
public void publishEvent(ApplicationEvent event) {
this.publishEvent(event, (ResolvableType)null);
}

测试

1
2
3
4
5
6
7
8
9
10
java复制代码public static void main(String[] args) {
//获取IOC容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//注册监听者
context.register(MyContextListener.class);
//刷新容器
context.refresh();
//事件发布
context.publishEvent(new MyContextEvent("publish this event ...."));
}

结果

1
2
kotlin复制代码source message->publish this event ....
listener this MyContextEvent....

在IOC容器中,添自定义事件的监听者==context.register(MyContextListener.class);==、
事件发布通过发布自定义事件== context.publishEvent(new MyContextEvent(“publish this event ….”));==自定义消息。监听者做出响应。

基于Springboot事件监听机制整合EventBus应用

前面将观察者模式以及spring的事件发布机制。通过案例代码的方式,介绍了一下。下面介绍一下基于观察者模式的一个落地实现,Google Guava的EventBus事件处理。EventBus无需实现复杂的事件、监听者、发布者。只需要通过提供的API来实现事件机制。Evnet是根据发布者发布的事件类型,监听者订阅此事件类型,来时间发布订阅模式。在使用EventBus时,我们只需将自己要发布的事件类型通过EventBus API中的public void post(Object event) 方法。监听者通过@Subscribe监听指定类型的事件。
springboot事件机制也是基于spring事件机制实现。spring boot中支持的事件类型定在org.springframework.boot.context.event包中,springboot的项目启动加载过程,是通过springboot事件触发器EventPublishingRunListener来完成。目前支持的事件类型有如下6种。

  1. ApplicationStartedEvent spring boot 启动监听类
  2. ApplicationEnvironmentPreparedEvent 环境事先准备,spring boot中的环境已经准备ok
  3. ApplicationPreparedEvent 上下文准备事件
  4. ApplicationReadyEvent 上下文已经准备ok
  5. ApplicationFailedEvent 该事件为spring boot启动失败时的操作(启动时出现异常事件)
  6. SpringApplicationEvent 获取SpringApplication
    每一步加载过程通过触发不同的事件,来完成相应的操作。这里不一一赘述,springboot的事件。感兴趣的同学可以通过翻看启动类SpringApplication.run(CommonApplication.class, args) 。SpringApplication类中的run方法来研究整个启动过程,做了哪些工作,网上的这部分的讲解也是一大堆,贴一张springboot的启动流程。
    springboot启动流程
    EventBus API的调用实现起来并不复杂,下面通过一个实际项目中的运用,结合springboot的事件发布机制,来整合基于事件驱动的项目案例。首先要清楚前面所将的Spring的事件发布机制,通过Spring的事件发布机制,整合EventBus。

思想思路:
1.通过Springboot的事件加载机制,监听ApplicationPreparedEvent上下文准备事件,在上下文准备事件时,将监听这一事件的监听者(实现spring中的ApplicationListener),添加至EventBus的事件监听者中(即 Event的监听者注册register(Object handler))。
2.添加订阅者(即实现ApplicationListener监听者)的监听者。通过注解@Subscrib实现监听。
3.事件发布。调用EventBus中的事件发布API post(Object event).发布事件。
4.订阅者响应。

案例3.

Maven 引用相应Jar包
EventBus 事件总线

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复制代码//api封装
public class MyEventBus {

/** 事件任务总线 */
private final static EventBus tiemEventBus = new EventBus();
/**
* 触发同步事件
*
* @param event
*/
public static void post(Object event) {
tiemEventBus.post(event);
}
/**
* 注册事件处理器
*
* @param handler
*/
public static void register(Object handler) {
tiemEventBus.register(handler);
}
/**
* 注销事件处理器
*
* @param handler
*/
public static void unregister(Object handler) {
tiemEventBus.unregister(handler);
}
}

消息实体类

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
java复制代码public class Message {
private MessageType messageType;
private String messageContent;

public Message(MessageType messageType, String messageContent) {
this.messageType = messageType;
this.messageContent = messageContent;
}
public MessageType getMessageType() {
return messageType;
}
public void setMessageType(MessageType messageType) {
this.messageType = messageType;
}
public String getMessageContent() {
return messageContent;
}
public void setMessageContent(String messageContent) {
this.messageContent = messageContent;
}

public enum MessageType {
OPENDOOR(1, "openDoor"),
CLOSEDOOR(2,"closeDoor");
private int code;
private String value;

MessageType(int code, String value) {
this.code = code;
this.value = value;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

事件监听者
监听ApplicationPreparedEvent上下文准备事件,即springboot加载至这一步时,将此监听者注册到EventBus中。通过抽象封装的方式,后继承的子类通过实现此类,实现订阅者的eventBus注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@Component
abstract class MyApplicationListener implements ApplicationListener<ApplicationPreparedEvent> {
/**
* ApplicationPreparedEvent 上下文准备事件
* @param applicationPreparedEvent
*/
@Override
public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) {
ConfigurableApplicationContext applicationContext = applicationPreparedEvent.getApplicationContext();
MyApplicationListener bean = applicationContext.getBean(this.getClass());
System.out.println("regist listener to eventBus...."+bean);
MyEventBus.register(bean);
}
}

订阅者(也即监听者)继承至MyApplicationListener。

1
2
3
4
5
6
7
java复制代码@Component
public class MyListentenerSubscribe extends MyApplicationListener{
@Subscribe
public void on(Message message){
System.out.println("subscribe message-> messgeType:"+message.getMessageType()+"\n messageContent:"+message.getMessageContent());
}
}

测试
这里因为是案例实现,直接在Controller实现时间的发布。真实的业务中,通业务层,具体业务触发的事件的发布。
我通过发布User

1
2
3
4
5
6
7
8
java复制代码@RestController
public class EventPublishCtrl extends LogBase {
@GetMapping("/publish")
public void publishEvent() {
log.info("this publish method...");
MyEventBus.post(new Message(Message.MessageType.OPENDOOR,"芝麻开门!"));
}
}

启动输出

2019-08-22 14:39:15.811 WARN 73026 — [ main] com.netflix.discovery.DiscoveryClient : Using default backup registry implementation which does not do anything.
2019-08-22 14:39:15.813 INFO 73026 — [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
2019-08-22 14:39:15.814 INFO 73026 — [ main] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
2019-08-22 14:39:15.818 INFO 73026 — [ main] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1566455955817 with initial instances count: 0
2019-08-22 14:39:15.820 INFO 73026 — [ main] o.s.c.n.e.s.EurekaServiceRegistry : Registering application UNKNOWN with eureka with status UP
2019-08-22 14:39:15.820 INFO 73026 — [ main] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1566455955820, current=UP, previous=STARTING]
2019-08-22 14:39:15.822 INFO 73026 — [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/kafka1:9090: registering service…
regist listener to eventBus....com.uniubi.springcloud.practice.eventBus.MyListentenerSubscribe@329a1f8d
2019-08-22 14:39:15.914 INFO 73026 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path ‘’
2019-08-22 14:39:15.915 INFO 73026 — [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 9090
2019-08-22 14:39:15.917 INFO 73026 — [ main] c.u.s.practice.PracticeApplication : Started PracticeApplication in 4.101 seconds (JVM running for 4.895)
2019-08-22 14:39:16.761 INFO 73026 — [-192.168.63.121] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet ‘dispatcherServlet’

项目的启动加载,注册相应的监听者至eventBus中。
测试事件发布

1
2
3
java复制代码2019-08-22 14:43:44.399  INFO 73026 --- [nio-9090-exec-1] c.u.springcloud.practice.common.LogBase  : this publish method...
subscribe message-> messgeType:OPENDOOR
messageContent:芝麻开门!

订阅者收到具体的消息类型,以及消息内容。
总结

springBoot的底层实现,很多都是基于事件的发布订阅模式来做的。我们日常的开发过程中,做到业务的解耦,消息的异步传输。都可以通过实现发布订阅的模式来实现。这里也是实现项目中基于事件驱动,来完成核心业务的解耦。

  • 文中的图片来自网络。如有侵权行为联系作者。

本文转载自: 掘金

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

C语言03-函数(上)

发表于 2021-11-08

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

最近,想复习一下C语言,所以笔者将会在掘金每天更新一篇关于C语言的文章! 各位初学C语言的大一新生,以及想要复习C语言/C++知识的不要错过哦! 夯实基础,慢下来就是快!

1.函数介绍

tolower:字母转小写

image.png

1
c复制代码参数:可以传ASCII码值 也可以传字符
1
2
3
4
5
6
c复制代码	printf("%c ", tolower('A'));	//a
printf("%d ", tolower('A')); //97
printf("%c ", tolower(65)); //a

// 'A' : 65
// 'a' : 97

toupper:字母转大写

image.png

1
c复制代码参数:可以传ASCII码值 也可以传字符
1
2
c复制代码printf("%d ", toupper('a'));	//65
printf("%c ", toupper('a')); //'A'

fabs() / abs() :求绝对值

image.png

image.png

1
2
3
c复制代码//注意fabs()函数返回值为double类型 且要引头文件math.h
printf("%d ", abs(-1.1)); //1
printf("%.2lf ", fabs(-1.1)); //1.10

sqrt:开平方函数

image.png

1
2
c复制代码printf("%.2lf ", sqrt(4));	//2.00
printf("%.2lf ", sqrt(8)); //2.83

pow:求平方函数

image.png

1
2
c复制代码第一个参数:底数:x  
第二个参数:次幂:y
1
2
c复制代码  printf("%.2lf ", pow(2,3));	//2的三次方 8
printf("%.2lf ", pow(3,2));//3的平方 9

strcpy函数-字符串拷贝

image.png

1
2
3
4
5
c复制代码第一个参数:目标位置
第二个参数:要拷贝的字符串---因为不作修改,所以可以加const修饰
//注意:可以不接收strcpy函数返回值
//strcpy函数返回值为:目标空间的地址
//要保证目标空间足够大
1
2
3
4
5
6
7
8
9
c复制代码int main()
{
char arr[20] = { 0 }; //目标空间
char* arr2 = "hello world";
char* ret =strcpy(arr, arr2);
printf("%s\n", arr); //hello world
printf("%s\n", ret); //hello world
return 0;
}

memset函数

image.png

将dest指向的内存块的前count个字节设置为指定值

1
2
3
4
5
6
7
c复制代码第一个参数:指针
第二个参数:要设置的指定值 可以为整数也可以为字符
第三个参数:字节数,可以为变量也可以为常量

注意:以字节为单位进行扩充
如果要接收返回值,要强制类型转化一下,因为返回类型是void*.不转换也不会出错,但是规范点比较好
也可以不接收返回值
1
2
3
4
5
6
7
8
9
c复制代码int main()
{
char arr[20] ="hello world";
int n = 5;
char *ret = (char*)memset(arr, 'x', n);
printf("%s\n", arr); //xxxxx world
printf("%s\n",ret); //xxxxx world
return 0;
}
1
2
3
4
5
6
7
8
c复制代码//想要将数组前五个元素初始化为1
//错误程序:
int main()
{
int arr[10] = {0};
memset(arr,1,5*sizeof(int));
return 0;
}

image.png


原因:memset是以字节为单位初始化

整形->4个字节 共初始化字节数为5*sizeof(int) =20个字节

所以前5个元素被初始化为0x01010101,而并非0x00000001


2.自定义函数

1.求两个数的较大值

1
2
3
4
5
6
7
8
9
10
11
12
c复制代码int get_max(int a, int b)
{
return a > b ? a : b;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int max = get_max(a, b);//
printf("%d和%d的较大者为:%d\n",a,b,max);
}

2.交换两个数的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
c复制代码//错误程序
void Swap(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("before: a = %d,b = %d\n", a, b); //20 10
Swap(a, b);
printf("after: a = %d,b = %d\n", a, b); //20 10
return 0;
}
//原因:传值时,实参a和b传给形参x,y时,形参是实参的一份临时拷贝
//改变形参变量x,y不会影响实参a和b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
c复制代码//正解->传址
void Swap(int* x, int* y)
{
int tmp =*x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("before: a = %d,b = %d\n", a, b); //20 10
Swap(&a,&b);
printf("after: a = %d,b = %d\n", a, b); //10 20
return 0;
}

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
c复制代码//写法1:
//是素数返回1,不是素数返回0
int is_prime(int n)
{
int i = 0;
for (i = 2; i < n; i++)
{
if (n %i == 0)
{
return 0;
}
}
return 1;
}
//写法2
//用布尔类型 ->引用头文件 stdbool.h
bool is_prime(int n)
{
int i = 0;
for (i = 2; i < n; i++)
{
if (n %i == 0)
{
return false;
}
}
return true;
}

法2:开平方
1
2
3
4
5
6
7
8
9
10
11
12
c复制代码int is_prime(int n)
{
int i = 0;
for (i = 2; i < sqrt(n); i++)
{
if (n %i == 0)
{
return 0;
}
}
return 1;
}

今天就先到这吧~感谢你能看到这里!希望对你有所帮助!欢迎老铁们点个关注订阅这个专题! 同时欢迎大佬们批评指正!!!

本文转载自: 掘金

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

1…397398399…956

开发者博客

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