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

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


  • 首页

  • 归档

  • 搜索

SpringBoot dev-tools 热加载自动重启以及

发表于 2021-05-27

本文介绍dev-tools 在日常开发中的基本使用以及原理讲解,dev-tools 可以通过热加载的方式实现应用的自动重启以及和浏览器配合使用,实现热更新后页面的自动刷新,提升开发效率,早点下班,少拿996福报

基本环境:

springboot 2.x ,java 8, idea/eclipse

一,自动重启以及与实现原理

1.引入依赖

1
2
3
4
5
6
7
xml复制代码<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

2.devtools 实现应用的自动重启

devtools 可以对classpath 下的所有文件进行监控,当classpath下面的文件发现改变时,进行自动重启

  • idea触发自动重启的条件为

Build -> Build Project

  • eclipse 触发自动重启的条件

保存代码

有没有觉得这里的自动重启没有那么自动,我也这么觉得,因为需要手动触发 Build 才可以,自动重启,原理在后面解释

静态文件修改不需要自动重启的,可以通过如下参数设置,排除自动加载

spring.devtools.restart.exclude=static/**,public/**

如果你使用的是 maven 或者gradle 自动重启, 需要将参数 forking 设置为enabled, devTools依赖于应用程序上下文的shutdown钩子来在重启期间关闭它。如果禁用了shutdown钩子,它就不能正常工作 (SpringApplication.setRegisterShutdownHook(false))

3.dev-tools 热加载实现原理:

Restart 技术(自动重启)采用了两个类加载器实现,对于有的class是不会被改变的,比如第三方依赖jar,这部分class被加载到一个 base 类加载器中,对你自己日常开发的class文件则被加载到另外一个 restart类加载器中。重启的时候,直接销毁掉restart类加载器,重新创建一个新的restart类加载器,从而实现快速重启的功能,因为 base 类加载器中的类没有发生改变,加载一次就好了。

如果您发现重新启动对您的应用程序来说不够快,或者遇到类加载问题,您可以考虑ZeroTurnaround 公司的重加载技术 JRebel。这些方法是在加载类时重写它们,使它们更易于重新加载

当你使用java -jar 形式运行一个springboot 项目时 dev-tools 将被自动禁用(dev-tools
根据使用的类加载器来判断是否启用,springboot以jar启动时使用的是自定义类加载器),
你也可以通过系统参数进行启用,这时dev-tools 将忽略你所使用的类加载器类型:

-Dspring.devtools.restart.enabled=true

如上方式,将启动 dev-tools, dev-tools 适合在开发环境下使用,不建议在生产中启用

springboot 很多组件都提供了缓存的功能,比如 template engines, 缓存已经编译好的模板,避免了重复解析模板文件,spring mvc 也缓存了请求头信息,用来提升访问静态文件的性能, 缓存是个很好的设计,但是如果用在开发环境,会比较容易出错,写bug将变得更简单。 在开发环境,可以通过spirng-boot-devtools 配置禁用缓存

当然,带缓存的一般都会有特定的设置可以进行禁用比如 thymeleaf, 提供了 spring.thymeleaf.cache 缓存开关,spring-boot-devtools模块不需要手动设置这些属性,而是自动应用合理的开发时配置,

更完整的默认值设置在这个文件下面:
默认值

二,页面自动刷新

页面自动刷新需要和浏览器插件配合使用

比如chrome 浏览器需要安装 LiveReload

插件安装启用.png

浏览器插件装好后,启动springboot 项目,修改完静态文件,然后触发重新编译,页面将自动刷新
触发重新编译,不同的编译器,不同的方式,和上面的自动重启是一样的

  • idea触发自动重启的条件为

Build -> Build Project

  • eclipse 触发自动重启的条件

保存代码

我们以Idea为例, 引入了dev-tools 插件的 SpringBoot 项目启动时,会同时启动一个 liveReload server 服务(这个服务为后端服务与前端浏览器插件进行交互的 websocket 服务),可以通过以下日志验证

liveReload.png

修改文件后,重新编译项目,如下图

重新编译.png

重新编译后,前端页面自动刷新,通过网络监控可以看到,这个是一个websocket协议通知触发的自动刷新

自动刷新.png

各位 未来的 IT精英,文章内容个人整理验证通过,仅供参考,如果文章有任何错误,希望指正,一起探讨交流,如果文章对你有帮助,感谢 点赞 鼓励,谢谢。转载希望表明出处

本文转载自: 掘金

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

你知道为什么要选择B+树作为数据库索引结构?谈谈你的理解

发表于 2021-05-27

背景

首先,来谈谈B树。为什么要使用B树?我们需要明白以下两个事实:

【事实1】

不同容量的存储器,访问速度差异悬殊。以磁盘和内存为例,访问磁盘的时间大概是ms级的,访问内存的时间大概是ns级的。有个形象的比喻,若一次内存访问需要1秒,则一次外存访问需要1天。所以,现在的存储系统,都是分级组织的。

最常用的数据尽可能放在更高层、更小的存储器中,只有在当前层找不到,才向更低层、更大的存储器中寻找。这也就解释了,当处理大规模数据的时候(指无法将数据一次性存入内存),算法的实际运行时间,往往取决于数据在不同存储级别之间的IO次数。因此,要想提升速度,关键在于减少IO。

【事实2】

磁盘读取数据是以数据块(block)(或者:页,page)为基本单位的,位于同一数据块中的所有数据都能被一次性全部读取出来。

换句话说,从磁盘中读1B,与读1KB几乎一样快!因此,想要提升速度,应该利用外存批量访问的特点,在一些文章中,也称其为磁盘预读。系统之所以这么设计,是基于一个著名的局部性原理:

当一个数据被用到时,其附近的数据也通常会马上被使用,程序运行期间所需要的数据通常比较集中

B树

假设有10亿条记录(100010001000),如果使用平衡二叉搜索树(Balanced Binary Search Tree, BBST),最坏的情况下,查找需要log(2, 10^9) = 30次 I/O 操作,且每次只能读出一个关键字(即如果这次读出来的关键字不是我要查找的,就要再进行一次I/O去读取数据)。如果换成B树,会是怎样的情况呢?

B 树是为了磁盘或其它辅助存储设备而设计的一种多叉平衡搜索树。**多级存储系统中使用B树,可针对外部查找,大大减少I/O次数。通过B树,可充分利用外存对批量访问的高效支持,将此特点转化为优点。**每下降一层,都以超级结点为单位(超级结点就是指一个结点内包含多个关键字),从磁盘中读入一组关键字。那么,具体多大为一组呢?

一个节点存放多少数据视磁盘的数据块大小而定,比如磁盘中1 block的大小有1024KB,假设每个关键字的大小为 4 Byte,则可设定每一组的大小m = 1024 KB / 4 Byte = 256。目前,多数数据库系统采用 m = 200~300。假设取m = 256,则B树存储1亿条数据的树的高度大概是 log(256, 10^9) = 4,也就是单次查询所需要进行的I/O次数不超过 4 次,由此大大减少了I/O次数。

一般来说,B树的根节点常驻于内存中,B树的查找过程是这样的:首先,由于一个节点内包含多个(比如,是256个)关键码,所以需要先顺序/二分来查找,如果找到则查找成功;如果失败,则根据相应的引用从磁盘中读入下一层的节点数据(这里就涉及到一次磁盘I/O),同样的在节点内顺序查找,如此往复进行…事实上,B树查找所消耗的时间很大一部分花在了I/O上,所以减少I/O次数是非常重要的。

B树的定义

B树就是平衡的多路搜索树,所谓的m阶B树,即m路平衡搜索树。根据维基百科的定义,一棵m阶B树需满足以下要求:

  • 每个结点至多含有m个分支节点(m>=2)。
  • 除根结点之外的每个非叶结点,至少含有┌m/2┐个分支。
  • 若根结点不是叶子结点,则至少有2个孩子。
  • 一个含有k个孩子的非叶结点包含k-1个关键字。(每个结点内的关键字按升序排列)
  • 所有的叶子结点都出现在同一层。实际上这些结点并不存在,可以看作是外部结点。

根据节点的分支的上下限,也可以称其为(┌m/2┐, m)树。比如,阶数m=4时,这样的B树也可以称为(2,4)树。(事实上,(2,4)树是一棵比较特殊的B树,它和红黑树有着特别的渊源!后面谈及红黑树时会谈到。)

并且,每个内部结点的关键字都作为其子树的分隔值。比如,某结点含有2个关键字(假设为a1和a2),也就是说该结点含有3个子树。那么,最左子树的关键字均小于a1;中间子树的关键字介于a1~a2;最右子树的关键字均大于a2。

示例,一棵3阶的B树是这个样子:

B树的高度(了解)

假定一棵B树非空,具有n个关键字、高度为h(令根结点为第1层)、阶数为m,那么该B树的最大高度和最小高度分别是多少?

最大高度

当树的高度最大时,则每个结点含有的关键字数应该尽量少。根据定义,根结点至少有2个孩子(即1个关键字),除根结点之外的非叶结点至少有┌m/2┐个孩子(即┌m/2┐-1个关键字),为了描述方便,这里令p = ┌m/2┐。

  • 第1层 1个结点 (含1个关键字)
  • 第2层 2个结点 (含2*(p-1)个关键字)
  • 第3层 2p个结点 (含2p*(p-1)^2个关键字)
  • …
  • 第h层 2p^(h-2)个结点

故总的结点个数n

≥ 1+(p-1)*[2+2p+2p^2+...+2p^(h-2)]
≥ 2p^(h-1)-1

从而推导出 h ≤ log_p[(n+1)/2] + 1 (其中p为底数,p=┌m/2┐)

最小高度

当树的高度最低时,则每个结点的关键字都至多含有m个孩子(即m-1个关键字),则有

n ≤ (m-1)*(1 + m + m^2 +...+ m^(h-1)) = m^h - 1

从而推导出 h ≥ log_m(n+1) (其中m为底数)

B+树

B+树的定义

B+树是B树的一个变体,B+树与B树最大的区别在于:

  • 叶子结点包含全部关键字以及指向相应记录的指针,而且叶结点中的关键字按大小顺序排列,相邻叶结点用指针连接。
  • 非叶结点仅存储其子树的最大(或最小)关键字,可以看成是索引。

一棵3阶的B+树示例:(好好体会和B树的区别,两者的关键字是一样的)

问:为什么说B+树比B树更适合实际应用中操作系统的文件索引和数据库索引?

答:

  • B+树更适合外部存储。由于内结点不存放真正的数据(只是存放其子树的最大或最小的关键字,作为索引),一个结点可以存储更多的关键字,每个结点能索引的范围更大更精确,也意味着B+树单次磁盘IO的信息量大于B树,I/O的次数相对减少。
  • MySQL是一种关系型数据库,区间访问是常见的一种情况,B+树叶结点增加的链指针,加强了区间访问性,可使用在区间查询的场景;而使用B树则无法进行区间查找。

写在最后

欢迎大家关注我的公众号【风平浪静如码】,海量Java相关文章,学习资料都会在里面更新,整理的资料也会放在里面。

觉得写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!

本文转载自: 掘金

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

王者并发课-青铜5:一探究竟-如何从synchronized

发表于 2021-05-27

欢迎来到《王者并发课》,本文是该系列文章中的第5篇。

在前面的文章《青铜4:synchronized用法初体验》中,我们已经提到锁的概念,并指出synchronized是锁机制的一种实现。可是,这么说未免太过抽象,你可能无法直观地理解锁究竟是什么?所以,本文会粗略地介绍synchronized背后的一些基本原理,让你对Java中的锁有个粗略但直观的印象。

本文将分两个部分,首先你要从Mark Word中认识锁,因为对象锁的信息存在于Mark Word中,其次通过JOL工具实际体验Mark Word的变化。

一、从Mark Word认识锁

我们知道,在HotSpot虚拟机中,一个对象的存储分布由3个部分组成:

  • 对象头(Header):由Mark Word和Klass Pointer组成;
  • 实例数据(Instance Data):对象的成员变量及数据;
  • 对齐填充(Padding):对齐填充的字节,暂时不必理会。

在这3个部分中,对象头中的Mark Word是本文的重点,也是理解Java锁的关键。Mark Word记录的是对象运行时的数据,其中包括:

  • 哈希码(identity_hashcode)
  • GC分代年龄(age)
  • 锁状态标志
  • 线程持有的锁
  • 偏向线程ID(thread)

所以,从对象头中的Mark Word看,Java中的锁就是对象头中的一种数据。在JVM中,每个对象都有这样的锁,并且用于多线程访问对象时的并发控制。

如果一个线程想访问某个对象的实例,那么这个线程必须拥有该对象的锁。首先,它需要通过对象头中的Mark Word判断该对象的实例是否已经被线程锁定。如果没有锁定,那么线程会在Mark Word中写入一些标记数据,就是告诉别人:这个对象是我的啦!如果其他线程想访问这个实例的话,就需要进入等待队列,直到当前的线程释放对象的锁,也就是把Mark Word中的数据擦除。

当一个线程拥有了锁之后,它便可以多次进入。当然,在这个线程释放锁的时候,那么也需要执行相同次数的释放动作。比如,一个线程先后3次获得了锁,那么它也需要释放3次,其他线程才可以继续访问。

下面的表格展示的是64位计算机中的对象头信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vbnet复制代码|------------------------------------------------------------------------------------------------------------|--------------------|
| Object Header (128 bits) | State |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| Mark Word (64 bits) | Klass Word (64 bits) | |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Normal |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Biased |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 | OOP to metadata object | Lightweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | Heavyweight Locked |
|------------------------------------------------------------------------------|-----------------------------|--------------------|
| | lock:2 | OOP to metadata object | Marked for GC |
|------------------------------------------------------------------------------|-----------------------------|--------------------|

从表格中,你可以看到Object Header中的三部分信息:Mark Word、Klass Word、State.

二、通过JOL体验Mark Word的变化

为了直观感受对象头中Mark Word的变化,我们可以通过 JOL(Java Object Layout) 工具演示一遍。JOL是一个不错的Java内存布局查看工具,希望你能记住它。

首先,在工程中引入依赖:

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>

在下面的代码中,master是我们创建的对象实例,方法decreaseBlood()中会执行加锁动作。所以,在调用decreaseBlood()加锁后,对象头信息应该会发生变化。

1
2
3
4
5
6
7
8
9
java复制代码 public static void main(String[] args) {
Master master = new Master();
System.out.println("====加锁前====");
System.out.println(ClassLayout.parseInstance(master).toPrintable());
System.out.println("====加锁后====");
synchronized (master) {
System.out.println(ClassLayout.parseInstance(master).toPrintable());
}
}

结果输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
python复制代码====加锁前====
cn.tao.king.juc.execises1.Master object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int Master.blood 100
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

====加锁后====
cn.tao.king.juc.execises1.Master object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 48 f9 d6 00 (01001000 11111001 11010110 00000000) (14088520)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int Master.blood 95
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total


Process finished with exit code 0

从结果中可以看到,代码在执行synchronized方法后,所打印出的object header信息由01 00 00 00、00 00 00 00变成了48 f9 d6 00、00 70 00 00等等,不出意外的话,相信你应该看不明白这些内容的含义。

所以,为了方便阅读,我们在青铜系列文章《借花献佛-JOL格式化工具》中提供了一个工具类,让输出更具可读性。借助工具类,我们把代码调整为:

1
2
3
4
5
6
7
8
9
java复制代码 public static void main(String[] args) {
Master master = new Master();
System.out.println("====加锁前====");
printObjectHeader(master);
System.out.println("====加锁后====");
synchronized (master) {
printObjectHeader(master);
}
}

输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yaml复制代码====加锁前====
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
Class Pointer: 11111000 00000000 11000001 01000011
Mark Word:
hashcode (31bit): 0000000 00000000 00000000 00000000
age (4bit): 0000
biasedLockFlag (1bit): 0
LockFlag (2bit): 01

====加锁后====
Class Pointer: 11111000 00000000 11000001 01000011
Mark Word:
javaThread*(62bit,include zero padding): 00000000 00000000 01110000 00000000 00000100 11100100 11101001 100100
LockFlag (2bit): 00

你看,这样一来,输出的结果的结果就一目了然。从加锁后的结果中可以看到,Mark Word已经发生变化,当前线程已经获得对象的锁。

至此,你应该明白,原来synchronized的背后的原理是这么回事。当然,本文所讲述只是其中的部分。出于篇幅考虑和难度控制,本文暂且不会对Java对象头中锁的含义和锁的升级等问题展开描述,这部分内容会在后面的文章中详细介绍。

以上就是文本的全部内容,恭喜你又上了一颗星✨

夫子的试炼

  • 下载JOL工具,在代码中体验工具的使用和对象信息的变化。

延伸阅读与参考资料

  • 《王者并发课》专栏文集下载:github.com/ThoughtsBet…

关于作者

专注高并发领域创作。姊妹篇小册《高并发秒杀的设计精要与实现》作者,关注公众号【MetaThoughts】,及时获取文章更新和文稿。


如果本文对你有帮助,欢迎点赞、关注、监督,我们一起从青铜到王者。

本文转载自: 掘金

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

Mybatis Log Plugin插件自动打印SQL语句

发表于 2021-05-27

这几天在做业务的时候,偶尔会遇到sql语句出错,抛出异常的问题。想排查sql的问题就要去翻日志,这是一件很痛苦的事情,尤其是日志一直在一条条输出DEBUG信息或者某条SQL语句有异常但并不在日志中报错。
如果能有一个东西自动帮我把日志中的SQL语句筛选出来,还能把参数都给我带上,那岂不是很完美。

比如日志中的这条查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码SELECT 
supply_materials.id, supply_materials.tax_code,
supply_materials.supplier_code, supply_materials.material_code,
mdm_material.material_name, supply_materials.purchase_duration,
supply_materials.material_type, supply_materials.is_valid,
supply_materials.operate_user, supply_materials.operate_time,
supply_materials.create_user, supply_materials.create_time FROM
mdm_material Right JOIN supply.supply_materials ON
mdm_material.material_code = supply_materials.material_code
where 1=1
and supply_materials.supplier_code ~* ?
and supply_materials.material_type =? and supply_materials.is_valid='有效'

==> Parameters: gy(String), 物料(String)

找到这条语句,还得再把参数与问号一一对应真的好折磨人的。
于是找到了这个插件——Mybatis Log Plugin
安装好以后看看效果吧:

会自动打印出刚刚执行的sql语句,并把参数也带了出来。非常好用了可以说是。

接下来就来介绍这个东西怎么安装和使用。

  1. Idea中直接marketPlace搜Mybatis Log Plugin,安装;或者在互联网上找到插件的压缩文件并下载,然后Install Plugins From Disk安装上这个jar包就行。

具体方法如下:将下载好的jar文件放在某个地方,最好是专门放IDEA插件的文件夹方便以后再找。然后,settings->plugins->右上角小齿轮中的nstall Plugins From Disk->选中该文件->apply->OK

  1. 可能会提示Restart IDEA,重启再打开下面的菜单里就有了。如果没有就去View ——>Tool Windows里面找找吧。
    完结,撒花!

本文转载自: 掘金

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

MySQL 数据表优化设计(一):选择合适的数据类型

发表于 2021-05-26

MySQL支持大量的数据类型,选择正确的类型对性能十分关键。本篇介绍了MySQL 的数据类型选择原则,可以根据这些基本的原则确定数据表字段的具体数据类型。

小而美

通常来说,尽可能使用占用存储空间小的数据类型来存储数据。这类数据类型通常也会更快,并且占用的磁盘空间、内存乃至缓存都更小,而且占用的 CPU 处理周期也少。

但是,务必准确估计要存储的数据值的范围。因为在数据表结构的多个地方扩充数据范围会是一个痛苦且耗时的过程。如果在犹豫哪种数据类型合适,那就选择你认为不会超出范围的最小空间的类型(在系统早期或者数据表 数据不多的情况下也可以进行调整)。

简单至上

数据类型越简单意味着处理数据的 CPU 周期越少。例如,整型相比字符型而言,处理起来更容易,这是因为字符集和比对使得字符的比较更复杂。举两个例子:应该使用 MySQL 内置的类型来存储时间和日期,而不是字符串。IP 地址也应该使用整型存储。

避免空值

很多数据表都是要可为空的列,虽然在应用中并不需要存储缺省值NULL。通常来说,指定列为 NOT NULL 会比存储 NULL 要更优。

MySQL 对于涉及到可为空的列优化起来更为困难,这是因为空值列使得索引、索引统计和值比较都变得复杂。而且,可为空的列占据的存储空间更大,且需要特殊的处理。如果在可为空的列上指定了索引,这会需要每个索引入口多一个额外的字节,甚至会导致 MyISAM 引擎固定大小的索引转换为可变大小的索引(例如对整数型字段做单列索引)。
不过,将 NULL 列转换为 NOT NULL列的性能改进通常并不大。因此,除非已经发现了 NULL 列对性能有很大的影响,否则不要优先去对已有的数据表结构进行改动。但是,如果需要对列构建索引,那应该尽量避免该列值可以为空,通常好的习惯是直接设置该列为 NOT NULL。

当然,也有例外,例如在 InnoDB 中仅仅使用了一个 bit 来存储 NULL 值,因此对大量数据存储来说可以有效节省空间,但是如果是 MyISAM 引擎就不是这样了。

选择数据类型的步骤

选择数据类型的第一步是决定数据列使用哪种常用的数据类型来表示,是数值型、字符串型还是时间类型。通常直接选择就挺不错的,但是在某些情况下会有特殊(比如金额、时间戳)。

第二步就是选择具体的类型。MySQL对于同一种数据类型会有多种存储方式,基于数据值范围、精度以及存储的物理空间,而还有些数据类型有一些特殊的属性。

例如,DATETIME 和 TIMESTAMP 都可以存储时间和日期,都可以精确到秒。然而,TIMESTAMP 类型只需要一半的存储空间,并且包括了时区信息,还支持自动更新。但另一方面,它存储的时间范围更小,它的这些特殊特性可能变成障碍。

再来看看基本数据类型。MySQL 支持数据类型的别名,例如 INTEGER,BOOL 和 NUMERIC。这些仅仅是别名,虽然看起来会让人困惑,但是实际上对性能没有影响。如果使用了别名数据类型创建数据表,可回忆使用 SHOW CREATE TABLE,可以看到实际上 MySQL 会转换为基础数据类型,而不是别名。

结语:MySQL 的数据表示方式很多,建议了解常用的数据类型的存储范围,占据的字节数,尽可能地根据产品预估数据值范围或长度,选择合适的数据类型,从而在创建表一开始就注重性能。后期再来调整的代价往往超出设计之初付出的细致思考的时间成本。

本文转载自: 掘金

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

SpringBoot 项目添加抵御跨站防御脚本(XSS)攻击

发表于 2021-05-26

一、XSS攻击

百度百科.
XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。

二、导入依赖库

因为Hutool工具包带有XSS转义的工具类,所以我们要导入Hutool,然后利用servlet规范提供的请求包装类,定义数据转义功能。

1
2
3
4
5
html复制代码<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.0</version>
</dependency>

三、请求定义包装类

我们平时写Web项目遇到的HttpservletRequest,它其实是个接口。如果我们想要重新定义请求类,扩展这个接口是最不应该的。因为HttpservletRequest接口中抽象方法太多了,我们逐一实现起来太耗费时间。所以我们应该挑选一个简单一点的自定义请求类的方式。那就是继承HttpServletRequestwrapper父类。
JavaEE只是一个标准,具体的实现由各家应用服务器厂商来完成。比如说Tomcat在实现servlet规范的时候,就自定义了HttpservletRequest接口的实现类。同时JavaEE规范还定义了HttpServletRequestwrapper,这个类是请求类的包装类,用上了装饰器模式。不得不说这里用到的设计模式真的非常棒,无论各家应用服务器厂商怎么去实现HttpServletRequest接口,用户想要自定义请求,只需要继承HttpServletRequestwrapper,对应覆盖某个方法即可,然后把请求传入请求包装类,装饰器模式就会替代请求对象中对应的某个方法。用户的代码和服务器厂商的代码完全解耦,我们不用关心HttpServletRequest接口是怎么实现的,借助于包装类我们可以随意修改请求中的方法。
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
java复制代码
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}

@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
return value;
}

@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
values[i] = value;
}
}
return values;
}

@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> parameters = super.getParameterMap();
Map<String, String[]> map = new LinkedHashMap<>();
if (parameters != null) {
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
values[i] = value;
}
map.put(key, values);
}
}
return map;
}

@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
return value;
}

@Override
public ServletInputStream getInputStream() throws IOException {
InputStream in = super.getInputStream();
StringBuffer body = new StringBuffer();
InputStreamReader reader = new InputStreamReader(in, Charset.forName("UTF-8"));
BufferedReader buffer = new BufferedReader(reader);
String line = buffer.readLine();
while (line != null) {
body.append(line);
line = buffer.readLine();
}
buffer.close();
reader.close();
in.close();

Map<String, Object> map = JSONUtil.parseObj(body.toString());
Map<String, Object> resultMap = new HashMap(map.size());
for (String key : map.keySet()) {
Object val = map.get(key);
if (map.get(key) instanceof String) {
resultMap.put(key, HtmlUtil.filter(val.toString()));
} else {
resultMap.put(key, val);
}
}
String str = JSONUtil.toJsonStr(resultMap);
final ByteArrayInputStream bain = new ByteArrayInputStream(str.getBytes());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bain.read();
}

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener listener) {
}
};
}

}

四、创建过滤器,把所用请求对象传入包装类

为了让刚刚定义的包装类生效,我们还要在 com.example .emos.wx.config.xss中创建xssFilter过滤器。过滤器拦截所有请求,然后把请求传入包装类,这样包装类就能覆盖所有请求的参数方法,用户从请求中获得数据,全都经过转义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码package com.example.emos.wx.config.xss;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class XssFilter implements Filter {

public void init(FilterConfig config) throws ServletException {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
(HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}

@Override
public void destroy() {
}
}

五、给主类添加注解

给SpringBoot主类添加@servletcomponentscan注解。
在这里插入图片描述

六、测试拦截XSS脚本

1
2
xml复制代码我是通过传入一段<script>alert("xss脚本攻击")</script>脚本,
看返回结果是否可以将其中的html标签过滤掉

测试结果:
在这里插入图片描述
如上图所述,参数中的标签被过滤掉,返回的是去掉标签的字符!

本文转载自: 掘金

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

纯Java我们依然可以实现滑动时间窗口限流算法|Java 刷

发表于 2021-05-26

本文正在参加「Java主题月 - Java 刷题打卡」,详情查看 活动链接

一、题目描述

最近的请求次数

写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请你实现 RecentCounter 类:

RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。

示例

输入:
[“RecentCounter”, “ping”, “ping”, “ping”, “ping”]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]

解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002]

二、思路分析

  • 相信在web开发中我们都有接触过对接口的一种限制,我们统称为限流。我们常见的限流算法有【固定时间窗口算法】、【滑动时间窗口算法】、【漏桶算法】、【令牌桶算法】
  • 此题就是让我们实现一种时间窗口限流算法。如果是网络开发我们可能会使用redis等中间件作为我们流量存储的载体。

  • 但是我们这是算法场景。使用redis这是不现实的。
  • 不考虑redis的情况下,在java中本身就为我们提供了这样的数据结构。想想我们在redis中实现也无非通过redis提供的list数据结构来存储我们的数据的。今天我们同样可以使用java的Queue类来实现

image-20210524193122443

  • 首先我们得理解队列的特性FIFO 。我们先加入的1会随着后面的元素的添加逐渐跑到最前面。
  • 而本题中正好是将时间戳加入到队列中的。那么我们可以每次加入元素后就开始检索队列头部元素判断时间戳是否超时。未超时的留在队列中。最后留在队列中的元素就是我们的单位时间内的有效请求
常用方法 作用 失败时措施
add 向队列中添加一个元素到队尾 抛出错误
remove 将队首元素删除并返回 抛出错误
element 获取队首元素,和remove不同的是不会剔除 抛出错误
offer 添加一个元素到队尾 默认值
poll 获取队首元素并删除 默认值
peek 获取队首元素但是不删除 默认值

三、AC 代码

队列实现

  • 基于队列实现我们很好理解,这个概念和我们的滑动时间窗口算法基本是吻合的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码class RecentCounter {

Queue<Integer> q;
public RecentCounter() {
q = new LinkedList();
}

public int ping(int t) {
q.add(t);
//每次有请求进来就会追加队尾,同时开始剔除时间窗口外的请求
while (q.peek() < t - 3000)
q.poll();
return q.size();
}
}

image-20210525140807345

  • 在性能综合上看表现还算不错。

set实现

  • 除了队列以外我们还可以使用set来实现。而treeset恰好就是顺序存储的。实现和队列一样只不过队列换成了set 。笔者认为通过set实现代码是真的简洁
1
2
3
4
5
java复制代码public int ping(int t) {
set.add(t);
set.removeIf(item->t - item > 3000);
return set.size();
}

image-20210525141033486

  • 不过这个性能是真的差,这里大家可以当做一种参考。抛砖引玉

set优化

  • set执行效率真的差主要原因是我追求代码的简洁了。稍加改动效果大不一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public int ping(int t) {
set.add(t);
/*set = set.stream().filter(item -> {
return t - item <= 3000;
}).collect(Collectors.toSet());*/
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
if (t - iterator.next() > 3000) {
iterator.remove();
} else {
break;
}
}
return set.size();
}

