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

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


  • 首页

  • 归档

  • 搜索

小码农教你循环队列 设计循环队列

发表于 2021-11-13

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

设计循环队列

题目

image-20211103204637814

我们会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。

image-20211103204617520

image-20211103230000226

可以认为队尾tail不是队尾,而是我们认知上队尾的后一个

数组形式(通过下标控制来达到循环的效果)

环队结构体(数组)

1
2
3
4
5
6
c复制代码typedef struct {
int* a;
int front;
int tail;
int k;//数组元素(队列长度)
} MyCircularQueue;

环队初始化

1
2
3
4
5
6
7
c复制代码MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* q = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
q->a = (int*)malloc(sizeof(int)*(k+1));//队列长度为k我们要多开一个
q->front = q->tail = 0;
q->k = k;
return q;
}

判断环队为空

1
2
3
c复制代码bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->tail;
}

判断环队为满

1
2
3
c复制代码bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail+1)%(obj->k+1) == obj->front;
}

环队入数据并入成功返回真

1
2
3
4
5
6
7
8
9
10
c复制代码bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(!myCircularQueueIsFull(obj))
{
obj->a[obj->tail] = value;//不是队满就下标tail的数据为value
obj->tail++;
obj->tail %= obj->k+1;//tail就步进一位
return true;
}
return false;//队满就插不进去了
}

环队删数据并删成功返回真

1
2
3
4
5
6
7
8
9
c复制代码bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
{
obj->front++;//队不空就是出队,头标步进就行了
obj->front %= obj->k+1;
return true;
}
return false;//对空就删不了了
}

环队取队头数据(对空返回-1)

1
2
3
4
5
6
7
c复制代码int myCircularQueueFront(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
{
return obj->a[obj->front]; //取front位置的数据就行
}
return -1;
}

环队取队尾数据(对空返回-1)

1
2
3
4
5
6
7
c复制代码int myCircularQueueRear(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
{
return obj->a[obj->tail == 0 ? obj->k : obj->tail-1];//取tail前一个数据就行,tail为0,前一个就是k位置数据
}
return -1;
}

环队销毁

1
2
3
4
5
6
7
8
c复制代码void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
obj->a = NULL;
obj->front = 0;
obj->tail = 0;
obj->k = 0;
free(obj);
}

image-20211104220502511

image-20211104220526761

环队(数组实现)

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
c复制代码typedef struct {
int* a;
int front;
int tail;
int k;//数组元素(队列长度)
} MyCircularQueue;

MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* q = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
q->a = (int*)malloc(sizeof(int)*(k+1));//队列长度为k我们要多开一个
q->front = q->tail = 0;
q->k = k;
return q;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(!myCircularQueueIsFull(obj))
{
obj->a[obj->tail] = value;//不是队满就下标tail的数据为value
obj->tail++;
obj->tail %= obj->k+1;//tail就步进一位
return true;
}
return false;//队满就插不进去了
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
{
obj->front++;//队不空就是出队,头标步进就行了
obj->front %= obj->k+1;
return true;
}
return false;//对空就删不了了
}

int myCircularQueueFront(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
{
return obj->a[obj->front]; //取front位置的数据就行
}
return -1;
}

int myCircularQueueRear(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
{
return obj->a[obj->tail == 0 ? obj->k : obj->tail-1];//取tail前一个数据就行,tail为0,前一个就是k位置数据
}
return -1;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail+1)%(obj->k+1) == obj->front;
}

void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
obj->a = NULL;
obj->front = 0;
obj->tail = 0;
obj->k = 0;
free(obj);
}

链表形式

环队结构体(链表)

1
2
3
4
5
6
7
8
9
10
11
12
13
c复制代码typedef int CQDataType;//环队数据类型

typedef struct CQNode
{
CQDataType x;//环队节点数据
struct CQNode* next;//环队节点指针
}CQNode;

typedef struct {
CQNode* head;
CQNode* tail;
int k;
} MyCircularQueue;//空环队就两个裸指针

环队初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
c复制代码MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
CQNode* cur = (CQNode*)malloc(sizeof(CQNode));
cq->k = k;
cq->head = cq->tail = cur;
while(k--)
{
CQNode* newnode = (CQNode*)malloc(sizeof(CQNode));
CQNode* tail = cq->tail;//最后一个节点
tail->next = newnode;//最后一个节点指向新的节点
newnode->next = cq->head;//新的节点指向头节点
cq->tail=newnode;
}
cq->head = cq->tail;
return cq;
}

判断环队为空

1
2
3
4
c复制代码bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
return obj->head == obj->tail;
}

判断环队为满

1
2
3
4
c复制代码bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
return obj->tail->next == obj->head;
}

环队入数据并入成功返回真

1
2
3
4
5
6
7
8
9
10
c复制代码bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsFull(obj))
{
obj->tail->x = value;
obj->tail = obj->tail->next;
return true;
}
return false;
}

环队删数据并删成功返回真

1
2
3
4
5
6
7
8
9
c复制代码bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsEmpty(obj))
{
obj->head = obj->head->next;
return true;
}
return false;
}

环队取队头数据(对空返回-1)

1
2
3
4
5
6
c复制代码int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsEmpty(obj))
return obj->head->x;
return -1;
}

环队取队尾数据(对空返回-1)

1
2
3
4
5
6
7
8
9
10
11
12
13
c复制代码int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsEmpty(obj))
{
CQNode*ret=obj->head;
while(ret->next!=obj->tail)
{
ret=ret->next;
}
return ret->x;
}
return -1;
}

环队销毁

1
2
3
4
5
6
7
8
9
10
11
c复制代码void myCircularQueueFree(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
while(obj->head!=obj->tail)
{
CQNode*ret=obj->tail;
obj->tail=obj->tail->next;
free(ret);
}
free(obj->head);
free(obj);
}

image-20211105064130129

环队(链表实现)

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

typedef struct CQNode
{
CQDataType x;//环队节点数据
struct CQNode* next;//环队节点指针
}CQNode;

typedef struct {
CQNode* head;
CQNode* tail;
int k;
} MyCircularQueue;//空环队就两个裸指针

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);


MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
CQNode* cur = (CQNode*)malloc(sizeof(CQNode));
cq->k = k;
cq->head = cq->tail = cur;
while(k--)
{
CQNode* newnode = (CQNode*)malloc(sizeof(CQNode));
CQNode* tail = cq->tail;//最后一个节点
tail->next = newnode;//最后一个节点指向新的节点
newnode->next = cq->head;//新的节点指向头节点
cq->tail=newnode;
}
cq->head = cq->tail;
return cq;
}


bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsFull(obj))
{
obj->tail->x = value;
obj->tail = obj->tail->next;
return true;
}
return false;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsEmpty(obj))
{
obj->head = obj->head->next;
return true;
}
return false;
}

int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsEmpty(obj))
return obj->head->x;
return -1;
}

/*int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsEmpty(obj))
{
CQNode* ret = obj->head;//这个ret 是用来找tail前一个的,不可用直接返回tail,会改变原来环队的链接关系
while(--obj->k)
{
obj->tail = obj->tail->next;
}
ret = obj->tail;
obj->tail = obj->tail->next;
return obj->tail->x;
}
return -1;
}*/
int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
if(!myCircularQueueIsEmpty(obj))
{
CQNode*ret=obj->head;
while(ret->next!=obj->tail)
{
ret=ret->next;
}
return ret->x;
}
return -1;
}


bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
return obj->tail->next == obj->head;
}

