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

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


  • 首页

  • 归档

  • 搜索

今日谈:BoltDB数据库,一款纯Go实现的KV数据库

发表于 2021-11-18

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

今日谈:BoltDB数据库,一款纯Go实现的KV数据库

本文收录于我的专栏:细讲区块链

本专栏会讲述区块链共识算法以及以太坊智能合约、超级账本智能合约、EOS智能合约相关知识,还会详细的介绍几个实战项目。如果有可能的话,我们还能一起来阅读以太坊的源码。有兴趣的话我们一起来学习区块链技术吧~

一、什么是BoltDB

这是一款纯粹使用Go语言编写的key/value数据库,据说它是参考了开源的内存映射数据库LMDB。值得注意的是,这款数据库不支持网络连接,也没有复杂的SQL语句查询支持。但是它在Go的应用中能够比较方便的来达到数据持久化。

二、使用场景

  1. Go语言编写的程序,这个程序需要内嵌数据库。
  2. 不需要复杂的SQL语句查询。
  3. 这款数据库更擅长读的操作,所以在读多写少的场景下,它效果比较好。

三、安装及使用方法

安装方法很简单,就是在命令行执行:

1
arduino复制代码go get github.com/boltdb/bolt/...

那么我们如何打开数据库呢?我们来看一看下面这段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go复制代码package main
​
import (
"log"
"github.com/boltdb/bolt"
)
​
func main() {
// Open the my.db data file in your current directory.
// It will be created if it doesn't exist.
db, err := bolt.Open("my.db", 0600, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
...
}

这个 bolt.Open()是在给定的路径上打开数据库。如果要打开的数据库文件不存在,则将自动创建该文件。 第二个参数是文件模式。第三个参数传入nil值就让Bolt使用默认选项打开数据库。

Bolt 多个进程无法同时打开同一个数据库。打开一个已经打开的 Bolt 数据库会导致它挂起,直到另一个进程关闭它。为了防止无限期等待,我们需要给Open()函数一个超时参数:

1
css复制代码db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})

事务

Bolt一次只支持一个读写事务,但是一次可以支持多个只读事务。

用于读写的事务API是Update,二用于读的事务API是View。

1
2
3
4
5
go复制代码err := db.Update(func(tx *bolt.Tx) error {
...
return nil
})
​
1
2
3
4
5
go复制代码err := db.View(func(tx *bolt.Tx) error {
...
return nil
})
​

注意:只读事务和读写事务通常不应同时在同一个 goroutine 中打开。这可能会导致死锁,因为读写事务需要定期重新映射数据文件,但在只读事务打开时无法这样做。

还有一种Batch事务可以减小并发写操作的开销。Batch 仅在有多个 goroutine 调用它时才有用。

1
2
3
4
5
go复制代码err := db.Batch(func(tx *bolt.Tx) error {
...
return nil
})
​

读写操作

1
2
3
4
5
6
go复制代码db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
err := b.Put([]byte("answer"), []byte("42"))
return err
})
​

这是往数据库中写入键为answer值为42的数据。

1
2
3
4
5
6
7
go复制代码db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("MyBucket"))
v := b.Get([]byte("answer"))
fmt.Printf("The answer is: %s\n", v)
return nil
})
​

这是读取键为answer的数据。

是不是发现有个tx.Bucket()不知道是干什么的呢?

Bolt有个很有意思的地方就是这个Bucket,这个是这个数据库组织数据的基本方式,比如有一个Bucket叫公司,这个Bucket里面还建一个Bucket叫部门,里面建一个key:ReganYue,这就说明ReganYue是这个部门里面的人,但是同时也是这个公司里面的人,这个key属于部门这个Bucket,也属于公司这个Bucket。

本文转载自: 掘金

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

Ubuntu添加root用户和开启远程登陆 Java随笔

发表于 2021-11-18

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


相关文章

Java随笔记:Java随笔记


一.添加root用户

  • 因为腾讯云服务每次登录都是默认用户为Ubuntu用户,但是我要使用scp命令来上传一些文件到服务器上,很多操作没有权限,很是麻烦,所以修改一下为允许root用户登录。
  • 1.使用Ubuntu正常登录,命令如下:
1
java复制代码ssh ubuntu@118.89.193.239
  • 显示如下:
  • ubuntu登录
  • 2.修改root密码,命令如下:
1
java复制代码sudo passwd root
  • 密码输入不可见,而且需要再次输入密码!
  • 显示如下:
  • 密码
  • 3.修改ssh配置,命令如下:
1
java复制代码sudo vi /etc/ssh/sshd_config

4.重启ssh服务

1
java复制代码sudo service ssh restart

二.开启SSH远程登陆

  • 以下所有操作都是在root用户下: 若不是root用户登录状态,所有命令前加 sudo
  • 1.安装SSH
1
java复制代码apt-get install openssh-server
  • 2.查看SSH是否安装成功
1
java复制代码 ps -e |grep ssh
  • 如果安装成功显示如下:有sshd,说明ssh服务已经启动
  • 安装成功
  • 3.如果没有启动ssh服务,输入以下命令:ssh服务则会启动
1
java复制代码service ssh start
  • 4.此时我们的服务已经全部启动,但是远程连接可能会报错如下:
1
java复制代码Permission denied, please try again(publickey,password)
  • 这是因为我们没有开启远程连接服务:
  • 首先打开/etc/ssh/sshd_config文件
1
java复制代码vim /etc/ssh/sshd_config
  • 显示如下:
  • config文件内容
  • 找到 PermitRootLogin prohibit-password 位置,将其注释,在原位置添加如下内容:
1
java复制代码PermitRootLogin yes
  • 再次连接试试:
1
java复制代码ssh root@118.89.193.239
  • 显示连接成功:
  • 连接成功
  • 5.重启ssh服务
1
java复制代码sudo service ssh restart

路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah

本文转载自: 掘金

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

「踩坑记录」ThreadLocalRandom 的正确使用

发表于 2021-11-18

问题引入