image-20210525141307231

  • 但是执行速度降了下来。

四、总结

  • 队列和set两种方式各有优缺点。队列在时间和内存上总体上比较平稳。set内存空间占用少。但是速度很慢

点赞、关注哈哈哈

本文转载自: 掘金

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

小傅哥,一个有“副业”的码农!

发表于 2021-05-26

作者:小傅哥

博客:bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!😄

一、简介

小傅哥,一线互联网 Java 工程师、架构师,开发过交易、营销类项目,实现过运营、活动类项目,设计过中间件,组织过系统重构,编写过技术专利。不仅从事业务系统的开发工作,也经常做一些字节码插桩类的设计和实现,对架构的设计和落地有丰富的经验。在热衷于Java语言的同时,也喜欢研究中继器、I/O板卡、C#和PHP,是一个技术活跃的折腾者。

13年毕业的我和大多数同期毕业的小伙伴一样,经历过;校企合作的培训、传统外包的求职、平米小屋的蜗居、工作跳槽的应聘,以及逐步在互联网大厂中承担起 Java 工程师、架构师的职责。这些经历让我在技术职业发展中不断的积累、沉淀和成长,直至目前完成了个人第一本技术书籍的出版。

希望我接下来关于这些经历和经验的分享,也能帮助你在求职和职场发展中少走一些弯路,增加一些成长借鉴。

