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

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


  • 首页

  • 归档

  • 搜索

k8s series 23 calico初级(数据路径)

发表于 2021-11-12

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

数据包

calico的数据路径在kubenetes中指的是一个主机上的POD数据包如何到达另一台主机的上POD

数据路径流程图来自官网

image.png

当calico部署成功后,它会在主机上创建多条路由,如下

image.png

IPIP网络模式

因为我们使用的是默认的网络模式IPIP,,另外还有一种支持大型网络BGP模式,以及刚出不久的虚拟子网VXLAN

IPIP模式是基于标准的linux dataplance(基于iptables)的规则实现的

以下截图只显示部分iptables规则,随着节点的增加和pod的增加,规则是越来越多
image.png

路由表

以下路由表显示192.168.166.192为当前calico在本机创建的网卡ip

192.168.109.64和192.168.219.0为另外2个节点42,43的calico tunl0 ip

本节点还有一个pod,它的ip是192.168.166.193,可以通过网络接口cali7511027af72来直接访问

1
2
3
4
5
6
7
8
9
10
11
js复制代码#为了不暴露ip,使用x代替
[root@k8s1 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 x.x.1.1 0.0.0.0 UG 100 0 0 enp0s3
10.10.1.0 0.0.0.0 255.255.255.0 U 100 0 0 enp0s3
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.109.64 x.x.1.42 255.255.255.192 UG 0 0 0 tunl0
192.168.166.192 0.0.0.0 255.255.255.192 U 0 0 0 *
192.168.166.193 0.0.0.0 255.255.255.255 UH 0 0 0 cali7511027af72
192.168.219.0 x.x.1.43 255.255.255.192 UG 0 0 0 tunl0

iptables规则

通过过滤cali7511027af72可以查到,已经在当前iptables规则里面,其中fw(来自pod)和tw(去pod)的规则都是允许任意全部

1
2
3
4
5
js复制代码[root@k8s1 ~]# iptables -L | grep cali7511027af72
cali-fw-cali7511027af72 all -- anywhere anywhere [goto] /* cali:QIxsGh4h35ITP8qT */
Chain cali-fw-cali7511027af72 (1 references)
cali-tw-cali7511027af72 all -- anywhere anywhere [goto] /* cali:GeGRNugqesjK7L0e */
Chain cali-tw-cali7511027af72 (1 references)

小结

因此calico的数据包流转是基于已经建好的网卡,路由表,以及iptables规则来实现的

跨node访问流程是: Pod-IP->本机IP->目标主机IP->iptables规则->POD-IP

同node之间流程是: Pod1->本机iptables规则->Pod2

本文转载自: 掘金

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

leetcode 1275 Find Winner on

发表于 2021-11-12

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

描述

Tic-tac-toe is played by two players A and B on a 3 x 3 grid. The rules of Tic-Tac-Toe are:

  • Players take turns placing characters into empty squares ‘ ‘.
  • The first player A always places ‘X’ characters, while the second player B always places ‘O’ characters.
  • ‘X’ and ‘O’ characters are always placed into empty squares, never on filled ones.
  • The game ends when there are three of the same (non-empty) character filling any row, column, or diagonal.
  • The game also ends if all squares are non-empty.
  • No more moves can be played if the game is over.

Given a 2D integer array moves where moves[i] = [rowi, coli] indicates that the ith move will be played on grid[rowi][coli]. return the winner of the game if it exists (A or B). In case the game ends in a draw return “Draw”. If there are still movements to play return “Pending”.

You can assume that moves is valid (i.e., it follows the rules of Tic-Tac-Toe), the grid is initially empty, and A will play first.

Example 1:

1
2
3
lua复制代码Input: moves = [[0,0],[2,0],[1,1],[2,1],[2,2]]
Output: "A"
Explanation: A wins, they always play first.

Example 2:

1
2
3
lua复制代码Input: moves = [[0,0],[1,1],[0,1],[0,2],[1,0],[2,0]]
Output: "B"
Explanation: B wins.

Example 3:

1
2
3
lua复制代码Input: moves = [[0,0],[1,1],[2,0],[1,0],[1,2],[2,1],[0,1],[0,2],[2,2]]
Output: "Draw"
Explanation: The game ends in a draw since there are no moves to make.

Example 4:

1
2
3
lua复制代码Input: moves = [[0,0],[1,1]]
Output: "Pending"
Explanation: The game has not finished yet.

Note:

1
2
3
4
5
ini复制代码1 <= moves.length <= 9
moves[i].length == 2
0 <= rowi, coli <= 2
There are no repeated elements on moves.
moves follow the rules of tic tac toe.

解析

根据题意,两个玩家在一个 3*3 的方格内玩名叫 Tic-tac-toe 的游戏,规则如下:

  • 玩家轮流将角色放入空方块中。
  • 第一个玩家 A 总是放置 ‘X’ 字符,而第二个玩家 B 总是放置 ‘O’ 字符。
  • ‘X’ 和 ‘O’ 字符总是被放置在空方块中
  • 当三个相同(非空)字符填充任何行、列或对角线时,游戏结束。
  • 如果所有方格都不为空,游戏也会结束。
  • 如果游戏结束,就不能再进行任何动作。

给定一个二维整数数组 moves ,其中 move[i] = [rowi,coli] 表示第 i 个操作在 grid[rowi][coli] 上进行。 如果 A 或 B 能满足三个连续字符在同行、同列或者对角线相同,则返回游戏的获胜者。 如果游戏以平局结束,则返回 Draw 。 如果仍有空间可要继续进行游戏,请返回 Pending 。

题目其实理解之后也很简单,思路如下:

  • 将 A 在方格内的所有位置都拿出来放入列表 A 中,将 B 在方格内的所有位置都拿出来放入列表 B 中
  • 因为赢的条件只有八种情况,所以都列出来放入 winCom 中
  • 遍历 winCom 中的每种赢得情况 com ,如果判断列表 A 和 com 中的元素相等则直接返回 A ,同理如果判断列表 B 和 com 中的元素相等则直接返回 B
  • 否则如果列表 A 和列表 B 的长度和为 9 说明平局了,直接返回 Draw
  • 否则直接返回 Pending

解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
lua复制代码class Solution(object):
def tictactoe(self, moves):
"""
:type moves: List[List[int]]
:rtype: str
"""
winCom = [[[0,0],[0,1],[0,2]] , [[1,0],[1,1],[1,2]], [[2,0],[2,1],[2,2]],
[[0,0],[1,0],[2,0]],[[0,1],[1,1],[2,1]],[[0,2],[1,2],[2,2]],
[[0,0],[1,1],[2,2]],[[0,2],[1,1],[2,0]]]
A = [c for c in moves[0:len(moves):2]]
B = [c for c in moves[1:len(moves):2]]
for com in winCom:
if self.isWin(A, com): return 'A'
if self.isWin(B, com): return 'B'
if len(A)+len(B)==9: return "Draw"
return "Pending"

def isWin(self, L, com):
result = 0
for c in L:
if c in com:
result += 1
return result == 3

运行结果

1
2
erlang复制代码Runtime: 24 ms, faster than 52.05% of Python online submissions for Find Winner on a Tic Tac Toe Game.
Memory Usage: 13.3 MB, less than 71.11% of Python online submissions for Find Winner on a Tic Tac Toe Game.

原题链接:leetcode.com/problems/fi…

您的支持是我最大的动力

本文转载自: 掘金

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

SSIS学习使用五:Integration Services

发表于 2021-11-12

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

翻译参考

本文主要参考翻译自 the Stairway to Integration Services 系列文章的 Deleting Rows in Incremental Loads – Level 5 of the Stairway to Integration Services,目的在于对 SSIS 有一个全面清晰的认识,所有内容在原文的基础上进行实操,由于版本差异、个人疑问等多种原因,未采用完全翻译的原则,同时也会对文中内容进行适当修改,希望最终可以更有利于学习和了解 SSIS,

感谢支持!

删除行的增量加载

Deleting Rows in Incremental Loads 增量加载中删除行。

在上一篇文章中我们学习了如何从源到目标传输更新,同时也介绍使用基于集合的更新来优化实现。

本文我们在 SSIS 中继续构建增量加载功能,重点实现当加载到目标时删除那些已经从源中移除的行。

为了完成在增量加载中删除行,基本上在随后的数据流任务中修改了很多Update操作。

模拟源表中删除行

开始设置测试条件。源表中删除行表示行在目标表中但是不在源表中。也就是,之前插入到目标中的行已经从源中移除。

然后在SSMS中执行下面的T-SQL,增加4条语句

1
2
3
4
5
6
7
8
9
sql复制代码Use AdventureWorks2012
go
Insert Into dbo.FullName
(BusinessEntityID,FirstName, MiddleName, LastName)
Values
(20778,'Andy', 'Ray', 'Leonard'),
(20779,'Candy', 'Ray', 'Leonard'),
(20780,'Beany', 'Ray', 'Leonard'),
(20781,'Andy', 'Tony', 'Leonard');

当执行增量加载删除逻辑时它应该检查源中丢失的记录并从目标中删除这些行。

此处,你可以运行现在的SSIS包,检查下在目标表新增的记录对已有的处理增量加载的逻辑有什么影响。

删除行的增量加载实现

在向 SSIS 包添加组件之前,首先重命名下 “数据流任务data flow task” 为 “Insert and Update Rows”。

添加删除的数据流任务

拖拽另一个 数据流任务(Data Flow task) 到 控制流,并从 “Apply Staged Updates”执行SQL任务 中添加一个 “优先约束”(Precedence Constraint) 到这个新数据流任务。重命名数据流任务为 “Delete Rows”。

添加OLEDB源

打开 数据流”Delete Rows” 的数据流任务,添加一个 OLE DB源 并打开编辑器,配置如下属性:

  • OLE DB连接管理器: [Server-Name].AdventureWorks2012.sa
  • 数据访问模式: 表或视图(Table or view)
  • 表或视图名: dbo.FullName

添加查找转换

当前数据流任务中使用 目标表FullName 作为源,下一步是使用 查找转换(Lookup Transformation) 删除该源中丢失的行。这个处理过程和上一个数据流任务是一样的。

拖拽一个 查找转换 到 “Delete Rows”数据流任务。连接从 “OLE DB源”适配器 到 “查找” 的数据流路径。

打开查找转换编辑器,在”常规”页中,修改 “指定如何处理无匹配项的行”(Specify how to handle rows with no matching entries) 下拉列表为 “将行重定向到无匹配输出”(“Redirect rows to no match output”),缓存模式可以根据实际设置。

在”连接”页,确保 “OLE DB连接管理器” 下拉列表设置为”[Server-Name].AdventureWorks2012.sa”。选择 “使用SQL查询结果”(Use results of an SQL query) 选项并在 textbox 中输入如下T-SQL语句

1
2
sql复制代码Select BusinessEntityID As BID
From Person.Person

为了检测出在源表中已经被去除,而在目标表中存在的行,只需要指定 “BusinessEntityID” 字段即可。在”列”页面中,从 可用输入列 格子中拖拽 “BusinessEntityID”列 到 可用查找列 格子:

因为我们配置查找重定向不匹配的行到无匹配输出(“常规页”中),这样,如果在 源(dbo.FullName) 和 查找(Person.Person) 之间没有匹配,则行将会被发送到”无匹配输出”。点击确定按钮,关闭编辑器。

添加OLE DB目标

如何实现实际的删除操作?如同之前的练习一样,在原来的更新逻辑中,我们通过添加和配置一个 OLE DB命令转换(OLE DB命令转换) 来实现。此处也可以这样操作。但是你应该已经知道了最后的结果。因此我们跳过中间的这步(可以尝试自己实现)。添加一个 OLE DB目标 到 “Delete Rows”数据流任务。然后连接来自查找转换的无匹配输出。

重命名目标为 “StageDeletes” 并双击打开编辑器。选择 “OLE DB连接管理器”。点击 “表或视图的名称”(Name of the table or view) 下拉列表旁边的 新建按钮(New button),修改显示的DDL语句如下:

1
2
3
4
5
6
sql复制代码CREATE TABLE [StageDeletes] (
[BusinessEntityID] int PRIMARY KEY,
[FirstName] nvarchar(50) NOT NULL,
[MiddleName] nvarchar(50),
[LastName] nvarchar(50) NOT NULL
)

点击确定,将会创建 StageDeletes 表。然后点击 “映射” 页完成 OLE DB目标 的(自动映射,auto-mapped)配置

点击OK按钮,关闭 OLE DB目标 编辑器。

基于集合的删除

和之前的更新一样,我们需要应用”基于集合的删除”,使用类似基于集合和相关的语句实现。

在控制流中,添加一个 执行SQL任务(Execute SQL Task) 并连接来自 “Delete Rows”数据流任务 的绿色 “优先约束”。

打开 Execute SQL Task Editor,修改下面的属性:

  • Name: Apply Staged Deletes
  • Connection: [Server-Name].AdventureWorks2012.sa
  • SQLStatement:
1
2
3
4
sql复制代码Delete src
From dbo.FullName src
Join StageDeletes stage
On stage.BusinessEntityID = src.BusinessEntityID

点击”确定”,关闭”执行SQL任务编辑器”(Execute SQL Task Editor)。

单元测试(unit-test)

在继续之前,让我们对该配置进行单元测试。

如何单元测试SSIS任务?右键点击 “Apply Staged Deletes”执行SQL任务,点击 “执行任务”(Execute Task)

如果我们配置正确将会正确执行。

管理StageDeletes表

停止SSIS的调试,如同基于集合的更新逻辑,我们需要管理 “StageDeletes” 表。

和之前一样,我们在每次加载之前 截断(truncate) 它、将其加载到数据流任务、在加载过程的中间保留表中的记录以防我们需要查看。

不需要为此创建单独的 执行SQL任务(Execute SQL Task)。我们已经有一个截断的执行SQL任务 —— “Truncate StageUpdates”,双击它打开编辑器,在已有SQL语句上添加下面的T-SQL语句:

1
sql复制代码Truncate Table StageDeletes;

关闭 输入SQL查询 窗口。然后修改下 Name 属性为 “Truncate StageUpdates and StageDeletes”,点击”确定”,关闭编辑器。

执行删除数据的增量更新

按”F5”(或”启动调试”按钮)执行 SSIS 包。检查执行的 控制流 和 “Delete Rows” 数据流任务

多出的4行已经从 目标表(dbo.FullName) 中删除。可以在SSMS中,通过下面的SQL语句验证:

1
2
3
4
sql复制代码 select * from  dbo.FullName f
left join Person.Person p
on p.BusinessEntityID=f.BusinessEntityID
where p.BusinessEntityID IS NULL;

【此处也可以直接使用 where 条件判断是否已经删除】

返回结果数为0。

总结

我们已经构建了第一个 SSIS设计模式(Design Pattern) —— 增量加载。同时学习了一些 ETL测试 入门示例,了解了 查找转换 和 执行SQL任务。

我们设计和构建了一个可重新执行的SSIS包,并结合了”增量加载”。可以每月执行一次,加载上个月更改的记录;也可以很方便的每五分钟执行一次,获取期间的数据变更。

非常灵活!

本文转载自: 掘金

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

Redis(九)list列表类型

发表于 2021-11-12

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

列表的功能十分独特,他可以在一个键下面存储N个可以重复的元素。其实就是把string类型右边的值换成了多个元素组成的列表。

一:字符串结构:

由于列表的可重复性,下面的结构实例中,第一个元素和最后一个元素可以重复。

1
2
3
4
5
6
7
bash复制代码[
"user":[
"camellia1",
"camellia2",
"camellia3"
]
]

二:hash散列类型相关操作命令(PHP+linux)

1:Linux命令使用

(1):rpush向列表添加值之后,会返回当前的长度(包含元素个数)。(这里是右侧添加,左侧添加同理)

1
2
3
bash复制代码rpush list-key camellia1                     // 返回(integer) 1
rpush list-key camellia2 // 返回(integer) 2
rpush list-key camellia3 // 返回(integer) 3

(2):lrange 获取一定范围内的值,成功返回 范围内所有元素。这里的0代表起始位置,-1代表结束位置。从0到-1,代表取出所有的值。

1
bash复制代码lrange list-key 0 -1

输出:

1
2
3
bash复制代码1) "camellia1"
2) "camellia2"
3) "camellia3"