void myCircularQueueFree(MyCircularQueue* obj) {
assert(obj->head && obj->tail);
while(obj->head!=obj->tail)
{
CQNode*ret=obj->tail;
obj->tail=obj->tail->next;
free(ret);
}
free(obj->head);
free(obj);
}

实际上这题我报错我不想找了太恶心了(家凌帮我找错的非常感谢)

img

本文转载自: 掘金

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

区块链卡牌游戏nft游戏开发

发表于 2021-11-13

随着推特等社交和科技巨头纷纷抢滩元宇宙布局,各类 NFT 项目也层出不穷,NFT游戏逐渐开始火了起来。区块链游戏则是建立在公链上的,区块链的分布式结构能够有效抵御黑客入侵,越是活跃的公链就越不可能被黑客篡改数据,虚拟资产的安全性更高。让更多的游戏及资产上链,并以爆款区块链nft宠物游戏系统的大量出现为标志达到高潮;而随着区块链宠物游戏系统以及相应虚拟资产的不断增多,跨游戏虚拟资产交易市场,以及基于区块链的游戏分发平台/社区的也会逐步兴起,成为流量汇集的中心。

一个好的 NFT 游戏项目离不开出色的剧本故事和有吸引力的游戏机制。

微信截图_20211112182522.png

Gods Unchaine是一款区块链NFT 卡牌游戏,里面人物形象生动。玩家可以选择自己喜欢的角色。玩家创造具有不同超能力和力量的卡牌去对抗其他玩家。当您获胜时,您会找到游戏内的道具来使用或出售。如果您赢得了排名游戏,您可以开始赚取Flux,让您制造强大的NFT 卡片。接着,您可以将其出售,或是重新投资新卡片来获得利润,并继续这个过程。

微信截图_20211112182633.png

与其他免费游戏不同,这个游戏让您能完全拥有游戏内物品的所有权。收集稀有卡片,构建您的甲板,并出售给其他玩家的卡。一旦发现神话卡,它将被替换为六神主题宣传卡之一的机会。区块链卡牌游戏NFT游戏开发,我们公司对于区块链的nft游戏开发有一定的研究,根据客户的需求可以定制开发,有需要可私信,欢迎大家一起交流。

微信截图_20211113134140.png

本文转载自: 掘金

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

手把手教 Docker Compose快速启动Postg

发表于 2021-11-13

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

借助于容器技术,我们能够方便的创建运行测试环境,今天这篇文章手把手教你们使用Docker Compose快速启动Postgres。

一、创建Docker Compose文件

首先创建一个文件夹来存储我们的文件:

1
shell复制代码mkdir ~/postgres-demo && cd ~/postgres-demo

然后创建一个 docker-compose. yml 文件:

1
shell复制代码touch docker-compose.yml

在文件中添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
yaml复制代码version: '3'

services:
postgres:
image: postgres:13.1
healthcheck:
test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "root" ]
timeout: 45s
interval: 10s
retries: 10
restart: always
environment:
- POSTGRES_USER=root
- POSTGRES_PASSWORD=password
- APP_DB_USER=docker
- APP_DB_PASS=docker
- APP_DB_NAME=docker
volumes:
- ./db:/docker-entrypoint-initdb.d/
ports:
- 5432:5432
  • image,指定使用何种 Docker 映像,包括版本号
  • healthcheck,用于确保 Postgres 正在运行,然后依赖于它的其他服务才能运行
  • restart,重新启动总是确保 DB 在系统启动时启动

下面的环境变量用于分配 Postgres 主数据库的用户名和密码:

  • POSTGRES_USER 用户
  • POSTGRES_PASSWORD 密码

init 脚本使用以下环境变量创建数据库用户和数据库供应用程序使用:

  • APP_DB_USER 应用程序数据库
  • APP_DB_PASS 应用程序数据库通过
  • APP_DB_NAME 应用程序数据库名

对于volume选项,我们将在/docker-entrypoint-initdb.d/中将名为db的本地文件夹映射到容器中的文件夹,这是我们将在下一步中放置数据库init脚本的地方。

我们还通过将Postgres端口分配给端口选项,将端口暴露于我们的服务器,这将允许我们从本地机器连接到数据库。

二、创建数据库init脚本

创建一个名为 db 的文件夹来存储 init 脚本。

1
shell复制代码mkdir db

创建一个名为01-init.sh 的脚本。

1
shell复制代码touch db/01-init.sh

添加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
postgres复制代码#!/bin/bash
set -e
export PGPASSWORD=$POSTGRES_PASSWORD;
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER $APP_DB_USER WITH PASSWORD '$APP_DB_PASS';
CREATE DATABASE $APP_DB_NAME;
GRANT ALL PRIVILEGES ON DATABASE $APP_DB_NAME TO $APP_DB_USER;
\connect $APP_DB_NAME $APP_DB_USER
BEGIN;
CREATE TABLE IF NOT EXISTS event (
id CHAR(26) NOT NULL CHECK (CHAR_LENGTH(id) = 26) PRIMARY KEY,
aggregate_id CHAR(26) NOT NULL CHECK (CHAR_LENGTH(aggregate_id) = 26),
event_data JSON NOT NULL,
version INT,
UNIQUE(aggregate_id, version)
);
CREATE INDEX idx_event_aggregate_id ON event (aggregate_id);
COMMIT;
EOSQL

这个脚本将:

  • 创建一个新用户,其名称分配给 app_db_user,密码分配给 app_db_pass
  • 用分配给 APP_db_name 的任何名称创建一个数据库
  • 为数据库上的用户授予所有权限
  • 连接到数据库并创建一个名为 event 的表

三、docker-compose up

运行 docker compose 以启动 Postgres 数据库并运行数据库 init 脚本。

1
复制代码docker-compose up

少年,没看够?点击石头的主页,随便点点看看,说不定有惊喜呢?欢迎支持点赞/关注/评论,有你们的支持是我更文最大的动力,多谢啦!

本文转载自: 掘金

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

【spring源码-7】bean的实例化(4)后置处理 引题

发表于 2021-11-13

上一篇【spring源码-6】bean的实例化(3)注解收集、依赖注入(DI)

引题

上篇回顾:

image.png

注解收集

  1. CommonAnnotationBeanPostProcessor 收集 @PostConstruct、@PreDestroy、@Resource 注解。
  2. AutowiredAnnotationBeanPostProcessor 收集 @Autowired、@Value 注解。
    通过 BeanPostProcessor 完成收集过程。

依赖注入(DI)

对含有 @Autowired、@Value、@Resource 注解的属性和方法,通过反射完成注入。
3. 注入属性,如果是引用类型,会触发 getBean() 实例化需要注入的对象(实例化逻辑就是之前说到的)。
4. 注入方法时,如果方法形参是引用类型,也会触发 getBean() 操作。
556 行:完成了bean的实例化。

539 行:完成了依赖注入。此时bean已经完成了实例化。

实例化完成之后还有一些后置工作:

initializeBean()

一、Aware 接口的调用

主要逻辑:

Snipaste_2021-11-13_12-49-39.png

如果当前 bean 实现了 Aware 接口,就调用对应的方法:

  1. 实现了 BeanNameAware 接口,调用 setBeanName(beanName);
  2. 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader(bcl);
  3. 实现了 BeanFactoryAware 接口,调用 setBeanFactory(AbstractAutowireCapableBeanFactory.this);

应用示例:

1. BeanNameAware 接口

image.png