之前在《Java 并发编程之美》中学到 ThreadLocalRandom 的时候,自己去实验了一下,于是写出了下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
js复制代码import java.util.concurrent.ThreadLocalRandom;
public class RandomStu {
// 初始化一个 ThreadLocalRandom 实例
private static ThreadLocalRandom TEST = ThreadLocalRandom.current();
public static void main(String[] args) {
// 多线程输出随机数程序
for(int i=0;i<4;++i){
new Thread(()->{
for(int j=0;j<5;++j) {
System.out.println(Thread.currentThread().getName()+"第"+j+"个:"+TEST.nextInt(1000));
}
},"thread"+i).start();
}
}
}

运行之后发现根本就没有随机的效果,每个线程的输出都是一样的,并且不管运行多少次,每个轮次输出的数都不变,如下图:

image.png

之后作者去查阅了 ThreadLocalRandom 的源码,搞清楚了问题,本文即是对这一分析过程的记录。

ThreadLocalRandom 简介

ThreadLocalRandom 的思想和 ThreadLocal 类似,都是让每个线程自己保存一些变量,防止多线程的竞争。避免了原先的 Java.util.Random 类在多线程情况下,其它线程不断 CAS 自旋。它的具体原理,我就不在这里介绍了,大家可以去看《Java 并发编程之美》,或者这篇博客: Java 随机数生成原理与 ThreadLocalRandom 详解,本文主要记录自己的踩坑,填坑过程。

问题分析

出现了问题,并且产生随机数的直接方法就是 nextInt 方法,我自然直接去看了 nextInt 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
js复制代码public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BAD_BOUND);
int r = mix32(nextSeed());
int m = bound - 1;
// 一系列操作,得到随机数
if ((bound & m) == 0) // power of two
r &= m;
else { // reject over-represented candidates
for (int u = r >>> 1;
u + m - (r = u % bound) < 0;
u = mix32(nextSeed()) >>> 1)
;
}
// 最后返回 r
return r;
}

可以看到,进行了一系列计算,最后返回了r,并且这些计算主要是依赖于m也就是 bound - 1和r的值,这里m的值是我们在调用 nextInt 时传入的,所以关键是每个线程调用 nextInt 时,此函数一进来r的值应该每次都不一样才对。既然现在每次每个线程的序列都一样,就说明每个线程进来r的值都一样,所以后续经过同样的计算,产生了同样的序列。

r一进来的赋值语句为int r = mix32(nextSeed());,自然的,我先去看了 mix32 方法:

1
2
3
4
js复制代码private static int mix32(long z) {
z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32);
}

可见,还是依赖传进来的z,自然的,我去看了看得到z的语句,nextSeed 方法:

1
2
3
4
5
6
7
8
9
10
11
12
js复制代码private static final Unsafe U = Unsafe.getUnsafe();
// 用 Unsafe 类得到线程对应 threadLocalRandomSeed 变量的地址
private static final long SEED
= U.objectFieldOffset(Thread.class, "threadLocalRandomSeed");

final long nextSeed() {
Thread t; long r; // read and update per-thread seed
// 设置此ThreadLocalRandom 变量的值并存入线程
U.putLong(t = Thread.currentThread(), SEED,
r = U.getLong(t, SEED) + GAMMA);
return r;
}

可以看到,这个函数从每个线程拿到它对应的SEED,将此SEED加上一个预先定义的GAMMA,将这个值传给 mix32 方法,继而得到r的初始值,继而经过一系列计算,得到输出的随机数值。看来问题就是出在这了。这里用r = U.getLong(t, SEED) + GAMMA);语句赋值r。

如果看过 ThreadLocalRandom 的原理,就会知道,每个线程的种子初始值在调用ThreadLocalRandom.current();时初始化,于是,我去重新看了看这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码// instance 变量,饿汉型的单例模式
static final ThreadLocalRandom instance = new ThreadLocalRandom();

// 拿到每个线程对应的 threadLocalRandomProbe 的地址
private static final long PROBE
= U.objectFieldOffset(Thread.class, "threadLocalRandomProbe");

public static ThreadLocalRandom current() {
// 如果线程的 threadLocalRandomProbe 没被设置,进行 localInit
if (U.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
// 线程第二次以及以后进来,直接 return
return instance;
}

可见,ThreadLocalRandom.current( ) 会检查每个线程的 threadLocalRandomProbe 是否被设置,若无,调用 localInit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
js复制代码// seeder 变量,类加载时初始化,
// 每次都递增,保证每个线程的初始种子不一样
private static final AtomicLong seeder
= new AtomicLong(mix64(System.currentTimeMillis()) ^
mix64(System.nanoTime()));

// 每个线程只进来一次
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p; // skip 0
// 每个线程调用时,由于 seeder.getAndAdd(),每个线程初始化的种子都不一样
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
// 将计算出的 seed,probe 存入线程对象
U.putLong(t, SEED, seed);
U.putInt(t, PROBE, probe);
}

这里,存入线程对象的probe肯定是非 0 的,由于int probe = (p == 0) ? 1 : p;,那么线程下次在调用ThreadLocalRandom.current();,就会直接返回instance,不难看出,这里是一个饿汉单例模式。在这里,每个线程的种子得以初始化。

发现原因

一遍流程走完,我就发现原因了,因为我是在一开始在 main 线程里调用了private static ThreadLocalRandom TEST = ThreadLocalRandom.current();,接着各个线程共享此TEST,调用TEST.nextInt(1000),导致每个线程的初始种子都没有被设置。于是在 nextSeed 方法里,取到的每个线程的r的值都是确定的:

1
2
3
4
5
6
js复制代码final long nextSeed() {
Thread t; long r; // read and update per-thread seed
U.putLong(t = Thread.currentThread(), SEED,
r = U.getLong(t, SEED) + GAMMA);
return r;
}

在 nextSeed 里,走到r = U.getLong(t, SEED) + GAMMA时,由于子线程根本没进行过 threadLocalRandomSeed的设置,所以第一次每个线程的U.getLong(t, SEED)取出来都是 0,我甚至都可以预测第一次之后,r的值。GAMMA被初始化为这样的一坨值:

image.png

我们找个网站转换为十进制:

image.png

可见,转为十进制是这样的一坨值:-7046029254386353131,接下来,我们打断点看一下,可以断定:每个线程的r第一次肯定被设置为了 -7046029254386353131。为了便于观察,把内循环改为循环一次:

1
2
3
js复制代码for(int j=0;j<1;++j) {
System.out.println(Thread.currentThread().getName()+"第"+j+"个:"+TEST.nextInt(1000));
}