(3):lindex获取列表里面的 某个元素。

1
bash复制代码lindex list-key 1                   // 返回"camellia2"

(4):lpop列表左侧删除一个元素,成功返回 被删除的元素

1
bash复制代码hdel hash-key camellia1             // 返回(integer) 1

2:PHP使用:

(1):添加,成功返回自增主键(左右添加一致)

1
2
3
4
5
bash复制代码$r = $redis->rpush("list-key","camellia1"); // 从列表右侧添加一个值item
var_dump($r);// 此时,列表中有一个元素,返回int 1

$res = $redis->rpush("list-key","camellia2"); // 含有2个元素,返回int 2
var_dump($res);

(2):我们获取全部的值

1
2
php复制代码$result = $redis->lrange("list-key", 0 ,-1);
var_dump($result); // 输出:array(2) { [0]=> string(9) "camellia1" [1]=> string(9) "camellia2" }

(3):获取键对应的值。

1
2
php复制代码$result = $redis->lindex("list-key",1);
var_dump($result); // 返回camellia2

(4):lpop从左侧删除一个元素,或者使用rpop从右侧删除一个元素,成功返回 被删除元素的值

r=r = r=redis->lpop(“list-key”);
var_dump($r); // 返回被删除的值camellia1

三:基于redis列表list类型的简单队列实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
php复制代码<?php
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
// 进队列
// 随机数一个id,模仿用户id
$userId = mt_rand(000000, 999999);
$redis->rpush("QUEUE_NAME",json_encode(["user_id" => $userId]));
// 随机数一个id,模仿用户id
$userId = mt_rand(000000, 999999);
$redis->rpush("QUEUE_NAME",json_encode(["user_id" => $userId]));
// 随机数一个id,模仿用户id
$userId = mt_rand(000000, 999999);
$redis->rpush("QUEUE_NAME",json_encode(["user_id" => $userId]));
echo "数据进队列成功
";
echo "<br>";
// 查看队列
$res = $redis->lrange("QUEUE_NAME", 0, 1000);
echo "当前队列数据为:
";
print_r($res);
echo "-----------------------------
";
echo "<br>";
// 出队列
$redis->lpop("QUEUE_NAME");
echo "数据出队列成功
";
echo "<br>";
// 查看队列
$res = $redis->lrange("QUEUE_NAME", 0, 1000);
echo "当前队列数据为:
";
print_r($res);
?>