2. BeanClassLoaderAware 接口

image.png

3. BeanFactoryAware 接口

image.png

二、applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)

image.png
1786 行:调用到本类方法。如下图:

image.png

BeanProcessor 接口的应用:

循环所有的 BeanProcessor,并调用 postProcessAfterInitialization();完成不同的功能实现。

1. InitDestroyAnnotationBeanPostProcessor 类

完成对类中带有 @PostConstruct 注解的方法的调用。

Snipaste_2021-11-13_13-43-30.png
155 行:获取到类中带有 @PostConstruct 注解的方法,(注解收集的时候已经完成了对 类中带有 @PostConstruct 注解的方法 的收集)。

157 行:调用到如下图:

image.png
333 行:调用 Element 对象的invoke();当前是方法,调用到如下图:

image.png
388 行:将方法设置成可访问的。

389 行:反射调用。

2. ApplicationContextAwareProcessor

完成诸多 Aware 接口的调用。

image.png
81 - 85 行:如果当前bean不是这些Aware类型的,不进行操作,直接返回bean。

106 行:反之就调用对应的方法。

三、invokeInitMethods(beanName, wrappedBean, mbd)

支持 InitializingBean 接口、xml bean标签中 init-method 属性

image.png

主逻辑

定义类信息:
image.png
xmlbean标签:
image.png

image.png

  1. 支持 InitializingBean 接口
    1836 行:判断当前bean有没有实现 InitializingBean 接口。

1853 行:如果实现了就调用 afterPropertiesSet();注意该方法没有形参。

  1. 支持 xml bean标签中 init-method 属性。

1858 行:获取到BD对象中 InitethodName 属性值。

1859 - 1862 行:如果bean标签中设置了 init-method 属性,并且这个方法名称不是 afterPropertiesSet,就调用这个方法。还是反射调用,调用方法如下:
image.png

应用示例:

image.png
image.png

applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)

逻辑与上面
二、applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName) 类似。
image.png


总结

spring在完成对 bean 实例化之后,进行了一系列后置操作:

  1. 完成诸多 Aware 接口的调用(功能很多…)。
  2. 完成 类的初始化方法的调用。调用顺序为:@PostConstruct 注解 > InitializingBean 接口 > init-method 属性。

注:本文通过源码 + 行说明的方式进行描述,若不好理解可留言。本文仅为个人学习记录,有错误的地方,请大佬们指正。

下一篇【spring源码-8】bean的实例化(5)FactoryBean接口

本文转载自: 掘金

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

Java基础学习12之JDK三大主要特性 Java基础学习1

发表于 2021-11-13

Java基础学习12之JDK三大主要特性

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

关于作者

  • 作者介绍

🍓 博客主页:作者主页

🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。

🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。


JDK三大主要特性——泛型

泛型可以帮助我们解决参数转换的问题

泛型的引出

下面首先通过一个简单分析来研究一下泛型出现的主要目的是什么?例如现在要求定义一个表示坐标的操作类(Point)这个类可以表示三种类型的坐标:

​ 整数坐标:x=10、y=20;

​ 小数坐标:x=10.1、y=20.3;

​ 字符串数据:x=“东经10度”、y=“西经20度”。

类之中如果想要表示以上的数据,一定需要定义x和y两个属性,而且每一个属性可以接收三种数据类型,那么只能使用Object类来定义会比较合适,这样会发生如下的几种转换关系:

整数:int→自动装箱为Integer→向上转型为Object;

小数:double→自动装箱为Double→向上转型为Obejct;

字符串:字符串→向上转型为Obejct;

设置整形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
java复制代码package com.demo;
class Point {
private Object x;
private Object y;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
public class PointDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX(10);
p.setY(20);
//第二步:取出数据
int x = (Integer)p.getX();
int y = (Integer)p.getY();
System.out.println("x = " + x + ",y = " + y);
}
}

设置小数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
java复制代码package com.demo;
class Point {
private Object x;
private Object y;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
public class PointDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX(10.1);
p.setY(20.2);
//第二步:取出数据
double x = (Double)p.getX();
double y = (Double)p.getY();
System.out.println("x = " + x + ",y = " + y);
}
}

设置字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
java复制代码package com.demo;
class Point {
private Object x;
private Object y;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
public class PointDemo {
public static void main(String[] args) {
//第一步:设置数据
Point p = new Point();
p.setX("东经10");
p.setY("西经20");
//第二步:取出数据
String x = (String)p.getX();
String y = (String)p.getY();
System.out.println("x = " + x + ",y = " + y);
}
}

看起来所有功能都实现了,并根据之前所学的内容,也只能做到这些了,但是本程序还有一系列问题。

本程序解决问题的关键就在Object类,所有的类型都可以想Obejct转换,但是成也是它败也是它。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class PointDemo {
public static void main(String[] args) {
Point point = new Point();
//设置参数
point.setX(10);
point.setY("北纬");
//取出参数
String x = (String) point.getX();
String y = (String) point.getY();
System.out.println("x的坐标是:"+x+"y的坐标是:"+y);
}
}

这个时候程序并没有任何的语法错误,因为数字10 被包装成了Integer,可以使用Obejct接收,从技术上而言,本操作没有问题,但是从实际来讲,因为没有统一,多以在取得数据并且执行向下转型的过程中就会出现如下的错误提示信息:

1
2
java复制代码Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at cn.mldn.demo.TestDemo.main(PointDemo.java:30)

所以,就可以得出一个结论,以上的程序存在安全隐患,但是并没有在程序的编译过程中检查出来,而现在就可以利用泛型来解决这种问题。

泛型实现

泛型:类之中操作的属性或方法的参数类型不在定义的时候声明,而是在使用的时候动态设置。

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复制代码package com.demo;
//在定义Point不知道是什么类型,由使用者来进行定义使用
class Point<T> {//T表示参数,一个占位标记
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
public class PointDemo {
public static void main(String[] args) {
//第一步:设置数据
Point<String> p = new Point<String>();
p.setX("东经10");
p.setY("西经20");
//第二步:取出数据
String x = p.getX();//避免了向下转型
String y = p.getY();
System.out.println("x = " + x + ",y = " + y);
}
}

测试没有了向下转型的操作关系,那么程序就避免了安全性的问题,而且如果设置的类型不统一,在程序编译的过程之中也是可以很好的解决了,直接会报出语法错误。

而且当用户在使用Point类声明对象的时候没有设置泛型,程序在编译的过程之中,会提示警告信息,而且为了保证程序不出现错误,所有的类型都将使用Obejct进行处理。使用泛型可以很好的解决数据类型的统一问题。

但是在此处需要提醒的是,JDK1.5和JDK1.7在定义泛型的时候是稍微有些区别的。

JDK1.5的时候声明泛型的操作

1
java复制代码Point<String> p= new Point<String>();

以上是JDK1.5的语法,在声明对象和实例化对象的时候都必须设置好泛型类型。

JDK1.7的时候简化了

1
java复制代码Point<String> p= new Point< >();

​ 这个时候实例化对象时泛型的泛型类型就通过声明时泛型类型来定义了。

通配符

泛型的而出现的确是可以解决了数据的统一问题以及避免了向下转型操作,但同事也会带来新的问题,下面通过一段程序,来观察一下会产生什么问题?

为了简化定义一个简单的泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码package com.demo;

class Message<T>{
private T info;

public T getInfo() {
return info;
}

public void setInfo(T info) {
this.info = info;
}
}

以上的类对象进行引用传递

1
2
3
4
5
6
7
8
9
10
java复制代码public class MessageDemo {
public static void main(String[] args) {
Message<String> msg = new Message<>();
msg.setInfo("hello,world!");
print(msg);//以上的类对象进行引用传递
}
public static void print(Message<String> s){
System.out.println(s.getInfo());
}
}

但是如果现在定义的泛型类型不是String呢?例如:换成了int(不能是基本数据类型,只能是包装类)

1
2
3
4
5
6
7
8
9
10
java复制代码public class MessageDemo {
public static void main(String[] args) {
Message<Integer> msg = new Message<>();
msg.setInfo(100);
print(msg);//无法进行引用传递
}
public static void print(Message<String> s){
System.out.println(s.getInfo());
}
}

发现这个时候的print()方法无法再接收Message对象的引用,因为这个方法只能够接收Message对象的引用,那么可以将print()方法重载换成Message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码public class MessageDemo {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Message<Integer> msg = new Message<>();
msg.setInfo(100);
print(msg);
}
public static void print(Message<String> msg){
System.out.println(msg.getInfo());
}
public static void print(Message<Integer> msg){
System.out.println(msg.getInfo());
}
}

这个时候发现按照之前的方式根本就无法进行方法的重载,方法的重载没有说为一个类而定义的,因为方法重载的时候观察的不是泛型类型,而是类的名称,或者说是数据类型的,所以现在就可以发现,这个给出了泛型类之后,就相当于将一个类又划分成了几个小类。

image-20210815130238917

那么现在的问题:方法接收的参数问题又严重了,而且比之前使用对象多态性解决问题时出现的麻烦更大了,至少那个时候可以利用重载来接收一个类的所有子类对象,而现在连重载都使用不了。

​ 这个时候,有人提出了,干脆在定义方法的时候就别写泛型类型了。

定义方法的时候不定义泛型类型

1
2
3
4
5
6
7
8
9
10
11
java复制代码public class MessageDemo {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Message<Integer> msg = new Message<>();
msg.setInfo(100);
print(msg);
}
public static void print(Message msg){
System.out.println(msg.getInfo());
}
}