image.png

image.png

于是,豁然开朗了,在 main 线程调用了private static ThreadLocalRandom TEST = ThreadLocalRandom.current();,导致只有 main 线程的种子被设置了,然而我们的多线程程序和 main 线程没有关系,主要靠 4 个子线程输出随机数。4 个子线程种子没有被初始化过,默认都是 0,每次输出都经过相同的计算,自然每次都输出同样的值,没有任何随机效果。

正确的写法

找到了错误所在,正确写法也呼之欲出了,子线程不要共享 main 线程的TEST,要像下面这样调用 nextInt:

1
2
3
4
js复制代码for(int j=0;j<10;++j) {
System.out.println(Thread.currentThread().getName()+"第"+j+"个:"+
ThreadLocalRandom.current().nextInt(1000));
}

每个子线程都ThreadLocalRandom.current().nextInt(1000)这样调用 nextInt,由于只有线程第一次才会调用 localInit 方法,第一次之后,threadLocalRandomProbe肯定不为 0,也不用担心一直调用ThreadLocalRandom.current().nextInt(1000)会影响什么性能。

单例模式的疑问

有人问了,从ThreadLocalRandom.current();代码来看,instance只会生成一次啊,那你在哪里调用ThreadLocalRandom.current().nextInt(1000)又有什么区别?instance确实只生成一次,之所以每个子线程都调用它,就是为了每个线程第一次调用它时,可以初始化本线程的种子,下面再调用,就是调用instance.nextInt了,方法都是操作调用线程的内部变量,就相当于一个工具类,本身不存储什么东西。

总结

使用 ThreadLocalRandom,不能多个线程共享某线程返回的 ThreadLocalRandom 实例,需要每个线程分别调用ThreadLocalRandom.current().nextInt(1000),这样才能初始化每个线程的种子,才可以起到随机数的效果。

本文转载自: 掘金

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

SpringCloud入门 —— Feign服务调用 前言

发表于 2021-11-18

欢迎大家关注公众号:好码农,关注即可领取大礼包一份

前言

紧接上文,本文记录多个服务使用feign远程调用。

一、feign是什么?

Feign是一个声明性web服务客户端。

Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,返回给调用者。

二、代码示例

1.服务提供者

新建测试接口

1
2
3
4
5
6
7
8
9
less复制代码@RestController
@RequestMapping("api")
public class ProviderController {

@GetMapping("/user/{id}")
public String user(@PathVariable(value = "id") String id) {
return "我是服务提供者==>用户id:" + id;
}
}

2.服务消费者

引入feign依赖

1
2
3
4
5
xml复制代码 <!--feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

调用接口MyFeign.java

1
2
3
4
5
6
7
8
9
10
11
12
13
kotlin复制代码package com.local.springboot.client.clientcustomer.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "client-provider-server", path = "/api", fallback = MyFeignFallback.class
, fallbackFactory = MyFeignFallbackFactory.class)
public interface MyFeign {

@GetMapping("/user/{id}")
String getUser(@PathVariable("id") String id);
}

client-provider-server是服务提供者在eureka注册的名字,也可以直接指定url

@FeignClient(name = “client-provider-server”, path = “/api”,url = “http://localhost:8081“)

url可以直接指定第三方服务地址,path指定路径,接口的方法指定接口

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typescript复制代码/**
* feign远程调用
*/
@GetMapping("/query/{id}")
public String getUser(@PathVariable(value = "id") String id) {
return myFeign.getUser(id);
}

/**
* feign远程调用
*/
@GetMapping("/exception")
public String exception() {
return myFeign.exception();
}

3.测试

启动程序,可以看到服务已注册\

1-1.png

访问http://localhost:8082/query/imid\

1-2.png

成功调用

4.Fallback熔断

在网络请求或者服务提供者发生异常,如果还想再异常情况下使系统可用,那么就需要容错处理。

需要配置fallback来处理异常,如果需要获取到报错信息,则要配置fallbackFactory

1
2
ini复制代码@FeignClient(value = "client-provider-server", path = "/api", fallback = MyFeignFallback.class
, fallbackFactory = MyFeignFallbackFactory.class)

指定Feign接口的实现类,和FallbackFactory工厂接口类,如下:

MyFeignFallback.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typescript复制代码package com.local.springboot.client.clientcustomer.feign;

import org.springframework.stereotype.Component;

/**
* feign调用容错处理
*/
@Component
public class MyFeignFallback implements MyFeign {

@Override
public String getUser(String id) {
return null;
}

@Override
public String exception() {
return "网络请求超时,请稍后重试!";
}
}

MyFeignFallbackFactory.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
java复制代码package com.local.springboot.client.clientcustomer.feign;

import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

/**
* FallbackFactory工厂,获取HTTP请求错误状态码和信息
* (只打印异常)
*/
@Component
public class MyFeignFallbackFactory implements FallbackFactory<MyFeign> {

private final MyFeignFallback myFeignFallback;

public MyFeignFallbackFactory(MyFeignFallback myFeignFallback) {
this.myFeignFallback = myFeignFallback;
}

@Override
public MyFeign create(Throwable cause) {
// 打印异常
cause.printStackTrace();
return myFeignFallback;
}
}

模拟异常

服务提供者

1
2
3
4
typescript复制代码@GetMapping("/exception")
public String exception() {
throw new RuntimeException("服务器异常");
}

服务消费者

1-3.png

1
2
less复制代码@GetMapping("/exception")
String exception();

访问http://localhost:8082/exception\

**注意事项: 使用fallback,要开启Hystrix,配置feign.hystrix.enabled=true,如果springCloud 版本是2020.0.1或以上的,配置feign.circuitbreaker.enabled=true,

因为查看源代码,发现已修改为feign.circuitbreaker.enabled**

问题:

打包出现如下报错:

**

1
vbscript复制代码Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources) on project client-customer: Input length = 1 -> [Help 1]

解决方法:

对应的pom将

1
2
3
4
xml复制代码<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

改为:

1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-filtering</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
</plugin>

总结

通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

« 上一章:SpringCloud入门 —— Eureka服务注册与发现

» 下一章:SpringCloud入门 —— Ribbon负载均衡

作者:猫的树

链接:www.jianshu.com/p/b4492bcc4…

本文转载自: 掘金

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

Python matplotlib 绘制子图概述 复习回顾