四:其他常用redis List命令

序号

命令及描述

1
2
bash复制代码BLPOP   key1 [key2 ] timeout 
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
1
2
bash复制代码BRPOP   key1 [key2 ] timeout 
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
1
2
bash复制代码BRPOPLPUSH   source destination timeout 
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
1
2
bash复制代码LINDEX   key index 
通过索引获取列表中的元素
1
2
bash复制代码LINSERT   key BEFORE|AFTER pivot value 
在列表的元素前或者后插入元素
1
2
bash复制代码LLEN   key 
获取列表长度
1
2
bash复制代码LPOP   key 
移出并获取列表的第一个元素
1
2
bash复制代码LPUSH   key value1 [value2] 
将一个或多个值插入到列表头部
1
2
bash复制代码LPUSHX   key value 
将一个值插入到已存在的列表头部
1
2
bash复制代码LRANGE   key start stop 
获取列表指定范围内的元素
1
2
bash复制代码LREM   key count value 
移除列表元素
1
2
bash复制代码LSET   key index value 
通过索引设置列表元素的值
1
2
bash复制代码LTRIM   key start stop 
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
1
2
bash复制代码RPOP   key 
移除列表的最后一个元素,返回值为移除的元素。
1
2
bash复制代码RPOPLPUSH   source destination 
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
1
2
bash复制代码RPUSH   key value1 [value2] 
在列表中添加一个或多个值
1
2
bash复制代码RPUSHX   key value 
为已存在的列表添加值

以上基本上是我看的redis List列表类型的基本内容,有不足的地方,还请大佬指出。

有好的建议,请在下方输入你的评论。

欢迎访问个人博客
guanchao.site

欢迎访问小程序:

在这里插入图片描述

本文转载自: 掘金

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

短链接平台搭建详细分析及核心代码实现 短链接 实现思路 唯一

发表于 2021-11-12

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

作者:Mintimate

博客:www.mintimate.cn

Mintimate’s Blog,只为与你分享

短链接

什么是短链接

短链接,又称缩略网址服务、缩址、短址、短网址、缩略网址、网址缩短、缩短网址、URL缩短等,指的是一种互联网上的技术与服务。

此服务可以提供短URL以代替原来可能较长的URL,将长的URL地址缩短。

用户访问缩短后的URL时,通常将会重定向到原来的URL。

为什么用短链接

使用短链接,主要的场景有:

  • Twitter、微博等平台,消息字数限制,使用短链接对原有链接缩短。
  • 隐藏Get、PATH参数。
  • ……

实例演示

有些小伙伴可能还是没有概念,这里举个腾讯云自带的短链接。比如:腾讯云活动的链接是:

1
js复制代码https://cloud.tencent.com/act/cps/redirect?redirect=1077&cps_key=&from=console

而腾讯云对外给的短链接:

1
js复制代码https://curl.qcloud.com/XnaFxKqr

可以看到,链接有效地缩短了。同时,已经看不到PATH和Get参数。用户访问短链接,会自动301/302跳转到原链接:
腾讯重定向

实现思路

其实实现的思路很简单,我们生成一个短链接,大概的思路是传入原链接,在后台进行处理后,得到一个唯一识别码,一同存入数据库,最后再把这个唯一识别码回显给用户。
生成短链接

得到短链接后,用户发给其他用户进行访问时,后台根据这个识别码,再进行数据库查询(完善的系统内,还会有Redis进行缓存),最后重定向到原链接即可:

解析短链接

所以,其实实现很简单,要点:

  • 生成唯一识别码,对应链接,且识别码要短。
  • 后台301/302重定向跳转。

如何实现上述两个要点呢?

本文以Java(Springboot)为例,其他编程语言可以按图索骥。

唯一识别码

每次后台接收前台的响应,则生成一个识别码存储到数据库,已备后续调取重定向。

这个识别码最好与时间戳有关,同时,如果有多个服务器同时组网,这个识别码最好还要加上机械识别码。

相信很多人已经知道我要用什么了……

综上,我们可以使用雪花ID;同时,雪花ID为一个Long类型,转换为int类型有19位,肯定是太长了,但是肯定不会重复。

雪花ID

雪花算法(Snowflake)是一种生成分布式全局唯一ID的算法,生成的ID称为Snowflake IDs或snowflakes。

这种算法由Twitter创建,并用于推文的ID。Discord和Instagram等其他公司采用了修改后的版本。
一个雪花ID:

  • 前41位是时间戳:这意味着,雪花ID是可以排序的。
  • 之后10位代表计算机ID:便于分布式存储。
  • 其余12位代表每台机器上生成ID的序列号:便于分布式存储和集群。

雪花ID组成

Java版本参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
java复制代码/**
* Twitter的SnowFlake算法,使用SnowFlake算法生成一个整数,然后转化为62进制变成一个短地址URL
*
* https://github.com/beyondfengyu/SnowFlake
*/
public class SnowFlakeShortUrl {

/**
* 起始的时间戳
*/
private final static long START_TIMESTAMP = 1480166465631L;

/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATA_CENTER_BIT = 5; //数据中心占用的位数

/**
* 每一部分的最大值
*/
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);

/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

private long dataCenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastTimeStamp = -1L; //上一次时间戳

private long getNextMill() {
long mill = getNewTimeStamp();
while (mill <= lastTimeStamp) {
mill = getNewTimeStamp();
}
return mill;
}

private long getNewTimeStamp() {
return System.currentTimeMillis();
}

/**
* 根据指定的数据中心ID和机器标志ID生成指定的序列号
*
* @param dataCenterId 数据中心ID
* @param machineId 机器标志ID
*/
public SnowFlakeShortUrl(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}

/**
* 产生下一个ID
*
* @return
*/
public synchronized long nextId() {
long currTimeStamp = getNewTimeStamp();
if (currTimeStamp < lastTimeStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}

if (currTimeStamp == lastTimeStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currTimeStamp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}

lastTimeStamp = currTimeStamp;

return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
| dataCenterId << DATA_CENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}

public static void main(String[] args) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);

for (int i = 0; i < (1 << 4); i++) {
//10进制
System.out.println(snowFlake.nextId());
}
}
}

当然,如果你用使用Mybatis Plus,可以引用Mybatis Plus的IdWorker.getId方法,生成雪花ID。

生成后的Long类型,我们使用十进制展开,应该是一个17-19位的数字。

六十二进制

因为雪花ID通过十进制展开是一个17-19位的数字,如果直接用来当作短链接,太长了点,我们需要对其缩短。

为了保证唯一,且可对照。我们转换为六十二进制。

原因很简单:六十二进制使用A-Z、a-z和0-9组成。
把十进制,转换为六十二进制,能有效减短长度。