虽然现在print()方法的参数上出现了警告,但是现在的程序可算是正常了,但是新的问题又来了。问题就在于方法操作中,没有类型限制了。

1
2
3
4
java复制代码public static void print(Message msg){
msg.setInfo(100);
System.out.println(msg.getInfo());
}

发现此时在print()方法之中操作的时候,由于没有设置泛型类型,那么所有类型都统一变为了Object,也就可以修改了。而通过本程序也就发现了,必须找到一种方法,:此方法可以接收任意的泛型类型的设置,并且不能修改,只能输出,为了解决这样的问题,可以使用通配符“?”表示。

1
2
3
java复制代码public static void print(Message<?> msg){
System.out.println(msg.getInfo());
}

由于“?”出现的情况较多,尤其在学习一些类库的时候,所以对于“?”就记住一点,表示任意类型,如果有参数返回的时候也是这个“?”,当成Object进行理解。

既然现在谈到了Obejct,那么现在实际上又有了另外一个问题:对于所有的子类,都是Object子类,那么如果对于之前的程序都使用Object能不能接收?

1
2
java复制代码Message<String> msg = new Message<>();
Message<Object> s = msg;

因为Object的范围比String的范围大。

而在通配符“?”上有衍生出了两个子符号:

  1. 设置泛型的上限:?extends 类;

​ 例如:?extends Number,表示只能是Number或者是Number的子类Integer等;

​ 2.设置泛型的下限:?super类;

​ 例如:?super String,表示只能是String或者是String的父类(Object)

设置泛型上限

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.demo;

class Message<T extends Number>{
private T info;

public T getInfo() {
return info;
}

public void setInfo(T info) {
this.info = info;
}
}
public class MessageDemo {
public static void main(String[] args) {
Message<Integer> msg = new Message<>();
msg.setInfo(100);
//100
print(msg);
}
public static void print(Message<? extends Number> s){
System.out.println(s.getInfo());
}
}

设置泛型下限

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.demo;

class Message<T>{
private T info;

public T getInfo() {
return info;
}

public void setInfo(T info) {
this.info = info;
}
}
public class MessageDemo {
public static void main(String[] args) {
Message<String> msg = new Message<>();
msg.setInfo("Hello,world!100");
//Hello,world!100
print(msg);
}
//此时使用通配符“?”描述的是它可以接收任何任意数据类型,但是由于不确定类型,无法修改
public static void print(Message<? super String> s){
System.out.println(s.getInfo());
}
}

泛型接口

在之前的所有定义的泛型之中,都是在类上定义的,而对于接口也是可以进行泛型定义的,而使用泛型定义的接口可以称为泛型接口。

1
2
3
java复制代码interface Message<T>{
public String echo(T msg);
}

而对于泛型接口的实现,在Java中有两种方式:

方式一:在子类上继续定义泛型,同时此泛型继续在接口上使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码package com.test;
interface IMessage<T>{
public void print(T t);
}
class MessageImpl<T> implements IMessage<T>{

@Override
public void print(T t) {
// TODO Auto-generated method stub
System.out.println(t);
}


}
public class MessageTest {
public static void main(String[] args) {
IMessage<String> msgimpl = new MessageImpl();
msgimpl.print("Hello,world!!");
}
}

方式二:在子类上设置具体类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码package com.test;
interface IMessage<T>{
public void print(T t);
}
class MessageImpl implements IMessage<String>{

@Override
public void print(String t) {
// TODO Auto-generated method stub
System.out.println(t);
}

}
public class MessageTest {
public static void main(String[] args) {
IMessage<String> msgimpl = new MessageImpl();
msgimpl.print("Hello,world!!");
}
}
3.5 泛型方法

对于泛型除了可以在类上定义,也可以在方法上定义,而在方法上进行泛型的时候这个方法不一定非在泛型类中定义。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class TestDemo {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Integer result[] = get(1,2,3);
for(int temp : result){
System.out.println(temp);
}
}
public static <T> T[] get(T... date){
return date;
}
}

JDK三大主要特性——枚举

在讲解枚举之前回顾一个概念:多例设计模式,构造方法私有化(非public),之后在类的内部存在若干个指定的对象,通过一个方法返回指定对象。

多例与枚举

定义一个描述颜色基色的多例设计类

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
java复制代码package com.demo;
class Color {
private static final Color RED = new Color("红色");
private static final Color GREEN = new Color("绿色");
private static final Color BLUE = new Color("蓝色");
private String title;
private Color(String title){
this.title=title;
}
public String toString(){
return this.title;
}
public static Color getColor(int num){
switch(num){
case 0:
return RED;
case 1:
return GREEN;
case 2:
return BLUE;
default:
return null;
}
}
}
public class ColorDemo {
public static void main(String[] args) {
Color c = Color.getColor(0);
System.out.println(c);
}
}

基于枚举开发

1
2
3
4
5
6
7
8
9
java复制代码package com.demo;
enum Color {
RED,BULE,PINK;
}
public class ColorDemo {
public static void main(String[] args) {
System.out.println(Color.RED);
}
}

Enum类

很明显,现在可以发现,利用枚举实现多例设计会更加的简单直白一些,但是在Java之中,枚举并不是一个新的类型,严格来讲,每一个使用enum定义的类实际上都属于一个类继承了Enum父类而已,而java.lang.Enum类定义如下:

1
2
3
java复制代码public abstract class Enum<E extends Enum<E>>
extends Object
implements Comparable<E>, Serializable

而在Enum类种子红定义了两个方法:

  1. 取得枚举的序号:public final int ordinal();
  2. 取得枚举的名称:public final String name()。
1
2
3
4
5
6
7
8
9
10
java复制代码package com.demo;
enum Color {
RED,BULE,PINK;
}
public class ColorDemo {
public static void main(String[] args) {
//0===RED
System.out.println(Color.RED.ordinal() + "===" + Color.RED.name());
}
}

取得所有颜色数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码package com.demo;
enum Color {
RED,BULE,PINK;
}
public class ColorDemo {
public static void main(String[] args) {
/*0===RED
1===BULE
2===PINK*/
for (Color c : Color.values()) {
System.out.println(c.ordinal() + "===" + c.name());
}

}
}

面试题:请解释enum和Enum的区别?

enum是一个关键字,使用enum定义的枚举类本质上相当于一个类继承了Enum类而已。

枚举中定义其它结构

按照之前所理解,枚举就属于多例设计模式,那么既然是多例设计模式,对于类之中就肯定有多种组成,包括属性,方法,构造方法,在枚举之中也同样可以定义以上的内容,不过需要注意的是,枚举类之中定义的构造方法绝对不能是public,必须私有化。

除了这些要求之外枚举之中每一个定义的对象必须定义在第一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码package com.demo;
enum Color {
RED("红色"),BULE("蓝色"),PINK("粉色");
private String c;
private Color(String c){
this.c = c;
}
public String toString(){
return this.c;
}
}
public class ColorDemo {
public static void main(String[] args) {
/*红色*/
System.out.println(Color.RED);
}
}

枚举接口

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.demo;
interface IColor{
public String getColor();
}
enum Color implements IColor{
RED("红色"),BULE("蓝色"),PINK("粉色");
private String c;
private Color(String c){
this.c = c;
}
public String toString(){
return this.c;
}
@Override
public String getColor() {
// TODO Auto-generated method stub
return this.c;
}
}
public class ColorDemo {
public static void main(String[] args) {
/*红色*/
System.out.println(Color.RED);
}
}

枚举应用

只有指定的几个对象

性别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码package com.demo;
class Person{
private String name;
private int age;
private Sex sex;
public Person(String name, int age, Sex sex) {
super();
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
enum Sex{
MALE("男"),FEMALE("女");
private String sex;

private Sex(String sex) {
this.sex = sex;
}
public String toString(){
return this.sex;
}
}
public class MaleDemo {
public static void main(String[] args) {
Person person = new Person("jack", 10, Sex.MALE);
System.out.println(person.toString());
}
}

枚举还可以进行switch语句进行编写和判断。

JDK三大主要特性——Annotation

在JDK1.5之后,程序允许通过注解(Annotation)的方式来进行程序的定义,而在JavaSE之中攒在了三种Annotation:@Override、@Deprecated、@SuppressWarnings。

准确的覆写:@Override

方法的覆写:发生继承关系之中,子类定义了与父类的方法名称相同、参数类型以及个数相同的覆写,被覆写的方法不能够拥有比父类更为严格的访问控制权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码package com.annotation;
class Person{
//现在是希望进行toString()覆写,但遗憾的由于你自己的输入错误,导致方法的覆写错误
//@Override
public String tostring(){//现在希望可以进行toString()方法的覆写
return "一个人";
}
}
public class Demo1 {
public static void main(String[] args) {
System.out.println(new Person().tostring());
}
}

这个时候不叫覆写,属于自己定义一个扩展的方法,最为重要的是,这个问题在程序编译的根本就无法显示出来。但是现在为了保证我们的覆写方法的严格,可以使用一个注解(@Override)来检测:如果该方法确定成的覆写了

,则不会有我们的语法错误,如果进行成功的覆写,认为语法的错误。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码package com.annotation;
class Person{
//现在是希望进行toString()覆写,但遗憾的由于你自己的输入错误,导致方法的覆写错误
public String toString(){//现在希望可以进行toString()方法的覆写
return "一个人";
}
}
public class Demo1 {
public static void main(String[] args) {
System.out.println(new Person());
}
}

声明过期操作:@Deprecated

对于程序开发而言,往往一些使用的类要进行修改或者是维护,如果说现在一个类之中的某个方法,可能一开始推出的时候正常使用,但是在后面的版本就存在了一些问题,在修改之后不希望人再去使用这些方法,那么肯定不能直接删除,因为如果直接删除了,那么之前的程序就会出现问题了,所以最好的做法是告诉用户:这个方法存在了问题,不建议再使用了,这个时候就使用“@Deprecated”声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码package com.annotation;
class Person1{
@Deprecated//表示该方法不建议使用,即使使用仍然不会报错
public Person1(){}
public Person1(String name){}
@Deprecated
public void print(){}
}
public class Demo2 {
public static void main(String[] args) {
Person1 person = new Person1();//明确标记过期
person.print();
}
}

压制警告:@SuppressWarning

程序在编译的时候如果提示警告但是不会报错只是存在了某些安全隐患,肯定会提示用户,所以不想让其显示的话,就增加压制警告信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码package com.annotation;
class Person1<T>{
@Deprecated//表示该方法不建议使用,即使使用仍然不会报错
public Person1(){}
public Person1(String name){}
@Deprecated
public void print(){}
}
public class Demo2 {
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
Person1 person = new Person1();//明确标记过期
person.print();
}
}

image-20210815220449698

关于软件的开发模式;

​ 第一阶段:会将所有的操作都写在程序之中,例如:网络程序,连接程序,连接的服务器地址,用户验证等;

​ 第二阶段:程序+配置文件,配置文件和程序相分离,配置文件过多,后期修改非常复杂;

​ 第三阶段:将配置文件写回到程序之中,但是和程序进行有效的分离。

本文转载自: 掘金

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

「Rust 重写 sqlite」理解 SQLite

发表于 2021-11-13

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


数据库是如何工作的?

为了达到这个目的,我想从头开始用Rust编写一个数据库,我将以现有的SQLite为模型。

为什么是SQLite?

因为我没有时间去写像MySQL或PostgreSQL那样的东西,也可能没有这方面的专业知识。另外,我想开发一个小的数据库,只包含在一个文件中,主要目的是提高我对它的理解,为此我们不需要像大型分布式数据库那样有那么多的功能。

另外,我认为有必要说一下,即使是SQLite本身也有很多功能!你可以通过输入 sqlite3 --help 看到。我在这里的意图,至少在一开始,并不是要提供一个具有所有可用功能的完整的替代品,主要目的还是学习以及提高我对数据库的认知。

所以:我的目标是在一个文件中建立一个简单的关系型数据库,并以SQLite为模型。

为此,我的计划是,一是慢慢来,二是一步一步来,每一篇文章我都计划增加一到两个功能,让我们看看这需要多长时间。最后送大家一句话:

如果你花足够的时间在规划上,写代码其实很容易。

作为软件开发人员,我认为我们的天性是自己动手丰衣足食,然后能快速看到我们的产出(快速看到成果对开发来说有极大的成就感,即便是阶段性成果)。从无到有最终看到它们工作是件很有趣的事。但很多时候,如果我们急于求成,我们可能不得不回头重做整个事情。

不管怎么说,我这么说是因为我确定打算在每个部分上花点大量时间,并真正深入了解SQLite是如何设计的,这样我就可以尝试复制了。如果我真的了解它是如何工作的,而且我有一个循序渐进和明确的目标,那么编码部分将是小菜一碟。

理解 SQLite

值得庆幸的是,SQLite网站上的文档相当好 here。另外,还有一本 SQLite Database System: Design and Implementation,这对理解一些概念有很大帮助。

image.png

先总体理解一下:

  1. SQLite的工作方式是将SQL文本编译成 字节码,然后使用虚拟机运行该字节码。
  2. SQLite接口作为一个编译器,将SQL文本转换为字节码。这些 字节码 是基于一个实现单个SQL语句的 sqlite3 stmt 对象。
  3. 被编译成二进制形式(字节码)的语句已经准备好被处理,并被传递给虚拟机,虚拟机反过来运行程序,直到它完成,或组装要返回的结果行,或遇到致命的错误,或被 中断。

本文转载自: 掘金

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

一个基于Web服务器的PoW案例(二)

发表于 2021-11-13

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

一个基于web服务器的PoW案例

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

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

五、区块校验

1
2
3
4
go复制代码func isHashValid(hash string, difficulty int) bool {
prefix := strings.Repeat("0", difficulty)
return strings.HasPrefix(hash, prefix)
}

这个我们本专栏之前的文章介绍了,在此简单说一下,这里我们就校验一下哈希值前面的零的数量是不是和难度值一致。

六、启动HTTP服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go复制代码func run() error {
mux := makeMuxRouter()
httpAddr := os.Getenv("PORT")
log.Println("Listening on ", httpAddr)
   
s := &http.Server{
Addr: ":" + httpAddr,
Handler: mux,
ReadTimeout:  10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}

我们先从.env文件中获取PORT的值。然后监听获取的端口号。http.Server是设置http服务器的参数,其中Addr是地址,ReadTimeout、WriteTimeout分别是读写超时时间,然后是设置请求头的数据大小的最大值,1 << 20是位运算,算出来就是1MB。!!!最重要的就是回调函数了,这里需要我们自己编写来处理Get和Post请求。

然后我们就来监听事件并且根据监听到的事件来服务。

七、回调函数的编写

1
2
3
4
5
6
7
8
go复制代码func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/",
handGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/",
handWriteBlock).Methods("POST")
return muxRouter
}

mux.NewRouter()是用来创建路由,muxRouter.HandleFunc(“/“,handGetBlockchain).Methods(“GET”)是根据你访问的目录和请求类型来调用指定的方法。这里是使用Get方法访问根目录就调用handGetBlockchain方法。同样的,muxRouter.HandleFunc(“/“,handWriteBlock).Methods(“POST”)就是使用Post请求访问根目录时就调用handWriteBlock方法。

八、处理Get请求

1
2
3
4
5
6
7
8
go复制代码func handGetBlockchain(w http.ResponseWriter, r *http.Request) {
bytes, err := json.MarshalIndent(Blockchain, "", "\t")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.WriteString(w, string(bytes))
}

我们需要将数据转换为json格式,便于与前端进行交互。

同样我们的参数分别是响应和请求。然后处理错误,当出现500错误时,也就是http.StatusInternalServerError,我们将err.Error()写入w:

image.png

如果没出错,就将json数据写入w。

九、处理POST请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
go复制代码func handWriteBlock(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json")
var message Message
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(&message); err != nil {
responseWithJSON(writer, request, http.StatusNotFound, request.Body)
}

defer request.Body.Close()

mutex.Lock()
newBlock := generateBlock(Blockchain[len(Blockchain)-1], message.BPM)
mutex.Unlock()

if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
Blockchain = append(Blockchain, newBlock)
spew.Dump(Blockchain)
}
//返回响应信息
responseWithJSON(writer, request, http.StatusCreated, newBlock)
}

因为需要服务器响应结果为json,先设置响应头的”Content-Type”为”application/json”。然后从request中读取JSON数据,将JSON数据转成Message。如果转换失败,就交给下一步处理异常,如果成功就创建新的区块。

这里使用defer,说明我们要记得关闭请求哦~

然后添加区块时要记得上锁,可以防止同个时间点多个POST请求生成区块。

接下来就要校验生成的区块是否正确,如果正确就加入区块链中。

十、处理异常

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码func responseWithJSON(writer http.ResponseWriter, request *http.Request,
code int, inter interface{}) {
   
writer.Header().Set("Content-Type", "application/json")
response, err := json.MarshalIndent(inter, "", "\t")
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
writer.Write([]byte("HTTP 500:Server Error"))
return
}
writer.WriteHeader(code)
writer.Write(response)
}

如果将传入的inter转换为json格式的数据没有出现错误就往响应头写入响应码,并将数据写入。

十一、校验区块是否正确

1
2
3
4
5
6
7
8
9
10
11
12
go复制代码func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index {
return false
}
if oldBlock.HashCode != newBlock.PreHash {
return false
}
if calculateHash(newBlock) != newBlock.HashCode {
return false
}
return true
}

这里校验了新区块的index是否等于原来最后一个区块的index加一,新区块的PreHash应该等于之前区块链最后一个区块的HashCode。然后还需要再一次计算区块的哈希值,进行比对。

十二、主逻辑

然后我们现在用Go实现通过http请求来完成区块链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
go复制代码func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
​
go func() {
t := time.Now()
genessisBlock := Block{}
genessisBlock = Block{0, t.String(),
0, calculateHash(genessisBlock),
"", difficulty, 0}
mutex.Lock()
Blockchain = append(Blockchain, genessisBlock)
mutex.Unlock()
spew.Dump(genessisBlock)
}()
​
log.Fatal(run())
}

godotenv.Load()加载一个文件,如果不填写参数,就默认是加载.env文件。

这个.env文件我们这里就只需要填写一个端口号。

image-20211112171257325

这里我们先将创世区块加入区块链。然后用spew.Dump()将其格式化输出到命令行。

最后我们会要用run来启动http服务器。

十三、运行结果

我们可以使用curl来进行get和post请求。

image-20211112212602740

这是get请求,得到区块链。

image-20211112212642066

这是进行post请求,新建一个区块加到了区块链。

image-20211112212757976

可以看到再次get请求,已经有新的区块在区块链中了。

本文转载自: 掘金

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

基于Min-Max优化查询速度和空间利用率

发表于 2021-11-13

该账户不再使用,博客转移至旧账户,请移步juejin.cn/post/704766… 查看

本文转载自: 掘金

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

一篇文章搞定java的泛型 一、泛型的定义和意义 二、泛型的

发表于 2021-11-13

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

一、泛型的定义和意义

  1. 定义

泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)

  1. 泛型的意义

  1. 适用于多种数据类型执行相同的代码(代码复用)
  2. 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
  1. 泛型的特性

泛型只在编译阶段有效

1
2
3
4
5
6
7
8
9
10
java复制代码@Test
public void test1(){
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
System.out.println(classIntegerArrayList);
System.out.println(classStringArrayList);
System.out.println(classStringArrayList.equals(classIntegerArrayList));
}

在这里插入图片描述
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出(泛型擦除/类型擦除),并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

二、泛型的使用

  1. 一些常用的泛型类型变量