发表于 2021-11-18

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

复习回顾

我们在前面对matplotlib模块基本模块的认识,到对matplotlib.plot模块绘制折线图、柱状图饼图等,往期文章的内容快速查看

  • matplotlib 绘制折线图:介绍折线图相关属性进行汇总说明
  • matplotlib 绘制柱状图:介绍柱状图相关属性进行汇总说明
  • matplotlib 绘制直方图:介绍直方图相关属性进行汇总说明
  • matplotlib 绘制散点图:介绍散点图相关属性进行汇总说明
  • matplotlib 绘制等高线图:介绍等高线图相关属性进行汇总说明
  • matplotlib 绘制饼图: 介绍饼图相关属性进行汇总说明

在统计图表中,能详细地展现数据信息,往往我们都需要绘制多个子图帮助展示数据,pyplot.subplot()、Figure.add_subplot、pyplot.axes()等方法来绘制不同场景的子图

image.png

本期,我们将学习matplotlib绘制子图的方法,Let’s go~

  1. 子图介绍

我们之前在学习matplotlib模块底层时,都知道matplotlib模块主要分为脚本层、美工层、后端,其中在美工层中,我们对图表组成的元素包括:Figure->Axes->Axis

image.png

  • 怎么才能在图表中放下其他的图表呢?

这个时候我们需要在Figure画布去添加Axes对象即可创建第二个图表。

  • matplotlib 模块提供绘制方法
+ matplotlib模块提供了方法
方法 说明
matplotlib.pyplot.subplot() 将Axes添加到当前画布中
matplotlib.pyplot.subplots() 创建一个画布和子图
matplotlib.pyplot.subplot2grid() 利用网格的方式均匀划分不同部分
matplotlib.pyplot.axes() 将轴添加到当前画布并使其成为当前轴
matplotlib.pyplot.figure() 创建新画布
matplotlib.Figure.add_subplot() 向画布添加一个Axes对象作为子图
matplotlib.Figure.subplots() 向画布中添加一组子图
* 在matplotlib模块中,支持绘制子图的方法集中在pyplot和Figure类
* matplotlib 模块中提供的子图的方法我们常用主要subplot()、subplots()和add_subplot()方法
  1. 子图属性

在subplot()、subplots()和add_subplot()方法中会共用到如下属性。

  • 设置子图背景色

+ 关键字:facecolor或者fc
+ 取值可选:
    - 表示颜色的英文单词:如红色"red"
    - 表示颜色单词的简称如:红色"r",黄色"y"
    - RGB格式:十六进制格式如"#88c999";(r,g,b)元组形式
  • 设置子图x/y轴标签

+ 设置x轴关键字:xlabel
+ 设置y轴关键字:ylabel
+ 取值形式为:字符串
  • 设置子图x/y轴比例

+ 设置x轴比例关键字:xscale
+ 设置y轴比例关键字:yscale
+ 取值形式可选:{"linear", "log", "symlog", "logit", ...}
  • 设置子图的宽高比:

+ 关键字:box\_aspect
+ 取值形式为:浮点型
  • 设置轴位置:

+ 关键字:set\_position
+ 取值形式为:列表形式,[left, bottom, width, height]
  1. 绘制子图步骤

  1. 导入matplotlib.pyplot模块和matplotlib.Figure模块
  2. 准备数据,可以使用numpy/pandas整理数据
  3. 调用子图方法创建子图
  4. 调用绘制图形方法折线图plot,pie,bar等

4.小试牛刀

我们使用pyplot.subplot()方法绘制子图

  • 首先,我们使用np.random.randint()生成x,y轴数据
1
2
3
4
python复制代码import numpy as np
x = np.random.randint(0,100,25)

y = np.random.randint(0,100,25)
  • 结合for循环调用方法pyplot.subplot()方法在画布上创建四个子图对象
1
2
3
python复制代码import matplotlib.pyplot as plt
for i in range(1,5):
plt.subplot(2,2,i,fc="#88c999",xlabel="x label",ylabel="y label",title="subplot",aspect="auto")
  • 每个子图调用pyplot.bar()方法绘制柱状图
1
python复制代码plt.bar(x,y)
  • 调用pyplot.tight_layout()方法调整每个子图展示画面
1
python复制代码plt.tight_layout()
  • 最后绘制子图效果如下

image.png

总结

本次,我们对matplotlib 绘制子图方法和相关属性进行基本学习,关于子图的操作详细操作后续会继续展开。

以上是本期内容,欢迎大佬们点赞评论,下期见~

本文转载自: 掘金

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

grafan监控忘记登录密码

发表于 2021-11-18

我忘记密码了

grafana登录密码突然忘记了,感觉一时半会找不回账号密码怎么办?一般我会把一些数据文件全部删除掉,然后再重新启动,就和新的一样了。但是,遇到很重要的tidb监控看板,如果删除了数据,很多东西都没有了,那么有没有一种好的方法呢?

找到grafana的数据文件grafana.db

1
2
js复制代码find / -name "grafana.db"
ps:默认的安装路径为/var/lib/grafana/grafana.db

不规范的方法

image.png

rm -f grafana.db

rm -rf png

这样把所有文件都删除掉了,这是不规范的操作。

现在使用的方法

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
js复制代码[root@localhost data]# ll
total 4788
-rw-r----- 1 tidb tidb 4894720 Nov 18 17:21 grafana.db
drwx------ 2 tidb tidb 4096 Sep 9 20:01 png
[root@localhost data]#
[root@localhost data]#
[root@localhost data]#
[root@localhost data]# sqlite3 grafana.db
SQLite version 3.7.17 2013-05-20 00:56:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
alert dashboard_tag server_lock
alert_notification dashboard_version session
alert_notification_state data_source short_url
alert_rule_tag login_attempt star
annotation migration_log tag
annotation_tag org team
api_key org_user team_member
cache_data playlist temp_user
dashboard playlist_item test_data
dashboard_acl plugin_setting user
dashboard_provisioning preferences user_auth
dashboard_snapshot quota user_auth_token
sqlite> select * from user;
1|0|admin|admin@localhost||0e8518c7400b932a85a63d68dcb31e2728896c1f32dd2db4cca641b4ae26b5f80d4edf6c49f0744c776a365b47baa5179ffc|c6LfXYoaxO|XbuYjq51Qe||1|1|0||2021-09-09 12:01:45|2021-09-09 12:41:58|0|2021-09-13 01:49:30|0
sqlite> update user set password = '59acf18b94d7eb0694c61e60ce44c110c7a683ac6a8f09580d626f90f4a242000746579358d77dd9e570e83fa24faa88a8a6', salt = 'F3FAxVm33R' where login = 'admin';
sqlite>.exit
1
2
3
4
5
6
7
8
9
10
11
js复制代码#进入数据库
sqlite3 /var/lib/grafana/grafana.db
#查看表
.tables
#查看表中数据
select * from user;
#更改密码为admin
update user set password = '59acf18b94d7eb0694c61e60ce44c110c7a683ac6a8f09580d626f90f4a242000746579358d77dd9e570e83fa24faa88a8a6', salt = 'F3FAxVm33R' where login = 'admin';