根据Wiki-Base62规定,六十二进制中0-61对应0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz。所以,我们编写编码和解码:

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
java复制代码/**
* 初始化 62 进制数据,索引位置代表转换字符的数值 0-61,比如 A代表1,z代表61
*/
private static String CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

/**
* 进制转换比率
*/
private static int SCALE = 62;

/**
* 匹配字符串只包含数字和大小写字母
*/
private static String REGEX = "^[0-9a-zA-Z]+$";

/**
* 十进制数字转为62进制字符串
*
* @param val 十进制数字
* @return 62进制字符串
*/
public static String encode10To62(long val)
{
if (val < 0)
{
throw new IllegalArgumentException("this is an Invalid parameter:" + val);
}
StringBuilder sb = new StringBuilder();
int remainder;
while (Math.abs(val) > SCALE - 1)
{
//从最后一位开始进制转换,取转换后的值,最后反转字符串
remainder = Long.valueOf(val % SCALE).intValue();
sb.append(CHARS.charAt(remainder));
val = val / SCALE;
}
//获取最高位
sb.append(CHARS.charAt(Long.valueOf(val).intValue()));
return sb.reverse().toString();
}

/**
* 十进制数字转为62进制字符串
*
* @param val 62进制字符串
* @return 十进制数字
*/
public static long decode62To10(String val)
{
if (val == null)
{
throw new NumberFormatException("null");
}
if (!val.matches(REGEX))
{
throw new IllegalArgumentException("this is an Invalid parameter:" + val);
}
String tmp = val.replace("^0*", "");

long result = 0;
int index = 0;
int length = tmp.length();
for (int i = 0; i < length; i++)
{
index = CHARS.indexOf(tmp.charAt(i));
result += (long)(index * Math.pow(SCALE, length - i - 1));
}
return result;
}

再测试一下:

1
2
3
4
5
6
7
8
9
java复制代码//Test
public static void main(String[] args) {
Long snow = IdWorker.getId();
System.out.println(snow);
String str = encode10To62(snow);
System.out.println(str);
Long g = decode62To10(str);
System.out.println(g);
}

输出:

1
2
3
js复制代码1425664925648310274
1hJYkVByV0M
1425664925648310274

可以看到,这样生成一个短、不可重复的字符串了。这样用于短链接生成,挺合适的:

  • 每个短链接程度基本固定。
  • 短链接长度不至于过长。
  • 生成的短链接可排序(时间排序)

响应头

重定向链接,响应头很重要。Nginx内可以使用配置直接跳转301/302,比如强制HTTPS:

1
2
3
nginx复制代码if ($server_port !~ 443){
rewrite ^(/.*)$ https://$host$1 permanent;
}

而我们搭建短链接平台,也利用301或者302进行重定向:
雪花ID组成

301/302

301和302都是重定向,那它们的区别是什么呢?

  • 301:永久重定向,在请求的URL已被移除时使用,响应的location首部中应包含资源现在所处的URL
  • 302:临时重定向,和永久重定向类似,客户端应用location给出URL临时定位资源,将来的请求仍为原来的URL。

实际场景里,301在跳转后,浏览器会记住这个跳转,后续请求,不再请求原地址,而是直接请求新地址;所以301一般用于网站域名的迁移,强制网站https等,而302一般是网站维护,需要临时跳转到非维护页面等情况。

那我们搭建短链接平台,需要什么重定向呢?我认为是都可以。使用301重定向,可以减少服务器负载,而使用302重定向,可以方便我们统计链接实际调取次数。

Java内,进行301/302的跳转,其实很简单,使用类RedirectView,其中的HttpStatus即可:

1
2
3
4
5
6
java复制代码# RedirectView类
RedirectView redirectView = new RedirectView(fullURL);
# 301跳转
redirectView.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
# 302跳转
redirectView.setStatusCode(HttpStatus.MOVED_TEMPORARILY);

实际上,看HttpStatus的源码,可以看到这里枚举了很多HTTP的响应头:
HttpStatus枚举

Maven部署(代码实现)

最后,我们看看实际部署和代码实现。只是随便提供思路,代码可能有逻辑不严谨地方嗷。实际开发,应该还需要Redis缓冲,避免数据库过载。

本次使用MariaDB作为数据库,使用Mybatis Plus对数据库进行操作,Springboot提供框架并方便打包。

再次强调:实际开发,应该还需要Redis缓冲,避免数据库过载。

依赖包

首先,我们创建一个工程,其中Lombok是为了方便实体类生成Set/Get方法:

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
xml复制代码<dependencies>
<!-- Springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MariaDB驱动-->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!-- lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

实体类

我们看看短链接实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码@Data
@NoArgsConstructor
public class ShortUrl {
@TableId
private Long id;
private String baseUrl;
private String suffixUrl;
private String fullUrl;
private String shortCode;
@TableField(fill = FieldFill.INSERT)
private Integer totalClickCount;
@TableField(fill = FieldFill.INSERT)
private Date expirationDate;

public ShortUrl(String baseUrl, String suffixUrl, String fullUrl) {
this.baseUrl = baseUrl;
this.suffixUrl = suffixUrl;
this.fullUrl = fullUrl;
}
}

其中:

  • baseUrl:用户提供的原链接域名,如:tool.mintimate.cn。
  • suffixUrl:用户提供链接的参数,如:/user?login=yes。
  • fullUrl:用户提供的原链接,如:https://tool.mintimate.cn/user?login=yes。
  • shortCode:生成的短链接。
  • totalClickCount:统计点击次数(Hander自动设置默认值)
  • expirationDate:失效时间(Hander自动设置默认值)

短链接处理

首先,做一个控制器,用来接收用户请求:

1
2
3
4
5
6
7
8
9
10
java复制代码// 接收原链接,返回短链接
@ResponseBody
@PostMapping(value = "/add")
public ShortUrl encodeURL(@RequestParam(value = "UserURL") String UserURL){
String Domain = DomainUntil.getDomain(UserURL);
if (Domain==null){
return null;
}
return shortUrlService.saveURL(UserURL);
}

之后,看看业务层,我们需要对域名进行加工,先得到一个雪花ID,再对其转至六十二进制,并回显:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@Resource
ShortUrlMapper shortUrlMapper;
@Override
public ShortUrl saveURL(String UserURL) {
// 创建新对象
ShortUrl shortUrl=new ShortUrl(DomainUntil.getTopDomain(UserURL),DomainUntil.getFile(UserURL),UserURL);
// 使用Mybatis Plus,提前拿到对象的雪花ID
Long ID= IdWorker.getId(shortUrl);
// 转六十二进制
String Short_URL_CODE = DecimalAndSixtyBinary.encode10To62(ID);
shortUrl.setShortCode(Short_URL_CODE);
int code=shortUrlMapper.insert(shortUrl);
return shortUrl;
}

这个时候,我们使用Postman来测试一下:
测试成功
可以看到,测试成功。

短链接重定向

短链接重定向,就很简单了。我们写一个请求即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@ResponseBody
@RequestMapping(value = "/{UrlID}")
public RedirectView reverseURL(@PathVariable(value = "UrlID") String UrlID) {
// 接收请求参数为String
String fullURL=shortUrlService.findURL(UrlID);
if (fullURL==null){
// 定义的404页面
RedirectView redirectView = new RedirectView("/error/404");
redirectView.setStatusCode(HttpStatus.NOT_FOUND);
return redirectView;
}
else {
RedirectView redirectView = new RedirectView(fullURL);
redirectView.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
return redirectView;
}
}

其中,shortUrlService的 findURL就是简单的JDBC查询,不具体实现。

Demo

我根据上述思路,初略搭建一个Demo,并部署在个人服务器:

  • 前端:基于Vue,使用element ui和Bootstrap
  • 后端:Springboot

在线演示
我们可以在Linux/macOS上使用curl测试一下,比如直接用腾讯云轻量应用服务器的Linux远程终端:

1
arduino复制代码curl -I "https://curl.mintimate.ml/1Hjsg8wDe8i"

在线演示

完善思路

可以看到,文章实现的有些粗糙,提供以下完善思路:

  • 限制单IP一段时间的请求频率:目前我是使用前端Vue进行控制,但是最好后端也进行控制。
  • 数据库优化:目前使用的是MariaDB,如果要更好的体验,或者响应数据量大,使用Redis进行缓冲会更好。
  • Cron定时任务:使用雪花ID转六十二进制,在链接长度上,还是有点长,但是安全性应该是很高的;如果降低安全性,并进一步缩短长度,可以创建Cron定时线程,无效旧的短链接。