二、大学

给师弟和师妹出出招!

如果我的读者里有一些在校大学生,或者有你的师弟或者喜欢的师妹在校的话,那么你或者他喜欢编程,但在学习的路上一直没有找到方向,接下来的这段内容,可能会对你有一些帮助和指导。

大学阶段怎么学这突如其来的技术编程,看着课本上的C++已经有点战战兢兢,老师讲的课程内容不像是传道更像是劝退,所有别人明明已经可以运行出来的程序,只要自己写就会出现各种问题。我该从哪下手,怎么下手,老师让我问不会的,我都不知道哪不会。

可能这就是大多数同学学习编程的经历,以我过来人的学习经验来说,这个阶段其实会留下一部分有兴趣爱好的同学,巧妙的弄走一批学着痛苦的伙伴。这不是因为谁聪明、谁数学好、谁英语强导致的结果,而是坚持学习和本身的兴趣喜好导致。

就像我们新买回来一个自行车,那么是拆了看看轮子怎么卸下来更优雅还是骑上走几圈浪一浪呢。当然你肯定知道要走起来,让自行车在自己的屁股下奔跑奔跑,感受下沙际春风卷物华,意行聊复到君家。

所以呢,刚接触来的编程,最重要的是先把代码跑起来,把遇到的bug、问题、异常,蓝屏不算,那你得换电脑了,这些都一一解决掉。好,现在已经度过第一个阶段,就是学会了怎么Ctrl+C、Ctrl+V,把别人的代码复制过来,运行运行。接下来就是需要大量的编写阶段,要多大量呢,以我的经验来看,整个大学敲了不下20万行代码,毕业找工作真的太容易了。