#退出
.exit

重启grafana

1
js复制代码service grafana-server restart

本文转载自: 掘金

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

LeetCode374 猜数字大小

发表于 2021-11-18

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

题目描述:

374. 猜数字大小 - 力扣(LeetCode) (leetcode-cn.com)

猜数字游戏的规则如下:

  • 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
  • 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。

你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num

1:我选出的数字比你猜的数字大 pick > num

0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num

返回我选出的数字。

示例一

1
2
ini复制代码输入: n = 10, pick = 6
输出: 6

示例二

1
2
ini复制代码输入: n = 1, pick = 1
输出: 1

示例三

1
2
ini复制代码输入: n = 2, pick = 1
输出: 1

示例四

1
2
ini复制代码输入: n = 2, pick = 2
输出: 2

提示:

  • 1 <= n <= 2^31 - 1
  • 1 <= pick <= n

思路分析

二分查找

这个应该是典型的二分查找了,我们的区间即为 [1..n],再根据 guess(x) = 1 代表右区间大了。

AC代码

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
Kotlin复制代码class Solution {
/**
* The API guess is defined in the parent class.
* @param num your guess
* @return -1 if num is lower than the guess number
* 1 if num is higher than the guess number
* otherwise return 0
* fun guess(num:Int):Int {}
*/

class Solution:GuessGame() {
override fun guessNumber(n:Int):Int {
// 中位数严格小于目标数,那么左区间一定不是解
var left = 0
var right = n
while (left < right) {
val mid = (left + right) ushr 1
if (1 == guess(mid)) {
left = mid + 1
} else {
right = mid
}
}
return left
}
}

总结

典型的二分查找,可以尝试套用不同的二分查找公式来写出不一样的解。

参考

猜数字大小 - 猜数字大小 - 力扣(LeetCode) (leetcode-cn.com)

374. Guess Number Higher or Lower 猜数字大小 - 猜数字大小 - 力扣(LeetCode) (leetcode-cn.com)

本文转载自: 掘金

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

Threejs实现脸书元宇宙3D动态Logo

发表于 2021-11-18

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

背景

Facebook 近期将其母公司改名为 Meta,宣布正式开始进军 元宇宙 🪐领域。本文主要讲述通过 Three.js + Blender 技术栈,实现 Meta 公司炫酷的 3D 动态 Logo,内容包括基础模型圆环、环面扭结、管道及模型生成、模型加载、添加动画、添加点击事件、更换材质等。

什么是元宇宙

元宇宙 Metaverse 一词源于 1992 年尼尔·斯蒂芬森的 《雪崩》,该书描述了一个平行于现实世界的虚拟世界 Metaverse,所有现实生活中的人都有一个网络分身 Avatar。维基百科 对元宇宙的描述是:通过虚拟增强的物理现实,呈现收敛性和物理持久性特征的,基于未来互联网,具有链接感知和共享特征的 3D 虚拟空间。

元宇宙的内涵是吸纳了信息革命 5G/6G、互联网革命 web3.0、人工智能革命,以及 VR、AR、MR,特别是游戏引擎在内的虚拟现实技术革命的成果,向人类展现出构建与传统物理世界平行的全息数字世界的可能性;引发了信息科学、量子科学,数学和生命科学的互动,改变科学范式;推动了传统的哲学、社会学甚至人文科学体系的突破;囊括了所有的数字技术。正如电影 《头号玩家》 的场景,在未来某一天,人们可以随时随地切换身份,自由穿梭于物理世界和数字世界,在虚拟空间和时间节点所构成的元宇宙中生活学习。

实现效果

进入正题,先来看看本文示例的实现效果。

🔗 在线预览:dragonir.github.io/3d-meta-log… (由于模型较大,加载进度可能比较缓慢,需要耐心等待)

开发实现

📌 注意:上述示例动图展示的是试炼四,不想看试错过程(试炼一、试炼二、试炼三)的,可直接跳转到试炼四段落查看详细实现流程。失败流程中都列出了难点,知道解决方案的大佬请在评论区不吝赐教。

开发之前我们先观察一下 Meta Logo,可以发现它是一个圆环经过对折扭曲形成的,因此实现它的时候可以从实现圆环开始。

试炼一:THREE.TorusGeometry

Three.js 提供的基础几何体 THREE.TorusGeometry(圆环),它是一种看起来像甜甜圈 🍩 的简单图形。主要参数:

  • radius:可选。定义圆环的半径尺寸。默认值是 1。
  • tube:可选。定义圆环的管子半径。默认值是 0.4。
  • radialSegments:可选。定义圆环长度方向上的分段数。默认值是 8。
  • tubularSegments:可选。定义圆环宽度方向上的分段数。默认值是 6。
  • arc:可选。定义圆环绘制的长度。取值范围是 0 到 2 * π。默认值是 2 * π(一个完整的圆)。

语法示例:

1
js复制代码THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc);

😭 失败:没有找到扭曲圆环的方法。

试炼二:THREE.TorusKnotGeometry

THREE.TorusKnotGeometry 可以用来创建三维环面扭结,环面扭结是一种比较特别的结,看上去像一根管子绕着它自己旋转了几圈。主要参数:

  • radius:可选。设置完整圆环的半径,默认值是 1。
  • tube:可选。设置管道的半径,默认值是 0.4。
  • radialSegments:可选。指定管道截面的分段数,段数越多,管道截面圆越光滑,默认值是 8。
  • tubularSegments:可选。指定管道的分段数,段数越多,管道越光滑,默认值是 64。
  • p:可选。决定几何体将绕着其旋转对称轴旋转多少次,默认值是 2。
  • q:可选。决定几何体将绕着其内部圆环旋转多少次,默认值是 3。