本文转载自: 掘金

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

MySQL之order by使用

发表于 2021-11-12

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

1、简介

在使用select语句时可以结合order by对查询的数据进行排序。如果不使用order by默认情况下MySQL返回的数据集,与它在底层表中的顺序相同,可能与你添加数据到表中的顺序一致,也可能不一致(在你对表进行修改、删除等操作时MySQL会对内存进行整理,此时数据的顺序会发生改变)因此如果我们希望得到的数据是有顺序的,就应该明确排序方式。

2、正文

首先准备一张User表,DDL和表数据如下所示,可以直接复制使用。

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
sql复制代码SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
`age` int(11) NOT NULL COMMENT '年龄',
`sex` smallint(6) NOT NULL COMMENT '性别',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '李子捌', 18, 1);
INSERT INTO `user` VALUES (2, '张三', 22, 1);
INSERT INTO `user` VALUES (3, '李四', 38, 1);
INSERT INTO `user` VALUES (4, '王五', 25, 1);
INSERT INTO `user` VALUES (5, '六麻子', 13, 0);
INSERT INTO `user` VALUES (6, '田七', 37, 1);
INSERT INTO `user` VALUES (7, '谢礼', 18, 1);

SET FOREIGN_KEY_CHECKS = 1;

数据的初始顺序如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
sql复制代码mysql> select * from user;
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 1 | 李子捌 | 18 | 1 |
| 2 | 张三 | 22 | 1 |
| 3 | 李四 | 38 | 1 |
| 4 | 王五 | 25 | 1 |
| 5 | 六麻子 | 13 | 0 |
| 6 | 田七 | 37 | 1 |
| 7 | 谢礼 | 18 | 1 |
+----+--------+-----+-----+
7 rows in set (0.00 sec)

2.1 单个列排序

我们首先来看使用order by对单个列进行排序。

需求:

根据用户年龄进行升序排序。


语句:

1
sql复制代码select * from user order by age;

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
sql复制代码mysql> select * from user order by age;
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 5 | 六麻子 | 13 | 0 |
| 1 | 李子捌 | 18 | 1 |
| 7 | 谢礼 | 18 | 1 |
| 2 | 张三 | 22 | 1 |
| 4 | 王五 | 25 | 1 |
| 6 | 田七 | 37 | 1 |
| 3 | 李四 | 38 | 1 |
+----+--------+-----+-----+
7 rows in set (0.00 sec)

分析:

可以看到user表输出顺序按照age升序排序输出,因此可以猜测MySQL默认的排序方式是升序。此外我这里使用*通配符查询所有列,并未明确指明查询age列;其实MySQL的order by后跟的列不一定要查询出来,比如 select name from user order by age;这样也是一样的效果。

2.2 多个列排序

order by不仅可以对单个列进行排序,它也可以对多个列进行排序,只需要把需要排序的列依次跟在order by之后就可以了。

在测试之前,我们先往表中添加一条年龄相等的数据

1
2
sql复制代码mysql> insert into user (name, age, sex) values ('李子柒', 18, 1);
Query OK, 1 row affected (0.01 sec)

需求:

根据用户年龄升序排序之后再更加用户名称排序。

语句:

1
sql复制代码select * from user order by age, name;

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码mysql> select * from user order by age, name;
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 5 | 六麻子 | 13 | 0 |
| 1 | 李子捌 | 18 | 1 |
| 8 | 李子柒 | 18 | 1 |
| 7 | 谢礼 | 18 | 1 |
| 2 | 张三 | 22 | 1 |
| 4 | 王五 | 25 | 1 |
| 6 | 田七 | 37 | 1 |
| 3 | 李四 | 38 | 1 |
+----+--------+-----+-----+
8 rows in set (0.00 sec)

分析:

order by可以作用于多个列,MySQL会完全根据order by后列的顺序进行排序。不过MySQL对于中文的排序需要考虑数据库字符集编码的问题,如果不是很懂建议不要对中文进行排序,因为大多数情况我们需要得到的是拼音的排序结果,但是往往不尽人意哦!此外这里我们在age和name后都是没有跟排序方式的,所以默认都是升序,先根据age升序排序之后再根据name升序排序。如果需要使用降序,则需要指明DESC,比如 select id, name, age from user order by age, name desc;。

2.3 排序的方式

order by有两种排序方式,它们分别是:

  1. ASC -> 升序排序(默认排序方式)
  2. DESC -> 降序排序

注意: 在上面说了order by对多个列进行排序,排序方式只会作用于一个列,比如你需要对user表中的数据同时按照age和name进行降序,就应该两个列都指明降序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码mysql> select * from user order by age desc, name desc;
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 3 | 李四 | 38 | 1 |
| 6 | 田七 | 37 | 1 |
| 4 | 王五 | 25 | 1 |
| 2 | 张三 | 22 | 1 |
| 7 | 谢礼 | 18 | 1 |
| 8 | 李子柒 | 18 | 1 |
| 1 | 李子捌 | 18 | 1 |
| 5 | 六麻子 | 13 | 0 |
+----+--------+-----+-----+
8 rows in set (0.00 sec)

如果你只指定age列降序排序,name列不指定,那么MySQL会根据age降序排序后,再根据name列进行默认升序排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sql复制代码mysql> select * from user order by age desc, name;
+----+--------+-----+-----+
| id | name | age | sex |
+----+--------+-----+-----+
| 3 | 李四 | 38 | 1 |
| 6 | 田七 | 37 | 1 |
| 4 | 王五 | 25 | 1 |
| 2 | 张三 | 22 | 1 |
| 1 | 李子捌 | 18 | 1 |
| 8 | 李子柒 | 18 | 1 |
| 7 | 谢礼 | 18 | 1 |
| 5 | 六麻子 | 13 | 0 |
+----+--------+-----+-----+
8 rows in set (0.00 sec)

可以看到李子捌、李子柒、谢礼三行数据排序方式发生了改变。

2.4 order by结合limit

order by结合limit可以获取排序后的数据行记录数量。比如我们从user表中获取年龄最大的一个用户。可以看到李四同志38岁了,比较老了,属于年龄最大的人之一。

1
2
3
4
5
6
7
sql复制代码mysql> select * from user order by age desc limit 1;
+----+------+-----+-----+
| id | name | age | sex |
+----+------+-----+-----+
| 3 | 李四 | 38 | 1 |
+----+------+-----+-----+
1 row in set (0.00 sec)

limit需要跟在order by之后,如果位置不对,MySQL会抛出异常信息。

1
2
vbnet复制代码mysql> select * from user limit 1 order by age des;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order by age des' at line 1

本文转载自: 掘金

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

三分钟掌握casewhen的使用

发表于 2021-11-12

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


  温馨提示: 本文大约1732字,阅读完大概需要2-3分钟,希望您能耐心看完,倘若你对该知识点已经比较熟悉,你可以直接通过目录跳转到你感兴趣的地方,希望阅读本文能够对您有所帮助,如果阅读过程中有什么好的建议、看法,欢迎在文章下方留言或者私信我,如果觉的文章给你带来一点帮助,可以帮忙点一下赞和关注,谢谢!!

前言

  作用: 可以使用它们在数据库进行判断功能,跟代码中的if…else功能一样.但是,它们又存在差异,下面就来讲它们的具体作用和差别。

一: 使用语法

   (一)普通case函数

1
2
3
4
5
6
java复制代码CASE  <表达式>
WHEN <值1> THEN <操作>
WHEN <值2> THEN <操作>
...
ELSE <操作>
END

   (一)搜索case函数

1
2
3
4
5
6
java复制代码CASE
WHEN <条件1> THEN <命令>
WHEN <条件2> THEN <命令>
...
ELSE commands
END

作用一: 结合分组统计数据

   需求: 将下图的数据按照”洲”进行统计总人数

国家人口

   (一)方式一: 使用普通的case函数进行统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码		select (
case name
when '中国' then '亚洲'
when '日本' then '亚洲'
when '美国' then '北美洲'
when '加拿大' then '北美洲'
else '其他' end
) 洲,
sum(population) 总数
from t_country
GROUP BY
(
case name
when '中国' then '亚洲'
when '日本' then '亚洲'
when '美国' then '北美洲'
when '加拿大' then '北美洲'
else '其他' end
)

  方式一统计结果