20万行代码很多?其实在你学习的阶段这些代码量并不多,尤其是你写的各种属性、方法、调用、展示,以及在学习编程过程中需要熟练掌握的API、不同功能的方法片段,就像求个水仙花、兔子生兔子、杨辉三角等等,都是你在初学编程阶段的下手菜。那么这个阶段就是学习编程的蓄力阶段,主要是对编程技术的基础的夯实,以及多学习与数学相关的内容,比如;数据结构、算法逻辑,这些知识对将来在技术上的提升和造诣都是非常好的基石。

当你能坚持做一件事就把一件事做好,事事,事必躬亲、亲力亲为,想以后“为所欲为”都有可能。

三、培训

你愿意参加培训吗?

我参加过,参加过校企合作的就业培训、参加过入职前期的岗前培训,还有一次是在校期间大学聘请的外部有开发经验的工程师培训,似乎只有最开始的这次培训是让我收获最大的,后面的几次培训不断的重复Java基础,可能这样的课程对于连续几次都没有入门Java的同学是有帮助的,但对于很大一部分想提升技术的同学来讲,还是有些浪费时间的。

通过这样的经历想给大家表达的是培训,不一定好或者不好,如果是在校期间就能参加一些培训,那么还是非常能提升个人学习的能力的,也能开拓对技术的认知和视野范围。但如果因为刚一毕业就找不到工作,报名去参加Java培训来说,处于这个毕业需要就业阶段去参加培训就会显得非常匆忙,也会有不小的压力。因为你需要为培训还需要花家里一笔不小的费用而感觉惭愧,也因为有同期班里其他同学找到一份不错的工作而失衡。