语法示例:

1
js复制代码THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments , p, q);

😭 失败:没找到能够控制手动扭曲程度的方法。

试炼三:THREE.TubeGeometry

THREE.TubeGeometry 沿着一条三维的样条曲线拉伸出一根管。你可以指定一些定点来定义路径,然后使用 THREE.TubeGeometry 创建这根管。主要参数:

  • path:该属性用一个 THREE.SplineCurve3 对象来指定管道应当遵循的路径。
  • segments:该属性指定构建这个管所用的分段数。默认值为 64.路径越长,指定的分段数应该越多。
  • radius:该属性指定管的半径。默认值为 1.
  • radiusSegments:该属性指定管道圆周的分段数。默认值为 8,分段数越多,管道看上去越圆。
  • closed:如果该属性设置为 true,管道的头和尾会连起来,默认值为 false。

代码示例

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
js复制代码// ...
var controls = new function () {
// 点的位置坐标
this.deafultpoints = [
[0, 0.4, -0.4],
[0.4, 0, 0],
[0.4, 0.8, 0.4],
[0, 0.4, 0.4],
[-0.4, 0, 0],
[-0.4, 0.8, -0.4],
[0, 0.4, -0.4]
]
this.segments = 64;
this.radius = 1;
this.radiusSegments = 8;
this.closed = true;
this.points = [];
this.newPoints = function () {
var points = [];
for (var i = 0; i < controls.deafultpoints.length; i++) {
var _x = controls.deafultpoints[i][0] * 22;
var _y = controls.deafultpoints[i][1] * 22;
var _z = controls.deafultpoints[i][2] * 22;
points.push(new THREE.Vector3(_x, _y, _z));
}
controls.points = points;
controls.redraw();
};
this.redraw = function () {
redrawGeometryAndUpdateUI(gui, scene, controls, function() {
return generatePoints(controls.points, controls.segments, controls.radius, controls.radiusSegments,
controls.closed);
});
};
};
controls.newPoints();
function generatePoints(points, segments, radius, radiusSegments, closed) {
if (spGroup) scene.remove(spGroup);
spGroup = new THREE.Object3D();
var material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: false });
points.forEach(function (point) {
var spGeom = new THREE.SphereGeometry(0.1);
var spMesh = new THREE.Mesh(spGeom, material);
spMesh.position.copy(point);
spGroup.add(spMesh);
});
scene.add(spGroup);
return new THREE.TubeGeometry(new THREE.CatmullRomCurve3(points), segments, radius, radiusSegments, closed);
}
// ...

😊 勉强成功:但是管道连成的圆环不够圆,实现完美的圆弧需要精确的坐标,暂时没找到坐标计算方法。

试炼四:Blender + Three.js

虽然使用 THREE.TubeGeometry 可以勉强实现,但是效果并不好,要实现圆滑的环,需要为管道添加精确的扭曲圆环曲线路径函数。由于数学能力有限 🤕️,暂时没找到扭曲圆弧路径计算的方法。因此决定从建模层面解决。

成功 😄:但是手残的我使用 Blender 建模花费了大量的时间 💔。

建模教程

逛 B站 的时候发现了这位大佬发的宝藏视频,刚好解决了自己的难题。

🎦 传送门:【动态设计教程】AE+blender能怎么玩?脸书元宇宙Meta动态logo已完全解析,100%学会

用Blender建模

使用 Blender 进行建模,并导出可携带动画的 fbx 格式,导出的时候不要忘记勾选 烘焙动画 选项。

加载依赖

1
2
3
4
5
html复制代码<script src="./assets/libs/three.js"></script>
<script src="./assets/libs/loaders/FBXLoader.js"></script>
<script src="./assets/libs/inflate.min.js"></script>
<script src="./assets/libs/OrbitControls.js"></script>
<script src="./assets/libs/stats.js"></script>

场景初始化

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
js复制代码var container, stats, controls, compose, camera, scene, renderer, light, clickableObjects = [], mixer, mixerArr = [], manMixer;
var clock = new THREE.Clock();
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
// 场景
scene = new THREE.Scene();
scene.transparent = true;
scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);
// 透视相机:视场、长宽比、近面、远面
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 4, 16);
camera.lookAt(new THREE.Vector3(0, 0, 0));
// 半球光源:创建室外效果更加自然的光源
light = new THREE.HemisphereLight(0xefefef);
light.position.set(0, 20, 0);
scene.add(light);
// 平行光
light = new THREE.DirectionalLight(0x2d2d2d);
light.position.set(0, 20, 10);
light.castShadow = true;
scene.add(light);
// 环境光
var ambientLight = new THREE.AmbientLight(0xffffff, .5);
scene.add(ambientLight);
// 网格
var grid = new THREE.GridHelper(100, 100, 0xffffff, 0xffffff);
grid.position.set(0, -10, 0);
grid.material.opacity = 0.3;
grid.material.transparent = true;
scene.add(grid);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.setSize(window.innerWidth, window.innerHeight);
// 背景色设置为透明
renderer.setClearAlpha(0);
// 开启阴影
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
// 添加镜头控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();
window.addEventListener('resize', onWindowResize, false);
// 初始化性能插件
stats = new Stats();
container.appendChild(stats.dom);
}
// 屏幕缩放
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

📌 想了解场景初始化的详细流程,可阅读我的另一篇文章《使用three.js实现炫酷的酸性风格3D页面》。

加载Logo模型

使用 FBXLoader 加载模型,并设置模型的位置和大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码var loader = new THREE.FBXLoader();
loader.load('assets/models/meta.fbx', function (mesh) {
mesh.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
mesh.rotation.y = Math.PI / 2;
mesh.position.set(0, 1, 0);
mesh.scale.set(0.05, 0.05, 0.05);
scene.add(mesh);
});

添加材质