按洲统计结果

   (二)方式二: 使用搜索的case函数进行统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码	select (
case
when name in('中国','日本') then '亚洲'
when name in('美国','加拿大') then '北美洲'
else '其他' end
) 洲,
sum(population) 总数
from t_country
GROUP BY
(
case
when name in('中国','日本') then '亚洲'
when name in('美国','加拿大') then '北美洲'
else '其他' end
)

  方式二统计结果

搜索case的统计结果

作用二: 分条件更新字段值

   (一)需求: 将工资低于3000的员工涨幅工资20%,工资等于高于3000的员工涨幅8%,数据如下:

源数据

   可能有人看到这个需求的第一反应,想直接可以直接通过如下两条update语句直接更新:

1
2
java复制代码update t_salary set salary = salary + (salary * 0.2) where salary < 3000;
update t_salary set salary = salary + (salary * 0.08) where salary >= 3000;

  但是,如果是这样执行的话实际上会存在问题,比如:原来工资在2900的员工,执行完第一条语句后工资会变成3480,此时,再执行第二条更新语句,因为满足工资大于三千,则又会去添加多8%的工资,这样明显就是不符合我们的需求的,所以,如果想完成这个需求,又不想写太复杂的sql,可以通过case函数完成这个功能。

   (二)使用搜索的case函数进行分条件修改(此处不能使用简单case函数,因为简单case函数不能判断带范围的条件)

1
2
3
4
5
6
7
8
9
10
java复制代码update t_salary
set
salary =
(
case
when salary < 3000 then salary + salary * 0.2
when salary >= 3000 then salary + salary * 0.08
else salary
end
)

   (三)分条件修改后结果

分条件修改后结果

作用三: 检查表中字段值是否一致

   (一)需求: 判断两个表中name字段值是否一致,并返回结果,数据如下:

源数据

   (二)使用搜索的case函数进行分条件修改(此处不能使用简单case函数,因为简单case函数不能判断带范围的条件)

1
2
3
4
5
6
7
8
java复制代码select name,
(
case
when desciption in(select description from t_user2) then '一致'
else '不一致'
end
) 比较结果
from t_user1

   (三)比较结果:
字段比较结果

作用四: 行转列(重点-面试常见)

   (一)需求: 将表中数据按照每个学生姓名 、科目、成绩进行排序,数据如下:

源数据

   (二)使用case函数转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码// 使用普通case函数
SELECT NAME,
max( CASE class WHEN '语文' THEN grade ELSE 0 END ) 语文,
max( CASE class WHEN '数学' THEN grade ELSE 0 END ) 数学,
max( CASE class WHEN '英语' THEN grade ELSE 0 END ) 英语
FROM
t_source
GROUP BY
NAME

// 使用搜索case函数
SELECT NAME,
max( CASE WHEN class = '语文' THEN grade ELSE 0 END ) 语文,
max( CASE WHEN class = '数学' THEN grade ELSE 0 END ) 数学,
max( CASE WHEN class = '英语' THEN grade ELSE 0 END ) 英语
FROM
t_source
GROUP BY
NAME

   (三)转换结果

行转列

五:普通case函数和搜索case函数的区别

   通过上面的案例可看到,普通的case函数写法相对简洁,但是功能也相对简单,搜索case函数的功能更加强大,具体如下:

   1、简单case函数判断条件只能是等于,而搜索case函数的条件可以是子查询,In,大于、等于等等。

   2、如果只是使用简单的条件分组,可以选择普通case函数,如果需要判断更多的场景,则选择搜索case更好。

六:总结

   如果你想亲自实践,需要本文章的测试数据,可以私信回复: 【测试数据】即可

   看到此处,你应该对Case函数有了更深入的认识,但是、关于Case函数的使用远远不止这一篇文章描述的,还需要我们在实践中去发现更多的可能,如果你看完本文觉得有疑问或者本文有错误的地方,欢迎私信或者在下方留言指出。

   码字不易、如果你觉得本文对你有一点点帮助,可以点赞和关注!

本文转载自: 掘金

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

Flask 入门系列之路由!

发表于 2021-11-12

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

在上一篇Flask 入门系列之Hello Flask!中,我们用 Flask 框架写了一个 Hello Flask 应用程序,我们了解到 Flask 框架简洁高效、可以快速上手,接下来将对 Flask 框架的各项功能详细的介绍一下,本篇文章介绍的是 Flask 的路由(Route)。

路由

所谓路由,就是处理请求url和函数之间关系的程序,一个Web应用不同的路径会有不同的处理函数,当我们请求应用时,路由会根据请求的 url 找到对应处理函数。

视图函数绑定多个url

一个视图函数可以绑定多个 url,比如下面的代码把/hi和/hello都绑定到hello()函数上,这就会为hello()函数注册两个路由,用户访问这两个 url 均会触发该函数。

在上一篇 Hello Flask 的基础上,添加下面的函数,并运行程序。

1
2
3
4
python复制代码@app.route('/hi')
@app.route('/hello')
def hello():
return 'Hello Flask!'

动态url

Flask 支持在 url 中添加变量部分,使用<变量名>的形式表示,Flask 处理请求时会把变量传入视图函数,所以可以在试图函数内获取该变量的值。

1
2
3
python复制代码@app.route('/user/<name>')
def hello_user(name):
return 'Hello {}!'.format(name)

当我们在浏览器中访问http://127.0.0.1:5000/hello/tigeriaf地址时,将在页面上看到”Hello tigeriaf!”。url 路径中/hello/后面的参数被hello()函数的name参数接收并使用。

我们还可以在 url 参数前添加转换器来转换参数类型,比如:

1
2
3
python复制代码@app.route('/user/<int:user_id>')
def hello_user(user_id):
return 'Hello user:{}!'.format(user_id)

访问http://127.0.0.1:5000/hello/111,页面上会显示”Hello user:111!”。其中,参数类型转换器int:控制传入参数的类型只能是整形,传入其他类型将报 404 的错误,目前支持的参数类型转换器有:

  • string:字符型,但是其中不能包含斜杠”/“
  • int:整型
  • float:浮点型
  • uuid:uuid字符类型
  • path:字符型,可以包含斜杠”/“,如aa/bb/cc

除此之外,还可以设置 url 变量参数的默认值,如下,在app.route()装饰器里使用defaults参数设置,接收一个字典,来存储 url 变量参数默认值映射。

1
2
3
4
python复制代码@app.route('/user', defaults={'name': 'default_name'})
@app.route('/user/<name>')
def hello_user(name):
return 'Hello {}!'.format(name)

上述代码中,/user不带参数,访问/user时,变量name就会使用默认值”default_name”。其实,这种做法等同于在hello_user()函数内给name变量设置缺省值。

HTTP请求方法设置

HTTP 请求方法常用的有GET、POST、PUT、DELETE。Flask 路由也可以设置请求方法,在app.route()装饰器中使用使用methods参数传入一个包含监听的 HTTP 请求的可迭代对象。
比如,下面的视图函数同时监听GET请求和POST请求:

1
2
3
4
5
6
7
8
python复制代码from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return 'This is a POST request'
else:
return 'This is a GET request'

分别使用GET请求和POST请求访问http://127.0.0.1:5000/login时,会返回不同的内容,如果使用其他的请求方法(如PUT),会报 405 Method Not Allowed 的错误。

url构建

Flask提供了url_for()方法来快速获取及构建 url,方法的第一个参数是视图函数的名称,之后的一个或多个参数对应的是 url 变量部分。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码@app.route('/superuser')
def hello_superuser():
return 'Hello superuser!'


@app.route('/user/<name>')
def hello_user(name):
return 'Hello {}!'.format(name)


@app.route('/user/<name>')
def hello(name):
if name == 'superuser':
return redirect(url_for('hello_superuser'))
else:
return redirect(url_for('hello_user', name=name))

上述代码中,url_for()方法是根据试图函数名称获取url,redirect()是根据 url 重定向到视图函数,二者配合使用,用作 url 的重定向。hello(name)函数接受来自 url 的参数的值,判断值是否与superuser匹配,如果匹配,则使用redirect(url_for())将应用程序重定向到hello_superuser()函数,否则重定向到hello_user()函数。

原创不易,如果小伙伴们觉得有帮助,麻烦点个赞再走呗~

最后,感谢女朋友在工作和生活中的包容、理解与支持 !

本文转载自: 掘金

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

Redis(四):主从同步 一、Redis主从有两种结构模型

发表于 2021-11-12

前三篇详细分析了redis的特性和核心原理,从本篇开始将对redis的部署结构和运行模式进行分析解读。真正生产环境当中我们基本不会使用单节点的redis来提供服务,至少是主从结构的哨兵或者集群模式,以保障redis服务的可靠性。本篇就来详细解读下redis的主从同步机制。