其实讲道理,大学四年有足够的时间让你学会编程,也能从一个较长时间的学习中,知道自己适合不适合做程序员。就像我的同学里虽然都是计算机或者软件工程专业,但毕业后并没有都从事软件开发工作,或者从事了一段时间发现并不适合这个行业而选择到其他领域发展。

所以如果你是真的喜欢编程,那么在这个行业里工作你会很开心,如果不是那么往后路还长,你所要面对的晋升、加薪、跳槽都可能成为一道道障碍。目前可能你还是新人不了解自己是否喜欢编程开发,那么可以在以后的学习中体会,自己是否愿意为一个运行结果调来调去,辗转不寐的研究分析,只为最后那一个运行通过而激动!

那就究竟怎么学才能在毕业就能找到一份工作,而不需要再额外培训?

如果把这个“究竟”,用一个数量单位来度量的话,那就是在大学期间完成20万行代码的开发。20万行多不?如果从大一开始每天完成200行代码的开发,一个月算6000行,一年算6万行,3年就有18万行,在大学毕业前夕找工作时,你将成为同学眼里的面霸。因为有这20万行代码的学习,基本你已经成为了一个初级研发工程师的水平。

可能有人想,都工作十几年了也没写到10万行吧!这200行代码能写完没,有意义吗?