本文 Logo 使用的是 MeshPhysicalMaterial材质,它是一种 PBR 物理材质,可以更好的模拟光照计算,相比较高光网格材质 MeshPhongMaterial 渲染效果更逼真。使用 THREE.TextureLoader 为材质添加 map 属性来加载模型贴图。下图是金属质感的纹理贴图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
js复制代码var texLoader = new THREE.TextureLoader();
loader.load('assets/models/meta.fbx', function (mesh) {
mesh.traverse(function (child) {
if (child.isMesh) {
if (child.name === '贝塞尔圆') {
child.material = new THREE.MeshPhysicalMaterial({
map: texLoader.load("./assets/images/metal.png"),
metalness: .2,
roughness: 0.1,
exposure: 0.4
});
}
}
});
})

添加动画

  • AnimationMixer 对象是场景中特定对象的动画播放器。当场景中的多个对象独立动画时,可以为每个对象使用一个 AnimationMixer。
  • AnimationMixer 对象的 clipAction 方法生成可以控制执行动画的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
js复制代码loader.load('assets/models/meta.fbx', function (mesh) {
mesh.animations.map(item => {
mesh.traverse(child => {
// 因为模型中有多个物体,并且各自有不同动画,示例中只为贝塞尔圆这个网格添加动画
if (child.name === '贝塞尔圆') {
let mixer = new THREE.AnimationMixer(child);
mixerArr.push(mixer);
let animationClip = item;
animationClip.duration = 8;
let clipAction = mixer.clipAction(animationClip).play();
animationClip = clipAction.getClip();
}
})
})
});

添加动画之后,不要忘了要在 requestAnimationFrame 中更新动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码function animate() {
renderer.render(scene, camera);
// 获得前后两次执行该方法的时间间隔
let time = clock.getDelta();
// 更新logo动画
mixerArr.map(mixer => {
mixer && mixer.update(time);
});
// 更新人物动画
manMixer && manMixer.update(time);
stats.update();
requestAnimationFrame(animate);
}

展示加载进度

FBXLoader 同时返回两个回调函数,可以像下面这样使用,用来展示模型加载进程展示以及加载失败的逻辑实现。

1
2
3
html复制代码<div class="loading" id="loading">
<p class="text">加载进度<span id="progress">0%</span></p>
<div>
1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码var loader = new THREE.FBXLoader();
loader.load('assets/models/meta.fbx', mesh => {
}, res => {
// 加载进程
let progress = (res.loaded / res.total * 100).toFixed(0);
document.getElementById('progress').innerText = progress;
if (progress === 100) {
document.getElementById('loading').style.display = 'none';
}
}, err => {
// 加载失败
console.log(err)
});

实现效果

点击更换材质

监听页面的点击事件,通过 HREE.Raycaster 拿到当前点击对象,为了展示例子,我为点击对象更换了一种材质 THREE.MeshStandardMaterial,并赋予它随机的 color 颜色、metalness 金属质感以及 roughness 粗糙程度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
js复制代码//声明raycaster和mouse变量
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick(event) {
// 通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
raycaster.setFromCamera(mouse, camera);
// 获取raycaster直线和所有模型相交的数组集合
let intersects = raycaster.intersectObjects(clickableObjects);
if (intersects.length > 0) {
console.log(intersects[0].object)
let selectedObj = intersects[0].object;
selectedObj.material = new THREE.MeshStandardMaterial({
color: `#${Math.random().toString(16).slice(-6)}`,
metalness: Math.random(),
roughness: Math.random()
})
}
}
window.addEventListener('click', onMouseClick, false);

📌 更多关于网格材质的知识,可参考文章末尾的链接。

加载人物模型

人物模型的加载流程和 Logo 模型加载流程是一样的。我添加了一个正在施展龟派气功的人物,没想到与 Logo 模型的旋转动画非常契合 😂 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
js复制代码loader.load('assets/models/man.fbx', function (mesh) {
mesh.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
mesh.rotation.y = Math.PI / 2;
mesh.position.set(-14, -8.4, -3);
mesh.scale.set(0.085, 0.085, 0.085);
scene.add(mesh);
manMixer = new THREE.AnimationMixer(mesh);
let animationClip = mesh.animations[0];
let clipAction = manMixer.clipAction(animationClip).play();
animationClip = clipAction.getClip();
}, res => {
let progress = (res.loaded / res.total * 100).toFixed(0);
document.getElementById('progress').innerText = progress + '%';
if (Number(progress) === 100) {
document.getElementById('loading').style.display = 'none';
}
}, err => {
console.log(err)
});

本文示例人物模型来源于mixamo.com,该网站有有上百种人物和上千种动作可自由组合,免费 下载。大家可以挑选自己喜欢的人物和动画动作来练习 Three.js。

总结

本文中涉及到的主要知识点包括:

  • THREE.TorusGeometry:圆环。
  • THREE.TorusKnotGeometry:环面扭结。
  • THREE.TubeGeometry:管道。
  • Blender: 建模。
  • FBXLoader: 加载模型,显示加载进度。
  • TextureLoader:加载材质。
  • THREE.AnimationMixer:加载动画。
  • THREE.Raycaster:捕获点击模型。

🔗 完整代码:github.com/dragonir/3d…

参考资料

  • [1]. 使用three.js实现炫酷的酸性风格3D页面
  • [2]. ThreeJs认识材质
  • [3]. Three之Animation初印象
  • [4]. 什么是元宇宙?

本文转载自: 掘金

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

Java8中的时间处理

发表于 2021-11-18

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

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

在Java8中, 新的时间及⽇期API位于java.time包中, 该包中有哪些重要的类。 分别代表了什么?

Instant: 时间戳

Duration: 持续时间, 时间差

LocalDate: 只包含⽇期, ⽐如: 2021-10-20

LocalTime: 只包含时间, ⽐如: 231210

LocalDateTime: 包含⽇期和时间, ⽐如: 2021-10-20 231421

Period: 时间段

ZoneOffset: 时区偏移量, ⽐如: +8:00

ZonedDateTime: 带时区的时间

Clock: 时钟, ⽐如获取⽬前美国纽约的时间

新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

LocalTime 和 LocalDate的区别?

LocalDate表⽰⽇期, 年⽉⽇, LocalTime表⽰时间, 时分 秒

获取当前时间

在Java8中,使用如下方式获取当前时间:

1
2
3
4
5
Java复制代码LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
System.out.printf("Year : %d Month : %d day : %d t %n", year,month, day);复制ErrorOK!