一、Redis主从有两种结构模型:

image.png

1.1 主从复制

一主N从的这种复制结构复制关系只有一级,也是使用最多的形式,通常搭建哨兵或者集群结构的redis都是采用的这种复制结构,能够通过一级从节点的复制关系很好的保证服务的可用性,做到异常情况主从切换。

1.2 级联复制

级联复制结构的复制关系可以有多级,一个主节点的从节点可以是下属从节点的主节点。级联复制结构的应用相对比较少,这种结构能够在有多个从节点的结构下一定程度上缓解主节点的复制压力。

二、Redis主从关系的建立

redis的主从同步始于命令SLAVEOF host port,通过这个命令能够建立主从关系,SLAVEOF 命令用于在 Redis 运行时动态地修改复制功能的行为。通过执行 SLAVEOF host port 命令,可以将当前服务器转变为指定服务器的从属服务器(slave server)。如果当前服务器已经是某个主服务器(master server)的从属服务器,那么执行 SLAVEOF host port 将使当前服务器停止对旧主服务器的同步,丢弃旧数据集,转而开始对新主服务器进行同步。另外,对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。利用 SLAVEOF NO ONE 不会丢弃同步所得数据集这个特性,在没有搭建哨兵和集群的情况下,可以在主服务器失败的时候,将从属服务器用作新的主服务器,从而实现无间断运行。

下图为主从关系建立流程:

image.png

注意:

根据上执行流程这里有一个需要注意点,当我们对一个已有主从关系的节点执行slaveof命令时,会结束掉现有的主从关系并清空节点下的所以数据,在生成环境当中这是比较威胁的操作。有没有更安全的方式了?上面介绍slavelof命令的时候提到可以传递NO ONE参数,也就是执行SLAVEOF NO ONE命令,这个命令是只会结束主从复制关系不会清空数据的,相对安全很多。

三、数据同步

建立好主从关系后就要进入主从数据同步的过程了,这里主要分三种情况,刚建立主从关系后的数据全量同步;初始化同步完成后的命令传播阶段;主从关系异常中断重连后的同步方式选择,这里会有全量和增量同步两种场景。

3.1 全量同步

  1. 当slave节点启动后或断开重连后(重连不满足增量同步条件),会向master数据库发送SYNC命令。
  2. master节点收到SYNC命令后会开始在后台保存快照(即RDB持久化,在主从复制时,会无条件触发RDB),并将保存快照期间接收到的命令缓存起来。
  3. master节点执行RDB持久化完成后,向所有slave节点发送快照RDB文件,并在发送快照期间继续记录被执行的写命令。
  4. slave节点收到快照文件后丢弃所有旧数据(会清空所有数据),载入收到的快照。
  5. master节点快照发送完毕、slave节点载入快照完毕后,master节点开始向slave节点发送缓冲区中的写命令。
  6. slave节点完成对快照的载入,开始接收命令请求,并执行来自主数据库缓冲区的写命令。(从数据库初始化完成)
  7. master节点每执行一个写命令就会向slave节点发送相同的写命令,slave节点接收并执行收到的写命令。(命令传播操作,slave节点初始化完成后的操作)

全量同步流程如下图:

image.png

在redis2.8之前,从节点无论是初始化还是断线重连后都是采用全量同步的方式,在2.8之后版本,引入PSYNC命令,在从节点断线重连后会判断是否采用增量同步。

3.2 增量同步

PSYNC具备了数据全量重同步和增量同步模式。

  1. 全量重同步:跟旧版复制基本是一致的,可以理解为“全量”复制。
  2. 部分重同步:salve断开又重新连时,在命令传播阶段,只需要发送与master断开这段时间执行的写命给slave即可,可以理解为“增量”复制。

PSYNC执行过程中比较重要的概念有3个:runid、offset(复制偏移量)以及复制积压缓冲区。

1.runid

每个Redis服务器都会有一个表明自己身份的ID。在PSYNC中发送的这个ID是指之前连接的Master的ID,如果没保存这个ID,PSYNC的命令会使用”PSYNC ? -1” 这种形式发送给Master,表示需要全量复制。

2.offset(复制偏移量)

在主从复制的Master和Slave双方都会各自维持一个offset。Master成功发送N个字节的命令后会将Master里的offset加上N,Slave在接收到N个字节命令后同样会将Slave里的offset增加N。Master和Slave如果状态是一致的那么它的的offset也应该是一致的。

3.复制积压缓冲区

复制积压缓冲区是由Master维护的一个固定长度环形积压队列(FIFO队列),它的作用是缓存已经传播出去的命令。当Master进行命令传播时,不仅将命令发送给所有Slave,还会将命令写入到复制积压缓冲区里面。PSYNC执行过程和SYNC的区别在于:salve连接时,判断是否需要全量同步,全量同步的逻辑过程和SYNC一样。PSYNC执行步骤如下:

  1. 客户端向服务器发送SLAVEOF命令,即salve向master发起连接请求时,slave根据自己是否保存Master runid来判断是否是第一次连接。
  2. 如果是第一次同步则向Master发送 PSYNC ? -1 命令来进行完整同步;如果是重连接,会向Master发送PSYNC runid offset命令(runid是master的身份ID,offset是从节点同步命令的全局迁移量)。
  3. Master接收到PSYNC 命令后,首先判断runid是否和本机的id一致,如果一致则会再次判断offset偏移量和本机的偏移量相差有没有超过复制积压缓冲区大小,如果没有那么就给Slave发送CONTINUE,此时Slave只需要等待Master传回失去连接期间丢失的命令。如果runid和本机id不一致或者offset差距超过了复制积压缓冲区大小,那么就会返回FULLRESYNC runid offset,Slave将runid保存起来,并进行全量同步。

主节点在命令传播时,主数据库会将每一个写命令传递给从数据库的同时,都会将写命令存放到积压队列,并记录当前积压队列中存放命令的全局偏移量offset。当salve重连接时,master会根据从节点传的offset在环形积压队列中找到断开这段时间执行的命令,并同步给salve节点,达到增量同步结果。

PSYNC执行流程如下图:

image.png

从以上PSYNC的执行流程可以看出当slave节点断线重连以后判断是否采用增量同步的核心是slave的offset偏移量和master的偏移量相差有没有超过复制积压缓冲区大小,那么这个大小是由以下参数来配置的。复制积压缓冲区本质上是一个固定长度的循环队列,默认情况下积压队列的大小为1MB,可以通过配置文件设置队列大小:设置复制积压缓冲区大小,积压队列越大,允许主从数据库断线的时间就越长

1
arduino复制代码repl-backlog-size 1mb

Redis同时也提供了当没有slave需要同步的时候,多久可以释放环形队列,默认一小时,没有salve连接时,多久释放一次复制积压缓冲区

1
yaml复制代码repl-backlog-ttl 3600

四、主从复制策略

Redis采用了乐观复制的策略,也就是在一定程度内容忍主从数据库的内容不一致,但是保持主从数据库数据的最终一致性。具体来说,Redis在主从复制的过程中,本身就是异步的,在主从数据库执行完客户端请求后会立即将结果返回给客户端,并异步的将命令同步给从数据库,但是这里并不会等待从数据库完全同步之后,再返回客户端。这一特性虽然保证了主从复制期间性能不受影响,但是也会产生一个数据不一致的时间窗口,如果在这个时间窗口期间网络突然断开连接,就会导致两者数据不一致。如果不在配置文件中添加其他策略,那就默认会采用这种方式。为了防止主从不一致不可控,redis提供了以下两个参数来做约束:

1
2
arduino复制代码min-slaves-to-write 3
min-slaves-max-lag 10

当slave数量小于min-slaves-to-write,且延迟小于等于min-slaves-max-lag时,master停止写入操作。

还有一个参数也会影响主从之间的延时:

repl-disable-tcp-nodelay:

设置成yes,则redis会合并小的TCP包从而节省带宽,但会增加同步延迟,造成master与slave数据不一致。设置成no,则redis master会立即发送同步数据,几乎没有延迟。

Redis的主从同步无论那种场景可以抽象为以下七个步骤:

image.png

1.建立socket连接

从服务器根据设置的套接字创建连向主服务器的套接字连接,主服务器接收从服务器的套接字连接之后,为该套接字创建响应的客户端状态,并将此时的从服务器看做是主服务器的客户端,也就是该从服务器同时具备服务器与客户端两个身份。