其实对于一个在大学阶段上学求识的人来说,在编程的学习过程中,你要尝试开发各种书本、视频、资料中关于Java里API的使用,哪怕是一个现在看来很简单的 List 使用,可能对于初学编程的你来说都是巨大的障碍,所有的这些基础知识认认真真的学完,你都将要写下庞大数量的代码。

那些我们难以言表、不作声响、暗自发力的日子,其实并不是我们想要的生活,而是通往我们想要的生活的路上。

四、就业

我经历过两家公司的实习,你呢?

12年冬,校企合作培训后,我面试上了国家科技和中软国际的管培生,最终选择了月薪7k的中软。也就是因为这个选择,有意思的经历就此发生。

面试完中软国际,口头offer沟通完毕后,久久不发正式录用通知,也不能去实习。电话沟通后说是需要等到13年4月,具体时间待定。咋办?我又不能一直在北京租着房子啃馒头吧!没办法就又出去面试,找了一家中科软进去实习,直到13年4月中旬,接到了中软的同时,“来参加,岗前培训”,好,又是一次培训。

待培训结束后,终于正式的到公司里开始工作,但又出了点小状况,我一个Java开发,入职的到公司的岗位竟然是C#,还需要倒腾中继器、IO板卡、PLC、摄像头等,就这样我增加了许多其他的技能。好在当时遇到的第一任领导,非常赞,带着我学会了很多很多,否则可能很早就跑路了!

五、跳槽

2015年8月25日,我跳槽去大厂做Java了!

其实讲到跳槽,可能是每个程序员职业发展的必经之路,你积累了多久、沉淀了多少都将会为下一份工作做好铺垫。

对于我之所以跳槽最大的原因就是想从传统行业去互联网看看,也不太想写C#了,毕竟我从大学开始到经过到几次培训都是写Java语言,对于C#语言来说并没有那么大的热情。为了不抛弃Java语言的学习,在第一家公司期间,还把很多公司里用C#写的项目,拿去用Java重新写一遍,只为了学习Java语言。

后来因为在第一家公司需要做一款数据采集的软件,正好把 Java 和 Netty 叨叨给领导了,领导也是非常支持,去搞吧!正式因为有了这次技术实践验证的经历,在后来面试互联网大厂时,会Netty还是挺加分的。

就这样从一个似乎是外包、传统的行业里,跳槽了互联网大厂开始做Java开发,一做就是5年多。往往对于跳槽来说,选择一个你喜欢的行业、喜欢的技术,还是会做出很多自己想做的东西的,也能得到满足于自己那种快乐的成长。

六、副业

"副业",我是一个多能折腾的人?

开过移动售电话卡的小店、做过淘宝卖数据线、接过私活做网站、线上给人讲课赚钱、也做过一段时间的技术论坛。但无一例外这些事情并没有做的长久,有因为跳槽搬家黄的、有因为业余时间不足没的、有因为没有用户流量挂的,总归都一一而去了!

所有这些折腾的过的事情,虽然并没有赚多少钱,可这样一份份的经历却让我也摸索到了应该适合我的路,那就是做长期价值沉淀的事情。这需要找一个自己擅长的方向,有可持续输出的内容,与自己的工作内容贴近,能让自己沉淀也能帮助他人。

所以从2019年7月开始,我参与到技术号主这条路上了,用了整整将近两年的时间积累了全平台近8万粉丝才慢慢做的“人尽皆知”,说人尽皆知是夸张了,哈哈哈,但现在你去搜小傅哥、搜重学Java设计模式、搜字节码编程、搜面经手册等等,都会看到的我的身影。当然并不是我的技术有多牛,只是我希望在不断的沉淀积累中可以把每个属于我认知范围的技术内容全部输出出来,至少这样可以提升自己也可以帮助属于这个阶段的技术同好。

但目前这个副业并没有赚多少钱,因为没有接任何一条广告。当然我不反对技术号主适量的接广告,毕竟做技术输出也是非常耗费时间和经历的,我用了近两年的时间才走到这个圈子里,也深深的知道每一个技术号主的辛苦。就像你是否想过,你要放弃几乎全年的周末和假期,也可能需要家人承担更多的情况下,让你有空闲的完整时间去思考、编写、设计、发布你要做的文章和内容呢?你看到的每一篇长文配图、万字技术文,只要是原创都至少要在3-4个小时思考、68个小时输出、12个小时发布,也就是说一个周末的时间就全部投放到这项热爱的事情上了!

之所以我现在并没有去接更大广告收入,主要是因为我对公众号的技术输出主要是因为我对技术内容极大的兴趣爱好,而我公众号和博客的运营成本,包括:域名、服务器、图库VIP等,都是靠较长假期写一些付费的技术专栏、整理的PDF和赞赏收回这些费用。不过从21年情况有所好转,还可以少赚一些钱。早期的粉丝知道,以前小傅哥都是靠薅羊毛养活粉丝的!

所以对我来说,技术沉淀是具有长期价值的副业,我也更希望用技术输出来养活自己!

七、出书

2021年4月23日,图书节,我的第一本书《重学Java设计模式》正式在京东发售。

出书是我个人在技术成长路上的一次打卡,我就想走走没走过的路,爬爬没爬过的山,看看风雨后的彩虹是否有别人说的那般绚烂。

2020年07月12日,小傅哥的《重学Java设计模式》PDF版在公众号首发,但没想到那天起这本设计模式彻底火了,火成什么个鬼样子呢,几乎全网的号主都不知情的情况下被广告主投放过 “字节跳动总结的设计模式 PDF 火了,完整版开放下载!”

  1. 那3个月这本书我提供的链接全网下载量一度突破30万次,不能统计到的还有很多!
  2. 公众号一天能涨粉600个+
  3. 每天都有人问小傅哥,你是不是字节跳动的
  4. 百度搜小傅哥竟然给我加了热词“小傅哥的设计模式”
  5. GitHub 设计模式对应的代码库持续霸榜 GitHub Trending
  6. 可能就是从那段时间起,很多人知道了我,虽然当时不太喜欢被这样宣传,但也就这样莫名其妙的火了