E:元素(Element),多用于java集合框架
K:关键字(Key)
N:数字(Number)
T:类型(Type)
V:值(Value)

  1. 泛型的约束

  1. 不能实例化泛型类型变量
  2. 静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的
  3. 基本类型无法作为泛型类型
  4. 无法使用instanceof关键字或==判断泛型类的类型
  5. 泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
  6. 泛型数组可以声明但无法实例化
  7. 泛型类不能继承Exception或者Throwable
  8. 不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出
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
java复制代码/**
* <p>
* Description: 泛型的约束和局限性
*/
public class GenericRestrict1<T> {
static class NormalClass {

}

private T data;

/**
* 不能实例化泛型类
* Type parameter 'T' cannot be instantiated directly
*/
public void setData() {
//this.data = new T();
}

/**
* 静态变量或方法不能引用泛型类型变量
* 'com.jay.java.泛型.restrict.GenericRestrict1.this' cannot be referenced from a static context
*/
// private static T result;

// private static T getResult() {
// return result;
// }

/**
* 静态泛型方法是可以的
*/
private static <K> K getKey(K k) {
return k;
}

public static void main(String[] args) {
NormalClass normalClassA = new NormalClass();
NormalClass normalClassB = new NormalClass();
/**
* 基本类型无法作为泛型类型
*/
// GenericRestrict1<int> genericRestrictInt = new GenericRestrict1<>();
GenericRestrict1<Integer> genericRestrictInteger = new GenericRestrict1<>();
GenericRestrict1<String> genericRestrictString = new GenericRestrict1<>();
/**
* 无法使用instanceof关键字判断泛型类的类型
* Illegal generic type for instanceof
*/
// if(genericRestrictInteger instanceof GenericRestrict1<Integer>){
// return;
// }

/**
* 无法使用“==”判断两个泛型类的实例
* Operator '==' cannot be applied to this two instance
*/
// if (genericRestrictInteger == genericRestrictString) {
// return;
// }

/**
* 泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
*/
System.out.println(normalClassA == normalClassB);//false
System.out.println(genericRestrictInteger == genericRestrictInteger);//
System.out.println(genericRestrictInteger.getClass() == genericRestrictString.getClass()); //true
System.out.println(genericRestrictInteger.getClass());//com.jay.java.泛型.restrict.GenericRestrict1
System.out.println(genericRestrictString.getClass());//com.jay.java.泛型.restrict.GenericRestrict1

/**
* 泛型数组可以声明但无法实例化
* Generic array creation
*/
GenericRestrict1<String>[] genericRestrict1s;
// genericRestrict1s = new GenericRestrict1<String>[10];
genericRestrict1s = new GenericRestrict1[10];
genericRestrict1s[0]=genericRestrictString;
}

}

泛型的主要使用包括 泛型类,泛型接口,泛型方法 三种形式

  1. 泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;

public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}

public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}

注意:

  1. 泛型的类型参数只能是类类型,不能是简单类型(8种基本类型)
  2. 在使用泛型类的时候,不一定要传入泛型类型实参, 如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型
  1. 泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

1
2
3
4
java复制代码//定义一个泛型接口
public interface Generator<T> {
public T next();
}

当实现泛型接口的类,未传入泛型实参时:

1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}

当实现泛型接口的类,传入泛型实参时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
*/
public class FruitGenerator implements Generator<String> {

private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
  1. 泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}

调用泛型方法
Object obj = genericMethod(Class.forName("com.test.test"));

泛型方法举例

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
java复制代码public class GenericTest {
//这个类是个泛型类,在上面已经介绍过
public class Generic<T>{
private T key;

public Generic(T key) {
this.key = key;
}

//我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
//所以在这个方法中才可以继续使用 T 这个泛型。
public T getKey(){
return key;
}

/**
* 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
* 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
public E setKey(E key){
this.key = keu
}
*/
}

/**
* 这才是一个真正的泛型方法。
* 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
* 这个T可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public <T,K> K showKeyName(Generic<T> container){
* ...
* }
*/
public <T> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
//当然这个例子举的不太合适,只是为了说明泛型方法的特性。
T test = container.getKey();
return test;
}

//这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
public void showKeyValue1(Generic<Number> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}

//这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
//同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
public void showKeyValue2(Generic<?> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}

/**
* 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
* 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
public <T> T showKeyName(Generic<E> container){
...
}
*/

/**
* 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
* 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
* 所以这也不是一个正确的泛型方法声明。
public void showkey(T genericObj){

}
*/

public static void main(String[] args) {


}
}
  1. 泛型通配符

泛型通配符的种类:

一. <? extends T> 指定了泛型类型的上界

  1. 用于方法形参,限定只能传T以及T的子类
  2. 用于父类引用 ,主要用于安全地访问数据,可以访问 T 及其子类型,并且只能写入null

二. <? super T> 指定了泛型类型的下界

  1. 用于方法形参,限定只能传T以及T的父类
  2. 用于父类引用 ,主要用于安全地写入数据,可以写入Child及其子类型

三. <?> 指定了没有限制的泛型类型

  1. 用于方法形参,无任何限制,可以传任何类型
  2. 用于父类引用 ,只能写入null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
java复制代码/**
* <p>
* Description: 泛型通配符测试类
*/
public class GenericByWildcard {
private static void print(GenericClass<Fruit> fruitGenericClass) {
System.out.println(fruitGenericClass.getData().getColor());
}

private static void use() {
GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
print(fruitGenericClass);
GenericClass<Orange> orangeGenericClass = new GenericClass<>();
//类型不匹配,可以使用<? extends Parent> 来解决
// print(orangeGenericClass);
}

/**
* <? extends Parent> 指定了泛型类型的上界
* 用于方法形参 ,限定只能传Fruit已经fruit的子类
*/
private static void printExtends(GenericClass<? extends Fruit> genericClass) {
System.out.println(genericClass.getData().getColor());
}

public static void useExtend() {
GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
printExtends(fruitGenericClass);
GenericClass<Orange> orangeGenericClass = new GenericClass<>();
printExtends(orangeGenericClass);

GenericClass<Food> foodGenericClass = new GenericClass<>();
//Food是Fruit的父类,超过了泛型上界范围,类型不匹配
// printExtends(foodGenericClass);

//用于父类引用
//表示GenericClass的类型参数的上界是Fruit
GenericClass<? extends Fruit> extendFruitGenericClass = new GenericClass<>();
Apple apple = new Apple();
Fruit fruit = new Fruit();
/*
* 道理很简单,? extends X 表示类型的上界,类型参数是X的子类,那么可以肯定的说,
* get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。
* 但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。
* 总结:主要用于安全地访问数据,可以访问X及其子类型,并且只能写入null。
*/
// extendFruitGenericClass.setData(apple);
// extendFruitGenericClass.setData(fruit);

fruit = extendFruitGenericClass.getData();

}

/**
* <? super Child> 指定了泛型类型的下界
* 只能传apple 以及 apple的父类
*/
public static void printSuper(GenericClass<? super Apple> genericClass) {
System.out.println(genericClass.getData());
}

public static void useSuper() {
GenericClass<Food> foodGenericClass = new GenericClass<>();
printSuper(foodGenericClass);

GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
printSuper(fruitGenericClass);

GenericClass<Apple> appleGenericClass = new GenericClass<>();
printSuper(appleGenericClass);

GenericClass<HongFuShiApple> hongFuShiAppleGenericClass = new GenericClass<>();
// HongFuShiApple 是Apple的子类,达不到泛型下界,类型不匹配
// printSuper(hongFuShiAppleGenericClass);

GenericClass<Orange> orangeGenericClass = new GenericClass<>();
// Orange和Apple是兄弟关系,没有继承关系,类型不匹配
// printSuper(orangeGenericClass);

//用于父类引用
//表示GenericClass的类型参数的下界是Apple
GenericClass<? super Apple> supperAppleGenericClass = new GenericClass<>();
supperAppleGenericClass.setData(new Apple());
supperAppleGenericClass.setData(new HongFuShiApple());
/*
* ? super X 表示类型的下界,类型参数是X的超类(包括X本身),
* 那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,
* 但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。
* 编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。
* 总结:主要用于安全地写入数据,可以写入X及其子类型。
*/
// supperAppleGenericClass.setData(new Fruit());

//get方法只会返回一个Object类型的值。
Object data = supperAppleGenericClass.getData();
}

/**
* <?> 指定了没有限定的通配符
*/
public static void printNonLimit(GenericClass<?> genericClass) {
System.out.println(genericClass.getData());
}

public static void useNonLimit() {
GenericClass<Food> foodGenericClass = new GenericClass<>();
printNonLimit(foodGenericClass);
GenericClass<Fruit> fruitGenericClass = new GenericClass<>();
printNonLimit(fruitGenericClass);
GenericClass<Apple> appleGenericClass = new GenericClass<>();
printNonLimit(appleGenericClass);

GenericClass<?> genericClass = new GenericClass<>();
//setData 方法不能被调用, 甚至不能用 Object 调用;
// genericClass.setData(foodGenericClass);
// genericClass.setData(new Object());
//返回值只能赋给 Object
Object object = genericClass.getData();

}

}