2.发送PING命令

PING命令主要有两种作用:虽然建立了套接字连接,但是还未使用过,通过发送PING命令检查套接字的读写状态是否正常;通过发送PING命令检查主服务器能否正常处理命令请求,能处理主服务器回复PONG。

3.身份验证

从服务器接收到主服务器返回的“PONG”回复,接下来就需要考虑身份验证的事。如果从服务器设置了masterauth选项,那么进行身份验证,如果从服务器没有设置masterauth选项,那么不进行身份验证。

4.发送端口信息

在身份验证步骤之后,从服务器将执行命令REPLCONF listening-port ,向主服务器发送从服务器的监听端口号。

5.数据同步

从服务器向主服务器发送SYNC命令、PSYNC命令,执行同步操作。

6.命令传播

主从服务器就会进入命令传播阶段,主服务器只要将自己执行的写命令发送给从服务器,而从服务器只要一直执行并接收主服务器发来的写命令。

五、告一段落

本篇详细介绍了redis主从同步机制,不同场景下同步策略的选择,这也是redis高可用的基石。在此基础上,下一篇将对redis高可用的实现来进行分析讲解。


本文转载自: 掘金

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

Python开发基础总结(一)套接字+字符串+正则表达式

发表于 2021-11-12

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

一、套接字编程:

  1. 函数的功能基本和c类似,唯一不同的地方在于当发生错误时,它不是通过返回值来告知的,而是通过触发异常,所以udp中的bind, recvfrom, sendto必须要进行捕捉异常。
  2. 套接字在垃圾收集的时候也会关闭。
  3. 获取网卡的IP:
1
2
3
ini复制代码s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0X8915, struct.pack('256s', ethname[:15]))[20:24])

二、字符串的使用

  1. Python的字符串是不可以改变的。但是你可以操作字符串以形成新的字符串。
  2. 字符串中删除一个字串。没有直接提供这个方法,但是replace可以实现:
1
vbscript复制代码"abc def".replace(" ", "")

同样的功能还有一个方法:translate。它的原有作用是将字符串中的某个字符替换为另外一个字符,注意,不是字符串。它的第一个参数是一个转换表。第二个参数是要删除的字符串。我们可以利用第二个参数del,实现这个功能。同时,第一个参数设置为None。

translate可能更高效一点。另外,它的第二个参数可以使一个字符串,含有多个字符,这样就会删除多个。

注意: translate方法不会对这个字符串操作,而是返回一个新的字符串。

  1. strip方法:去除字符串两侧的空格,返回新的字符串。这个功能非常有用。
  2. str中有一个函数,format ,非常强大,有时间一定要看一下。
  3. endswitch:检查字符串是否已某字符串结尾。startswith:检查是否已某字符串开头。
  4. partition:它将字符串按指定的字符串分为三个部分,返回一个元组。第一个是指定字符串前面内容,第二个是指定字符串,第三个是指定字符串后面的内容。用于字符串解析非常好用。
  5. split:将字符串按照某指定字符串分割成多个子字符串,返回一个分割后的列表。
  6. join:将一个字符串列表中的各个字符串连接起来,中间插入指定的字符串。
  7. find的返回值不是false和true,所以不可以直接用于if判断。需要判断if s.find(‘’) >= 0:
  8. 基于字典的格式化:
1. sh = *'''*
2. *python -m compileall -fl ../src;*
3. *python -m compileall -fl ../src/micbase;*
4. *mkdir %(packname)s;*
5. *mdkir %(packname)s;*
6. *'''* % { *'packname'* :sys.argv[1], }
7. print(sh)
8.

内建函数:

string.capitalize() 把字符串的第一个字符大写
string.center(width) 返回一个原字符串居中,并使用空格填充至长度 width 的新串
string.count(str, beg=0, end=len(string)) 返回 str 在 string 里面出现的次数,如果 beg 或者 end 指返回指定范围内 str 出现的次数
string.decode(encoding=’UTF-8’, errors=’strict’) 以 encoding 指定的编码格式解码 string,如果出错默认报ValueError 的异常,除非 errors 指定的是’ignore’或’replace’
string.encode(encoding=’UTF-8’, errors=’strict’) 以 encoding 指定的编码格式编码 string,如果出错默认报ValueError的异常, 除非errors指定的是’ignore’或者’repl
string.endswith(obj, beg=0, end=len(string)) 检查字符串是否以 obj 结束,如果 beg 或者 end 指定则检定的范围内是否以 obj 结束, 如果是, 返回True,否则返回Fa
string.expandtabs(tabsize=8) 把字符串 string 中的 tab 符号转为空格, 默认格数 tabsize 是 8.
string.find(str, beg=0, end=len(string)) 检测 str 是否包含在 string 中,如果 beg 和 end 指定范则检查是否包含在指定范围内,如果是返回开始的索引值,返回-1
string.index(str, beg=0, end=len(string)) 跟find()方法一样, 只不过如果str不在string中会报一个异
string.isalnum() a, b, c R如果string至少有一个字符并且所有字符都是字母或数字回 True,否则返回 False
string.isalpha() a, b, c 如果string至少有一个字符并且所有字符都是字母则返回T否则返回 False
string.isdecimal() b, c, d 如果 string 只包含十进制数字则返回 True 否则返回 False.
string.isdigit() b, c 如果 string 只包含数字则返回 True 否则返回 False.
string.islower() b, c 如果 string 中包含至少一个区分大小写的字符,并且所有这些(大小写的)字符都是小写,则返回 True,否则返回 False
string.isnumeric() b, c, d 如果 string 中只包含数字字符,则返回 True,否则返回 False
string.isspace() b, c 如果 string 中只包含空格,则返回 True,否则返回 False.
string.istitle() b, c 如果 string 是标题化的(见 title())则返回 True,否则返回 False
string.isupper() b, c 如果 string 中包含至少一个区分大小写的字符, 并且所有这些(区分大小写的)字符都是大写,则返回 True,否则返回 False
string.join(seq) Merges (concatenates)以 string 作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串
string.ljust(width) 返回一个原字符串左对齐,并使用空格填充至长度 width 的新字符串
string.lower() 转换 string 中所有大写字符为小写.
string.lstrip() 截掉 string 左边的空格
string.partition(str) e 有点像 find()和 split()的结合体,从 str 出现的第一个位置起,把 字 符 串 string 分 成 一 个 3 元 素 的 元 组 (string_pre_str,str,string_post_str),如果 string 中不包含str 则 string_pre_str == string.
string.replace(str1, str2, num=string.count(str1)) 把 string 中的 str1 替换成 str2,如果 num 指定, 则替换不超过 num 次.
string.rfind(str, beg=0,end=len(string)) 类似于 find()函数,不过是从右边开始查找.
string.rindex( str, beg=0,end=len(string)) 类似于 index(), 不过是从右边开始.
string.rjust(width) 返回一个原字符串右对齐,并使用空格填充至长度 width 的新字符串
string.rpartition(str) e 类似于 partition()函数,不过是从右边开始查找.
string.rstrip() 删除 string 字符串末尾的空格.
string.split(str=””, num=string.count(str)) 以 str 为分隔符切片 string,如果 num有指定值,则仅分隔 num 个子字符串
string.splitlines(num=string.count(‘\n’)) b, c按照行分隔, 返回一个包含各行作为元素的列表, 如果 num 指定则仅切片 num 个行.
string.startswith(obj, beg=0,end=len(string)) b, e检查字符串是否是以 obj 开头,是则返回 True,否则返回 False。如果beg 和 end 指定值,则在指定范围内检查.
string.strip([obj]) 在 string 上执行 lstrip()和 rstrip()
string.swapcase() 翻转 string 中的大小写
string.title() b, c 返回”标题化”的 string,就是说所有单词都是以大写开始,其余字母均为小写(见 istitle())
string.translate(str, del=””) 根据str给出的表(包含256个字符)转换string的字符,要过滤掉的字符放到 del 参数中
string.upper() 转换 string 中的小写字母为大写
string.zfill(width) 返回长度为 width 的字符串,原字符串 string 右对齐,前面填充0

三、正则表达式

  1. 为什么要学习正则:主要是为了处理字符串更加方便,特别是为后面进行代码生成做储备。
  2. match是匹配字符串的开头是否匹配,而search是查看字符串任意起始位置是否满足。
  3. sub可以对字符串中模式匹配的部分进行替换
  4. split:可以对字符串进行分割,这里是根据模式分割。

​

本文转载自: 掘金

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

1…365366367…956

开发者博客

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