也是从火了那段时间开始,每天都有出版社编辑联系,要不要出书,最开始并没有心动,也觉得自己文笔不好,还很多错字。

之所以后来上车了是因为遇到了宋亚东,给我介绍、给我讲解、给我分析,好吧!在20年10月1日放假起,我开始重新整理设计模式稿件,重新整理文章、收集粉丝反馈、绘制技术图稿,一点点的完成所有内容并添加新的章节,于11月左右交给出版社,接下来的路漫漫长….

一本书的出版要选题、交稿、审稿、之后是一遍遍的改稿、审核、改稿、审核,终于感觉要完事了又开始了三审三校,说要过不了就不成功便成仁!好在是一周左右时间通过了,接下来又申请书号、出版印刷、晾干、装订,嗯多久呢,从提交编辑到上架京东商城,用了7个月时间,在加上我的编辑创建时间,这本书耗时一年半出版了!

其实出版一本书并不会特别难,而且也不会膨胀。出书只是个人成长努力路线上的一次结果印证,但不是最终的目标,我们可以用自己长期积累的个人能力完成更多自己想做的事情。

八、传承

沉淀、分享、成长,让自己和他人都能有所收获!

在过去码文的时间里付出了所有的深夜和假期,整理大学四年到毕业工作5年的学习路线资源汇总,编写了12个较大实战性专题类文章;《用Java实现JVM》、《Netty4.x专题》、《领域驱动设计》、《全链路监控》、《Java 面经手册》、《字节码编程》等和近240篇原创,以及出版图书《重学Java设计模式》和在线专栏《SpringBoot 中间件设计和开发》、《Netty 仿PC端微信》等。这些专栏和文章都有相应的源码,可以一边学习一边验证。

还创建了第一个关于:CodeGuide | 程序员编码指南,的Git仓库,涵盖了;文章、源码、案例、书籍等各项学习拓展技能的资料。

我的Github汇总我所有编写的内容,感谢给个 Star 支持:

  • 内容:本代码库是作者小傅哥多年从事一线互联网Java开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。这部分资料也是我所写博客中实践项目的源码,在这里你可以学到Netty、字节码编程、设计模式、领域驱动设计、规则引擎、面试、架构以及职场经历的分享。
  • 地址:github.com/fuzhengwei/… - 点击阅读原文,进去给个 Star 吧!

九、总结

👣心怀天下,声色犬“码”。生有热烈,藏与俗常。

有时候真的很感谢自己还能坚持做原创技术输出,即使再忙再累也给自己一个当下的交代,在写文章的过程中几乎没有过周末这样大块时间,也没有过小长假期。但每当自己完成每一篇文章后,那份给自己的努力下的沉淀,也传播给其他人知识,都能让我乐以忘忧。

所有的努力都是因为:能力,是你前行的最大保障。哪怕你是兢兢业业的工作者,也要拥有能留下的本事和跳出去的能力,才能在相对频繁的变化和不确定中获利。

好,祝大家在以后的路上,都能:所求皆如愿,所行化坦途。承遇着朝霞,刻印着风华!

本文转载自: 掘金

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

教你做超惊艳的南丁格尔玫瑰图 玫瑰图的前世今生 制作方法

发表于 2021-05-25

作者:可乐

来源:可乐的数据分析之路

转载请联系授权(微信ID:data_cola)

其实早在年初,疫情还很严重的时候,人民日报发布的这个图就吸引了广大数据分析者的注意。

今天我们就把这个图的前因后果以及怎么做一次性讲清楚。

玫瑰图的前世今生

这个图学名:南丁格尔玫瑰图,是弗罗伦斯·南丁格尔女士发明的,又名为极区图、鸡冠花图。

要说到南丁格尔女士,也是很传奇,她首先是一位护士,其次也是一名统计学家,更是英国皇家统计学会的第一位女性会员。

19世纪50年代,英国、法国、土耳其和俄国进行了克里米亚战争。南丁格尔主动申请,自愿担任战地护士。当时的医院卫生条件极差,甚至连干净的水源与厕所都没有,伤士死亡率高达42%,直到 1855 年卫生委员会来到医院改善整体的卫生环境后,死亡率才戏剧性地降至 2.5% 。当时的南丁格尔注意到这件事,认为政府应该改善战地医院的条件来拯救更多年轻的生命。

出于对资料统计的结果会不受人重视的忧虑,她发明出一种色彩缤纷的图表形式,让数据能够更加让人印象深刻。

这张图就是南丁格尔当时报告这件事时所用的图表,以表达军医院季节性的死亡率,从整体上来看: 这张图是用来说明、比较战地医院伤患因各种原因死亡的人数,每块扇形代表着各个月份中的死亡人数,面积越大代表死亡人数越多。

这张图里有一大一小两个玫瑰图,右侧较大的玫瑰图,展现的是1854 年 4 月至 1855 年 3 月的数据;而左侧的玫瑰图,展现的则是 1855 年 4 月至 1856 年 3 月的数据,以1855 年4 月做为分界,将24 个月的资料切分为左右两张图再用黑色线条连结,是因为这大约便是卫生委员会来改善环境时的日期,也因此我们可以比较两个年度的死亡人数与其原因的概略比例。

  • 灰色的区域的面积明显大于其他颜色的面积。这意味着大多数的伤亡并非直接来自战争,而是来自糟糕医疗环境下的感染。
  • 卫生委员到达后(1855年3月),死亡人数明显的下降。

她的方法打动了当时的高层,包括军方人士和维多利亚女王本人,于是医事改良的提案才得到支持,因该图的外形酷似一朵绽放的玫瑰,因此“南丁格尔玫瑰图”也就由此而来。

和饼图的区别

饼图是用角度的大小体现数值或占比

南丁格尔玫瑰图是用扇形的半径表示数据的大小,各扇形的角度则保持一致。可以说南丁格尔玫瑰图实际上是一种极坐标化的圆形直方图。它夸大了数据之间差异的视觉效果,适合展示数据原本差异小的数据

制作方法

用Excel、Python都可以实现,分别来讲解。

用Excel

Excel图表里本身是没有南丁格尔玫瑰图这个模板的,但是我们可以用雷达面积图来做,也可以用圆环图,这里我用雷达面积图来做。

step1:添加辅助列1
这里有12个国家,雷达图是按照极坐标来划分的,那么一个国家在极坐标里的占比就是360/12=30。

step2:做辅助列2
每一个国家后的累计数据复制30次,均复制到辅助列2上,也就是说辅助列2应该有360条数据。

step3:填充雷达图
对其插入雷达面积图,同时将辅助列1添加进去。

并将复制列1更改为圆环图,圆环图是用来加标签的。

将圆环图填充为无色,并设置标签格式,最后得到如图所示的南丁格尔玫瑰图:

网上有很多用Excel做玫瑰图的教程,不过说实话,没有对比,单纯用一组数据做这个南丁格尔玫瑰图,是为了显摆吗?柱形图它不是更简单直观吗

用Python

用Python里的pyecharts来作图,首先要安装pyecharts,安装完以后可按如下代码制作。

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
python复制代码import pandas as pd
from pyecharts.charts import Pie
from pyecharts import options as opts
# 读入数据,需要更改
df = pd.read_excel("30.xlsx")
v = df['疫情地区'].values.tolist()
d = df['新增'].values.tolist()
#设置颜色
color_series = ['#FAE927','#E9E416','#C9DA36','#9ECB3C','#6DBC49',
'#37B44E','#3DBA78','#14ADCF','#209AC9','#1E91CA',
'#2C6BA0','#2B55A1','#2D3D8E','#44388E','#6A368B'
'#7D3990','#A63F98','#C31C88','#D52178','#D5225B',
'#D02C2A','#D44C2D','#F57A34','#FA8F2F','#D99D21',
'#CF7B25','#CF7B25','#CF7B25']
# 实例化Pie类
pie1 = Pie(init_opts=opts.InitOpts(width='1350px', height='750px'))
# 设置颜色
pie1.set_colors(color_series)
# 添加数据,设置饼图的半径,是否展示成南丁格尔图
pie1.add("", [list(z) for z in zip(v, d)],
radius=["30%", "135%"],
center=["50%", "65%"],
rosetype="area"
)
# 设置全局配置项
pie1.set_global_opts(title_opts=opts.TitleOpts(title='玫瑰图示例'),
legend_opts=opts.LegendOpts(is_show=False),
toolbox_opts=opts.ToolboxOpts())
# 设置系列配置项
pie1.set_series_opts(label_opts=opts.LabelOpts(is_show=True, position="inside", font_size=12,
formatter="{b}:{c}例", font_style="italic",
font_weight="bold", font_family="Microsoft YaHei"
),
)
# 生成html文档
pie1.render("南丁格尔玫瑰图.html")

完成后效果如下:

在线

在线制作永远是满足临时性需求最方便快捷的,这里推荐两个在线制作的网站。

花火:hanabi.data-viz.cn
选择基础玫瑰图,也可选择圆环状的。

点击上传数据

点击右边的图表设置,可以设置颜色、字体、标题、图例等具体信息。

总感觉这个图这么小呢,是我做的不对吗?

图之典:tuzhidian.com
这个网站很好用,强推。
选择南丁格尔玫瑰图

这个网站的好处就是它会告诉你每一个图表怎么用以及它的应用场景

还告诉我们和饼图、柱形图的区别

往下滑,可以看到不同工具的制作教程,当然这里我们需要线上制作。

点击进去按需制作即可。

以上就是本篇文章的全部内容了,喜欢的小伙伴就点个在看吧~

我是可乐,更多精彩内容可关注公众号:可乐的数据分析之路
或加我微信:data_cola

本文转载自: 掘金

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

MySQL 中使用 insert on duplic

发表于 2021-05-25

  在 InnoDB 中,索引按照定义的顺序创建。在往 InnoDB 中写入数据时,InnoDB 首先会将完整的数据记录写入主键索引中,然后按照其他索引定义的顺序依次更新索引信息。

  通常,在往数据库中写入数据时,首先会 select 进行查找,如果记录已经存在,则 update 更新,否则 insert 插入。虽然使用 insert ... on duplicate key update 和 replace into 有时候也可以达到目的,但这两种方法在特定的条件下却会出问题。

1
2
3
4
5
6
7
8
9
sql复制代码CREATE TABLE secrets(
id INT AUTO_INCREMENT PRIMARY KEY,
global_secret_number INT NOT NULL UNIQUE,
account_id INT NOT NULL UNIQUE,
secret VARCHAR(50) NOT NULL
);

INSERT INTO secrets VALUES(0, 1, 10, 'account 10''s secret');
INSERT INTO secrets VALUES(0, 2, 20, 'account 20''s secret');

   在数据表 secrets 中,id 为主键,global_secret_number 和 account_id 都有唯一索引。所以,secrets 表中不会出现任何两行记录中 global_secret_number 或 account_id 相同。

   执行完上述 SQL 后,secrets 表的数据以及索引如下:

secrets 表中的数据

secrets 表中的索引

secrets 表中当前 auto_increment 自增情况

⒈ 使用 insert ... on duplicate key update 可能会引起的问题

   现在执行以下 SQL:

1
SQL复制代码INSERT INTO secrets(global_secret_number, account_id, secret) VALUES(1, 20, 'account 20''s new secret') ON DUPLICATE KEY UPDATE secret = 'account 20''s new secret';

   执行完以上 SQL 后,我们期望的结果:

  • 因为 global_secret_number = 1 的记录已经存在,所以报错
  • 因为 account_id = 20 的记录已经存在,所以报错
  • 如果数据表中存在一条 global_secret_number = 1 AND account_id = 20 的记录,那么更新 secret

  实际运行之后的结果如下:

SQL 执行之后 secrets 表中的数据

   按照之前所说的,InnoDB 在插入数据时,索引按照定义的顺行更新。所以,在执行上述 SQL 时,InnoDB 首先会向主键索引树中插入完整的记录,此时 auto_increment 变成了 4。之后,InnoDB 会相机更新两个唯一索引 global_secret_number 和 account_id ,在更新 global_secrent_number 时,因为 1 已经存在,所以之前插入主键索引树的记录会被删除,同时将 global_secret_number = 1 的记录的 secret 值更新为 account 20‘s new secret。

⒉ 使用 replace into 可能会引起的问题

   在 MySQL 中,replace into 的执行机制为

  • 当主键索引、任一唯一索引中有与 SQL 语句中对应字段相同的值时,会首先删除已经存在的记录,然后执行 insert 糙作
  • 否则直接执行 insert

   现在执行以下 SQL:

1
SQL复制代码REPLACE INTO secrets VALUES(0, 1, 20, 'account 20''s new secret');

  期望的结果:

  • 如果 global_secret_number = 1 AND account_id = 20 的记录存在,则执行 update
  • 如果 global_secret_number = 1 AND account_id = 20 的记录不存在,则执行 insert
  • 由于唯一索引中 global_secret_number = 1 的值已经存在,会报错

  实际运行之后得到的结果如下:

选区_007.png

  首先,由于 global_secret_number = 1 的值在唯一索引中已经存在,所以 InnoDB 会首先删除相应的记录(即 id = 1 的记录);之后,由于 account_id = 20 的记录在唯一索引中也已经存在,所以 InnoDB 又会删除相应的记录(即 id =2 的记录)。最后,InnoDB 才会将 SQL 语句中的记录插入数据表。

所以,在写入数据库时,为了保证数据的一致性,最可靠的方法还是先 select,如果存在则 update,否则 insert

本文转载自: 掘金

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

1…660661662…956

开发者博客

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