创建指定日期的时间

1
java复制代码LocalDate date = LocalDate.of(2021, 01, 01);复制ErrorOK!

检查闰年

直接使⽤LocalDate的isLeapYear即可判断是否闰年

1
2
3
java复制代码LocalDate nowDate = LocalDate.now();
//判断闰年
boolean leapYear = nowDate.isLeapYear();复制ErrorOK!

计算两个⽇期之间的天数和⽉数

在Java 8中可以⽤java.time.Period类来做计算。

1
java复制代码Period period = Period.between(LocalDate.of(2021, 1, 1),LocalDate.of(2021, 5, 5));

本文转载自: 掘金

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

【Spring系列】SpringContextgetBea

发表于 2021-11-18

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

在实际的业务开发中,为了方便获取Spring容器中的Bean对象,一个常见的case就是创建一个SpringUtil类,内部持有SpringContext上下文,然后提供一个静态的方式获取bean对象,然而这种使用姿势,一个不小心可能导致npe

今天我们来看一下这个场景

场景复现

1. 基础工程搭建

搭建一个基础的SpringBoot项目,具体的过程这里省略,下面标注关键的信息

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

开一个web服务用于测试

1
2
3
4
5
6
7
xml复制代码<dependencies>
<!-- 邮件发送的核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

2. SpringUtil

构建一个基础的SpringUtil工具类,借助SpringContextAware来持有上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码@Component
public class SpringUtil implements ApplicationContextAware, EnvironmentAware {
private static ApplicationContext applicationContext;
private static Environment environment;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtil.applicationContext = applicationContext;
}

@Override
public void setEnvironment(Environment environment) {
SpringUtil.environment = environment;
}

public static <T> T getBean(Class<T> clz) {
return applicationContext.getBean(clz);
}

public static String getProperty(String key) {
return environment.getProperty(key);
}
}

3. 使用实例

首先构建一个简单的bean对象

1
2
3
4
5
6
7
8
9
10
java复制代码@Component
public class TestDemo {
public String showCase() {
return UUID.randomUUID().toString();
}

public String testCase() {
return "test-" + Math.random();
}
}

接着是另外一个对象,依赖上面这个对象,对外提供的主要接口是 process,其内部实现是根据枚举类,来做的一个策略选择;

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
java复制代码@Component
public class BasicDemo {
@Autowired
private TestDemo testDemo;

public String process(String data) {
return Data.process(data);
}

private String show() {
return testDemo.showCase();
}

String test() {
return testDemo.testCase();
}

public enum Data {
SHOW("show") {
@Override
String doProcess() {
return SpringUtil.getBean(BasicDemo.class).show();
}
},
CASE("test") {
@Override
String doProcess() {
return SpringUtil.getBean(BasicDemo.class).test();
}
};

private String data;

Data(String data) {
this.data = data;
}

abstract String doProcess();

static String process(String data) {
for (Data d: values()) {
if (d.data.equalsIgnoreCase(data)) {
return d.doProcess();
}
}
return null;
}
}
}

重点关注上面实现中的枚举类,在枚举类中,根据SpringUtil获取到BasicDemo对象,然后执行它的私有方法show()及包内方法test()

这种用法会有什么问题么?

4. 测试case

接下来写个简单接口测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@Aspect
@RestController
@SpringBootApplication
public class Application implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}

@Autowired
private BasicDemo basicDemo;

@GetMapping(path = "show")
public String show(String data) {
return basicDemo.process(data);
}
}

接下来访问看看会是怎样

what? 不是说会npe么?这不是很正常的返回了么!!!

接下来就是见证bug的时刻了,同样是上面的代码,就让它出现npe

5. bug复现

接下来我们添加一个切面,目的就是让通过SpringUtil.getBean获取到的对象是代理类

1
2
3
4
5
java复制代码// 注意在这个方法所在类上,添加注解 @Aspect
@Around("execution(public * com.git.hui.boot.web.interceptor.server.BasicDemo.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}

然后再重新请求一下上面的访问

在访问私有方法 show()这里抛了异常,从服务端的堆栈可以看到异常类型为NPE,主要原因就是 testDemo 为null

简单来讲就是访问代理类的私有方法时,内部若有注入bean对象,这个时候拿到的是null

这个就有点神奇了,那么我们再变一下,私有方法内部不直接使用注入的bean对象,改调用一个bean对象的共有方法,会怎样

将上面的show()方法重写一下

1
2
3
4
5
6
7
java复制代码private String show() {
return show2();
}

public String show2() {
return testDemo.showCase();
}

再次测试,输出如

居然没有问题!!!

就这么神奇有木有,那么是什么原因呢?

  • 关键知识点:Spring代理类的生成逻辑

6. 小结

好像刚进入主体,结果到这里就结束了,真是过分😡,这里先小结一下这个问题出现的场景,至于具体原因有待下片博文介绍

当我们通过SpringContext获取到的bean对象时,不要直接访问它的私有方法,可能导致npe

100%必先的场景

  • 这个bean对象有代理类(如有切面拦截了它,如类内部有一些特定注解)
  • 私有方法内使用了注入对象

看到上面就会有个疑问,谁会去访问私有方法呢?我脑子又没坑😒,何况私有方法在外面也访问不了啊

这就涉及到一个相当常见的场景了,类内部方法A调用希望切面拦截的方法B,这时我们常这么做

1
2
3
4
5
6
7
8
9
10
11
12
13
typescript复制代码public class A {
@Autowired
private A a;

public void test() {
a.testB();
}

@Point
public String testB() {
return "hello";
}
}

上面的test方法,访问testB方法就可以走切面逻辑,在上面这个类中,就有可能出现直接是用a.privetMethod()的场景了

此外就是反射执行某些逻辑的时候也有可能出现访问私有方法了,这里就不展开了;

欢迎有兴趣的小伙伴回复互动一下,也可以关注我的公众号:一灰灰blog

III. 不能错过的源码和相关知识点

0. 项目

  • 工程:github.com/liuyueyi/sp…
  • 源码:github.com/liuyueyi/sp…

1. 微信公众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

  • 一灰灰Blog个人博客 blog.hhui.top
  • 一灰灰Blog-Spring专题博客 spring.hhui.top

本文转载自: 掘金

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

1…289290291…956

开发者博客

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