三、参考链接

Java泛型详解

java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

Java总结篇系列:Java泛型

菜鸟教程

聊一聊-JAVA 泛型中的通配符 T,E,K,V,?

本文转载自: 掘金

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

MySQL官方的数据库中间件,有人用么?

发表于 2021-11-13

MySQL官方的数据库中间件,mysql-proxy,有童鞋了解么?

mysql-proxy是什么?

mysql-proxy是mysql官方提供的mysql中间件服务,上游可接入若干个mysql-client,后端可连接若干个mysql-server。

画外音:中间件有基于客户端的,也有基于服务端的,此为后者。

mysql-proxy使用什么协议?

它使用mysql协议,任何使用mysql-client的上游无需修改任何代码,即可迁移至mysql-proxy上。

mysql-proxy能怎么用?能干嘛?

mysql-proxy最基本的用法,就是作为一个请求拦截,请求中转的中间层:

)

进一步的,mysql-proxy可以分析与修改请求。拦截查询和修改结果,需要通过编写Lua脚本来完成。

mysql-proxy允许用户指定Lua脚本对请求进行拦截,对请求进行分析与修改,它还允许用户指定Lua脚本对服务器的返回结果进行修改,加入一些结果集或者去除一些结果集均可。

所以说,根本上,mysql-proxy是一个官方提供的框架,具备良好的扩展性,可以用来完成:

(1)sql拦截与修改;

(2)性能分析与监控;

(3)读写分离;

(4)请求路由;

(5)…

这个框架提供了6个hook点,能够让用户能够动态的介入到client与server中的通讯中去。

mysql-proxy的架构与原理是怎样的?

如前文所述,mysql-proxy向用户提供了6个hook点,让用户实现Lua脚本来完成各种功能,这些hook点是以函数的形式提供的,用户可以实现这些函数,在不同事件、不同操作发生时,做我们期望的事情。

connect_server()

mysql-client向proxy发起连接时,proxy会调用这个函数。用户可以实现该函数,来做一些负载均衡的事情,例如选择将要连向那个mysql-server。假设有多个mysql-server后端,而用户又没有实现这个函数,proxy默认采用轮询(round-robin)策略。

read_handshake()

mysql-server向proxy返回“初始握手信息”时,proxy会调用这个函数。用户可以实现这个函数,来做更多的权限验证工作。

read_auth()

mysql-client向proxy发送认证报文(user_name, password,database)时,proxy会调用这个函数。

read_auth_result()

mysql-server向proxy返回认证结果时,proxy会调用这个函数。

read_query()

认证完成后,mysql-client每次经过proxy向mysql-server发送query报文时,proxy会调用这个函数。用户如果要拦截请求,就可以模拟mysql-server直接返回了,当然用户亦可以实现各种策略,修改请求,路由请求等各种不同的业务逻辑。

read_query_result()

认证完成后,mysql-server每次经过proxy向mysql-client返回query结果时,proxy会调用这个函数。需要注意,如果用户没有显示实现read_query()函数,则read_query_result()函数是不会被调用的。用户可以在此处实现各种合并策略,或者对结果集进行修改。

下图是一个各hook函数的触发架构图,箭头方向表示触发时机:

)

可以发现,最重要的两个函数其实是read_query()和read_query_result(),各种sql的改写与结果集的改写逻辑,都是在这两个函数中实现的,更细节的query过程如下图:

)

mysql-proxy可以实现什么?

案例一:sql时间统计分析

假设mysql-client提交的原sql为:

XYZ;

proxy可以在read_query()里将其改写为:

SELECT NOW();

XYZ;

SELECT NOW();

这样在返回结果集时,就可以在应用层对sql时间进行记录,以方便统计分析。

案例二:sql性能统计分析

假设mysql-client提交的原sql为:

XYZ;

proxy可以在read_query()里将其改写为:

XYZ;

EXPLAIN XYZ;

这样在返回结果集时,就可以在应用层对sql性能进行记录,以方便统计分析。

需要强调的是,这两个案例,由于proxy在read_query()时对sql进行了改写,故在read_query_result()时,mysql-server其实返回了比原请求更多的信息,proxy一定要将多余的信息去掉,再返回mysql-client。多说一句,可以加入一个唯一ID,来对请求sql和返回结果进行配对。

案例三:读写分离

mysql-proxy启动时,通过参数即可配置后端mysql-server是主server还是read-only,无需修改任何代码:

1
2
3
4
5
ini复制代码shell> mysql-proxy \

--proxy-backend-addresses=10.0.1.2:3306 \

--proxy-read-only-backend-addresses=10.0.1.3:3306

注意,这里的两个mysql-server为主从架构。

案例四:性能水平扩展

mysql-proxy启动时,通过参数配置多个后端,即可实现性能的水平扩展,无需修改任何代码:

1
2
3
4
5
ini复制代码shell> mysql-proxy \

--proxy-backend-addresses=10.0.1.2:3306 \

--proxy-backend-addresses=10.0.1.3:3306

注意,这里的两个mysql-server为主主架构,如果不做特殊修改,负载均衡策略为round-robin。

mysql-proxy Q&A?

提问:Lua脚本引入的额外开销有多大?

官网回答:Lua很快,对于大部分应用来说,额外开销很小,原始包(raw packet)开销大概在400微秒左右。

画外音:这,,,我不太相信。

提问:mysql-proxy和mysql-server可以部署在一台机器上么?

官网回答:proxy单独部署也可以,和mysql部署在同一台机器上也可以。相比mysql而言,proxy不怎么占CPU和内存,其性能损耗可以忽略不计。

画外音:这,,,性能损耗可以忽略,我也不太信。

提问:proxy可以处理SSL连接么?proxy不会获取和保存我的明文密码吧?

官网回答:作为中间人,不能处理加密信息。不会获取密码,也获取不到。mysql协议不允许密码以明文传输,传输的都是加密后的密文。

提问:在Lua脚本里可以使用LuaSocket,连缓存,连其他服务么?

官网回答:理论上可以。但是,大哥,你确定要这样做么,强烈不建议这样。

本文转载自: 掘金

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

1…355356357…956

开发者博客

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