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

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


  • 首页

  • 归档

  • 搜索

Go语言基础之切片

发表于 2021-11-20

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

切片slice的底层数据是数组,slice是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。数组是定长的,长度定义好之后,不能再更改。而切片则非常灵活,它可以动态地扩容。切片的类型和长度无关。数组就是一片连续的内存,slice 实际上是一个结构体,包含三个字段:长度、容量、底层数组。切片的数据结构如下图:

1
2
3
4
5
6
go复制代码// runtime/slice.go
type slice struct {
array unsafe.Pointer // 元素指针,指向一个数组的指针
len int // 当前切片的长度
cap int // 当前切片的容量
}

go切片结构

1 概述

  • 切片是围绕动态数组概念构建的。但是切片本身并不是动态数组,而是对底层数组的抽象。
  • 按需自动改变大小
  • 与数组相比,切片的长度可以在运行时修改
  • 切片不存具体的值,切片是一个引用类型
  • 相当于java中的ArrayList

2 引子

因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。 例如:

1
2
3
4
5
6
7
go复制代码func arraySum(x [3]int) int{
sum := 0
for _, v := range x{
sum = sum + v
}
return sum
}

这个求和函数只能接受[3]int类型,其他的都不支持。 再比如,

a := [3]int{1, 2, 3}

数组a中已经有三个元素了,我们不能再继续往数组a中添加新元素了。

3 切片的定义

::: tip 声明切片类型的基本语法
var name []T
:::

name:表示变量名 T:表示切片中的元素类型,举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码func main() {
// 声明切片类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化
fmt.Println(a) //[]
fmt.Println(b) //[]
fmt.Println(c) //[false true]
fmt.Println(a == nil) //true
fmt.Println(b == nil) //false
fmt.Println(c == nil) //false
// fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较
}

::: tip 切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。
:::

4 基于数组定义切片

由于切片的底层就是一个数组,所以我们可以基于数组定义切片。

1
2
3
4
5
6
7
go复制代码func main() {
// 基于数组定义切片
a := [5]int{55, 56, 57, 58, 59}
b := a[1:4] //基于数组a创建切片,包括元素a[1],a[2],a[3]
fmt.Println(b) //[56 57 58]
fmt.Printf("type of b:%T\n", b) //type of b:[]int
}

还支持如下方式:

1
2
3
go复制代码c := a[1:] //[56 57 58 59]
d := a[:4] //[55 56 57 58]
e := a[:] //[55 56 57 58 59]

5 切片再切片

除了基于数组得到切片,我们还可以通过切片来得到切片。

1
2
3
4
5
6
7
8
9
go复制代码func main() {
//切片再切片
a := [...]string{"北京", "上海", "广州", "深圳", "成都", "重庆"}
fmt.Printf("a:%v type:%T len:%d cap:%d\n", a, a, len(a), cap(a))
b := a[1:3]
fmt.Printf("b:%v type:%T len:%d cap:%d\n", b, b, len(b), cap(b))
c := b[1:5]
fmt.Printf("c:%v type:%T len:%d cap:%d\n", c, c, len(c), cap(c))
}

输出

1
2
3
go复制代码a:[北京 上海 广州 深圳 成都 重庆] type:[6]string len:6  cap:6
b:[上海 广州] type:[]string len:2 cap:5
c:[广州 深圳 成都 重庆] type:[]string len:4 cap:4

::: warning 注意
对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。
:::

6 使用make()构造切片

我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,
::: tip 格式如下
make([]T, size, cap)
:::

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量

举例如下

1
2
3
4
5
6
go复制代码func main() {
a := make([]int, 2, 10)
fmt.Println(a) //[0 0]
fmt.Println(len(a)) //2
fmt.Println(cap(a)) //10
}

上面代码中a的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。

make初始化和普通声明切片的区别

1
2
3
4
5
6
7
go复制代码    //用make初始化切片,并赋值初始值
s1:=make([]int,5,10)
fmt.Println(s1) //[0 0 0 0 0]
//声明切片 但是没有初始化赋值
var s2 []int
fmt.Println(s2==nil,s1==nil)//true false
fmt.Println(len(s1),len(s2)) //5 0

7 切片的本质

::: tip 切片本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。
:::

slice_01

切片s2 := a[3:6],相应示意图如下:

slice_02

切片不能直接比较

切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil,例如下面的示例:

1
2
3
go复制代码var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

::: warning 注意
所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。
:::

8 切片的赋值拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

8.1 一般赋值

1
2
3
4
5
6
7
go复制代码func main() {
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
}

8.2 使用copy()函数复制切片

1
2
3
4
5
6
7
8
9
go复制代码func main() {
a := []int{1, 2, 3, 4, 5}
b := a
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1 2 3 4 5]
b[0] = 1000
fmt.Println(a) //[1000 2 3 4 5]
fmt.Println(b) //[1000 2 3 4 5]
}

由于切片是引用类型,所以a和b其实都指向了同一块内存地址.修改b的同时a的值也会变化.
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

::: tip 格式如下
copy(destSlice, srcSlice []T)

  • srcSlice: 数据来源切片
  • destSlice: 目标切片
    :::
1
2
3
4
5
6
7
8
9
10
11
go复制代码func main() {
// copy()复制切片
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
}

9 切片遍历

切片的遍历方式和数组是一致的,支持索引遍历和for range遍历类似于java的增强foreach。

1
2
3
4
5
6
7
8
9
10
11
go复制代码func main() {
s := []int{1, 3, 5}

for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}

for index, value := range s {
fmt.Println(index, value)
}
}

10 append()方法为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素。 每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时。 举个例子

1
2
3
4
5
6
7
8
go复制代码func main() {
//append()添加元素和切片扩容,自动初始化切片
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
}
}

输出

1
2
3
4
5
6
7
8
9
10
go复制代码[0]  len:1  cap:1  ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000

一次性追加多个元素

1
2
3
4
5
6
7
8
9
go复制代码var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]

11 从切片中删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

1
2
3
4
5
6
7
go复制代码func main() {
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]
}

12 切片删除元素再举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码    arr := [...]int{1, 3, 5, 7, 9, 11}
//把数组arr赋值给切片s1
s1:=arr[:]
fmt.Println(s1)
//打印S1的内存地址
fmt.Printf("s1删除元素前的内存地址%p\n", s1)
//删除s1的中的3 5 s1的结果为[1 7 9 11]
s1=append(s1[:1],s1[3:]...)
//打印S1的内存地址
fmt.Printf("s1删除元素后的内存地址%p\n", s1)
//s1删除元素的值为[1 7 9 11]
fmt.Println(s1)
//s1指向的数组arr的值为[1 7 9 11 9 11]
fmt.Println(arr) //[1 7 9 11 9 11]

slice_03
::: tip 总结

  • 切片永远是不存值的(切片是指向对应一个底层数组,底层数组都是占用一块连续的内存)
  • 要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]…)
    :::

::: danger 切片的内置函数汇总

  • len 获取slice的长度
  • cap 获取slice的最大容量
  • append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice
  • copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数
    :::

本文转载自: 掘金

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

快看看区块链是什么,别再被骗了

发表于 2021-11-20

区块链是什么?很多人为之迷惑,因为从来没有一个技术的争议性有这么大,集财富、骗局、新科技等等话题于一身。

但是实际上,这是一种过度包装。之前看到过一个段子,用来形容这个情况再适合不过了:”他们说区块链不会骗人,但是他们会拿区块链骗人“。很多人也是因为不懂区块链所以被骗的。但区块链其实根本就不复杂,无论是从技术上还是概念上。

这篇文章会从科普的角度来说一下区块链运作的细节。主要以比特币网络为主,后面也会聊一下联盟链的一些情况。区块链中的新名词比较多,我会尽可能的说的通俗易懂一点。

  1. 区块链是什么

区块链脱胎于比特币,用来存储比特币中产生的交易数据,区块链有两个特点:

  • 数据写入到区块之后就很难(注意这里,是很难,不是不能)被篡改,通常被称之为不可篡改
  • 去中心化,没有人可以真正掌控整个系统

1.1 区块链的结构

区块链技术本身并不复杂,学过编程的人都对链表这个数据结构很清楚,某种程度上区块链其实就是这样的一个链表。

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4b565411aa9a45878300d121a11673ef~tplv-k3u1fbpfcp-zoom-1.image

区块链就是由这样的诸多区块构成的,这里需要注意的是,箭头的方向是不能反的,都是新产生的区块指向前一个区块,然后每个区块中都会存储一些数据。

那这些区块是怎么连接起来的呢?

这就要说到区块链中很重要的一个部分,哈希函数。这是数学或者计算机中的一个概念。它的特点是,它可以对一段数据做特定的操作,输出一段固定长度的字符串,这个字符串称之为哈希值,但是你无法(很难)根据输出推算出来输入是什么,但是你可以很快的根据输入来验证哈希值是否正确。

1
go复制代码h = hash("I got 100 bitcoins") //16df768dash

而且对于哈希函数来说,只要传入的数据有一点不一样,那么产生的哈希值就会天差地别。

对于哈希函数,你只需要知道它可以干什么就可以了。在下面讲挖矿的时候还会用到哈希函数。

区块就是利用这个特点,算出它前面那个区块的哈希值,并把这个哈希值放到自己的区块中。也就是是说每一个区块都记录了自己前一个区块的哈希值。并通过这种方式连接起来。如果这个链中任何一个区块的数据被篡改,这个区块的哈希值就会产生变化,那么这个区块之后的其他区块就无法连上这个区块。

这是真的牵一发而动全身,这也是区块链不可篡改这个特性在技术上的实现。

1.2 区块链网络

但我们日常所讨论的区块链实际是区块链网络。

区块链网络是去中心化的,也就是常说的分布式。比特币网络可以说是去中心化最彻底的软件系统。这个网络是完全放开的,任何人都可以下载一个比特币软件接入到网络中,成为网络中的一个节点。

在区块链网络中,每个网络节点都会维护上面的那个链状的结构。节点之间会相互同步数据,最终每个节点所维护的数据是一模一样的。

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc05ec16309f4d2381e23c5d595d6add~tplv-k3u1fbpfcp-zoom-1.image

任何一个人都可以接入到比特币网络中,把所有的区块链数据同步到自己本地。换句话说,比特币网络对所有人都是公开透明的。

在接入区块网络的时候,我们需要生成自己的账户。账户实际上是通过非对称加密生成的一堆公私钥对。其中,公钥是可以对外暴露的,公钥再做一些转换就成了地址。这个地址就是我们通常所说的钱包地址。

在区块链中,钱包这个词出现的频率也非常的高。但这个钱包与我们通常所认知的钱包是完全不同的。不同于支付宝或者任何一款银行 APP。

钱包实际上就是由一个地址及一个公私钥组成,掌握了私钥就掌握了这个钱包。

1.3 区块是怎么生成的

区块中包含的都是交易。交易在比特币网络中很简单,其实就是转账,你把你的比特币转移到任意一个地址中就是一笔交易。

但每个区块中能够包含的区块是有限的。所以一次性可以打包的交易数量也是有限的。这个时候就要让矿工来决定把哪些交易打包进区块了。刚开始了解区块链的人一定听说过矿工。这个词很容易让人混淆,但又很贴切。

矿工在这里就会开始争夺记账权。记账权就是打包下一个区块的权利。如果获得了记账权,就可以获得这个区块生成后的比特币奖励。这也是促使大量矿工加入比特币网络的原因。争夺这个记账权的过程也被称之为共识过程。

那么怎么争夺记账权呢?其实就是来做题。谁先找到答案,记账权就给谁。

在上面说到的哈希函数是起到作用了。这道题是这样的,找出一个字符串,使它的哈希值是 0x00000……。

这里 0 的个数是可以调整的,比特币网络中的难度调整其实就是调整 0 的个数。在上面我们说过根据哈希值去倒推原始的数据是非常困难的,基本可以等同于无法完成。所以唯一的方式就是暴力破解。

就是去尝试各种不同的字符串,直到找到一个符合条件的答案为止。找到这个答案之后,这个节点就会把结果同步给其他的节点,根据哈希函数的特性,其他的节点很轻松就验证出来这个答案是不是正确的。这个过程也被称之为 PoW(Proof of Work) 共识机制,Pow 是一种分布式一致性性的共识算法。

在产生新区块的过程中,需要进行大量的运算,而且随着比特币越来越值钱,那么加入的矿工也就越来越多,就会导致这道题的难度会越来越大。需要的计算量也就越来越大。耗费的电量也越来越多,这也是比特币最受人诟病的地方。

分布式一致性的算法还有 Paxos/Raft/PBFT 等等,为什么一定要使用 PoW 这么消耗能源的算法呢?因为 Paxos/ Raft 等算法对环境有着严格的要求:

  • 首先它需要 Leader 选举,这在区块链网络中是不可能的
  • 而且它会假设所有的节点都是可信的,而实际上,在开放的网络中,没有节点是可信的
  • 区块链网络中节点数量非常多,这些算法无法处理这么大的网络

公链上的区块链网络就是这么形成的。在一个无信任的网络环境中,通过哈希和 PoW 共识算法就构建起来一条可信的区块链。

  1. 联盟链有什么不同

还有除了比特币、以太坊这些公链之外,也还有一种联盟链的网络,这种网络是有准入门槛的,国内现在做的所有区块链应用都是基于联盟链的。

联盟链与公链最大的不同在于它的网络是准入制的,得到许可之后才能加入,所以联盟链中所有的节点都是可信的,而且节点的数量也会控制在一定范围内,外部的节点无法随意加入。

在这样的网络环境中,自然不需要 PoW 这种算法,使用常规的分布式一致性算法即可,甚至有的联盟直接不需要共识算法。

联盟链中暂时不能出现币,这样其实就把区块链网络中最重要的部分砍掉了。公链之所以能够运转起来,关键是内部的形成了一个完整的经济系统,什么时候联盟链可以把这部分补足了。那么联盟链的春天就到了。

  1. 区块链技术有价值吗

比特币火起来之后,有人就从中提取出来了一个部件,取名为区块链。然后又有人拿着这个,然后对所有人说,这,就是未来。但实际上,区块链只是一种技术,而且这种技术本身没有任何革命性的地方,区块链所用到的技术在很早之前就有了。它将现有的一些技术组合在一起,得到了一个新的概念。

拿比特币来说,区块链只是它用来存储交易的数据结构。重要的是比特币网络中汇集了诸多人的共识,这才是比特币真正的价值,就是说比特币的这些数据本身没有价值,是那些承认这些数据有价值的人早就了比特币的价值。

区块链技术本身没有价值,为什么那么多学习、抄袭比特币的各种币都无法追赶比特币,这是因为比特币中最有价值的不是区块链,而是共识。

搭建一个区块链网络的成本低的可怜。用自己的笔记本电脑,一小时就可以搭建一个区块链网络,对于做区块链来说,用的什么链根本不重要,重要的是这条链有多少人承认。

但这并不是说区块链技术一文不值。区块链的不(几乎)可篡改性让这种结构天然就适合作为价值存储的载体。我们当前的移动互联网让信息传递的成本几乎为零。但是价值的传输还是有很大的问题。而区块链有望来解决这个问题。

  1. 小结

区块链技术本身是中立的,各种骗局给区块链带来了坏名声。但就像所有的新生事物一样,在刚开始总是坎坷的。区块链技术目前也还在不断的发展中,也有不少场景的问题有望使用区块链来解决。在曲折中不断上升,区块链这条路还有很长的路要走。

文 / Rayjun

本文转载自: 掘金

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

MSQL的索引及其介绍总结

发表于 2021-11-20

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

前言

  大家好,作为开发人员,在操作关系型数据库中,经常会遇到索引失效的情况,导致查询速度慢,查询时间过长。那么那些情况下会导致索引失效呢?首先来了解MSQL中的索引。

索引

什么是索引

  索引是指在关系型数据库中一种单独的、物理的对数据库表中一列或者多列进行排序的存储结构。索引的引入类似于一种导航目录,能够快速的找到需要查询的数据信息。当查询条件中有索引时,在不读取整个表的情况下,索引使数据库应用程序可以更快地查找数据。当表中加入索引后,用户是无法直接查看到索引的,它们只能被用来加速搜索/查询,如果要查看表中是否加入索引,可以通过查询表结构来查看。

  索引虽然提高了查询速度,也同时降低了对表中的更新和修改速度,需要更新数据的同时,需要更新索引。

  MySQL中的主要索引类型有:普通索引、唯一索引、主键索引、组合索引。

  普通索引是最基本的索引,没有任何限制。唯一索引与普通索引类似,唯一索引的特点是唯一索引的列中必须是唯一的,可以为null,唯一索引可以是单列,也可以是多个列组合的唯一索引。主键索引是表中主键作为索引,建立表的主键的时候,一般就建立主键索引了。组合索引很好理解,就是多个数据库字段组合成的一个索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js复制代码CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`createTime` datetime DEFAULT NULL,
`email` varchar(200) DEFAULT NULL,
`expiredDate` date DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
`state` tinyint(4) DEFAULT NULL,
`tel` varchar(255) DEFAULT NULL,
`version` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4;

  为了方便测试索引的创建过程,本次将以下“user”表作为测试表来进行演示。

创建普通索引

  在实际操作用使用用户名称查询的是比较多的,我们基于用户名称创建一个普通索引。用户名称字段是“name”。
创建普通索引的语法结构为:CREATE INDEX 索引名称 ON 表名 (字段信息)

1
go复制代码CREATE INDEX `index_name` ON  user(`name` )

执行上面SQL之后可以看到已经执行成功。
图片.png
可以看到通过创建的方式可以创建一个普通索引,我们也可以通过修改表结构的方式创建索引。
在Navicat等数据库可视化工具中,选中需要操作的表,点击设计表,出现如下图的页面

图片.png
点击索引

图片.png
  其中名是索引名称,字段是指数据中表中的字段名称,选择索引类型和索引方法,填写注释点击保存即可创建索引。这种方式是不是很快捷呢。
创建索引保存前
图片.png
创建索引保存后
图片.png
当然,唯一索引、组合索引也是可以通过这种方式创建的。

创建唯一索引

  在我们设计表的时候,有时候某些字段是唯一的不能重复的,例如我们在注册用户的时候,一个邮箱或者手机号,一般只能注册一个用户,因此在设计中,针对此类字段就应该加上唯一索引。在程序针对此类数据进行校验处理。那么创建唯一索引的语法结构是:CREATE UNIQUE INDEX 索引名称
ON 表名称 (字段信息)

1
js复制代码CREATE UNIQUE INDEX `index_email` ON `user` (`email`)

执行上面的SQL语句可以看到,已经创建唯一索引成功了。
图片.png
查看表结构发现已经有两个索引了,一个普通索引,一个唯一索引。

图片.png

创建组合索引

  在业务开发中,针对某些场景,需要用户粗昂及组合索引,在使用组合索引时遵循最左前缀集合。
创建组合索引的语法结构如下:
CREATE INDEX 索引名称 ON 表名称 (字段信息1,字段信息2,…)

1
js复制代码CREATE  INDEX index_age_tel ON `user` (`age`,`tel`)

执行上面SQL之后,执行结果如下:
图片.png
图片.png

创建主键索引

  主键索引一般在创建表的时候就创建主键了,不需要专门创建主键索引。例如上面我们测试的表中,PRIMARY KEY (id)这个ID就是主键索引。

图片.png

结语

  好了,以上就是MSQL的索引及其介绍总结,感谢您的阅读,希望您喜欢,如对您有帮助,欢迎点赞收藏。如有不足之处,欢迎评论指正。下次见。

  作者介绍:【小阿杰】一个爱鼓捣的程序猿,JAVA开发者和爱好者。公众号【Java全栈架构师】维护者,欢迎关注阅读交流。

本文转载自: 掘金

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

【JAVA】【获取vue页面】什么?不支持vue页面的内容获

发表于 2021-11-20

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

【每日一点事】

  毛豆和黄豆其实是一个东西,毛豆是年轻的时候,黄豆是已经年老的时候。
image.png

一、前言

最近,一个网友问,怎么想做个数据调查报告,都被页面加载403、页面内容加载不到、vue页面劝退。什么?这点小事还解决不了?vue页面不支持?我立马甩了这篇代码给他!
vue页面
image.png
403页面
image.png

二、代码分析

没改代码前的代码(获取不到vue页面和403页面)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码	public static void main(String[] args) {
// 这里举例找了个vue页面(小牛翻译)的来测试
String nowHtml = "https://www.niutrans.com";
URL url;
try {
url = new URL(nowHtml);
URLConnection openConnection = url.openConnection();
InputStream inputStream = openConnection.getInputStream();
byte[] b = new byte[1024];
int len;
while ((len = inputStream.read(b)) != -1) {
System.out.println(new String(b, 0, len));
}
inputStream.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

改动后的代码需要的依赖

1
2
3
4
5
6
xml复制代码		<!-- 获取页面内容依赖 -->
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.43.0</version>
</dependency>

改动后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java复制代码
public static void main(String[] args) {
// 这里举例找了个vue页面(小牛翻译、b站页面)的来测试
String nowHtml = "https://www.niutrans.com";
// String nowHtml = "https://www.bilibili.com";
getWebBody(nowHtml);
}

public static void getWebBody(String nowHtml) {
WebClient webClient = new WebClient(BrowserVersion.CHROME);
webClient.getOptions().setActiveXNative(false);// 不启用ActiveX
webClient.getOptions().setCssEnabled(false);// 是否启用CSS,因为不需要展现页面,所以不需要启用
webClient.getOptions().setUseInsecureSSL(true); // 设置为true,客户机将接受与任何主机的连接,而不管它们是否有有效证书
webClient.getOptions().setJavaScriptEnabled(true); // 很重要,启用JS
webClient.getOptions().setDownloadImages(false);// 不下载图片
webClient.getOptions().setThrowExceptionOnScriptError(false);// 当JS执行出错的时候是否抛出异常,这里选择不需要
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);// 当HTTP的状态非200时是否抛出异常,这里选择不需要
webClient.getOptions().setTimeout(15 * 1000); // 等待15s
webClient.getOptions().setConnectionTimeToLive(15 * 1000);
webClient.waitForBackgroundJavaScript(10 * 1000);// 异步JS执行需要耗时,所以这里线程要阻塞30秒,等待异步JS执行结束

HtmlPage page = null;
try {
page = webClient.getPage(nowHtml);// 加载网页
} catch (Exception e) {
e.printStackTrace();
} finally {
webClient.close();
}
String htmlStr = page.getBody().asXml();
System.out.println(htmlStr);
}

结果展示

image.png

三、结论

在页面请求中,会存在http(s)证书是否有效问题,也会存在重定向(403)页面,并且还有存在页面是靠js动态渲染的(vue页面)。因此,在获取页面内容中,我们需要考虑的诸多问题。

  HtmlUnit都解决了这些问题,并且不需要像其他工具类那样,需要再配合浏览器,安装一个浏览器装置等等。总得来说,HtmlUnit功能很全,使用起来也很方便!

【最后】

  感谢你看到最后,如果你持有不同的看法,欢迎你在文章下方进行留言、评论。

我是南方者,一个热爱计算机更热爱祖国的南方人。

文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。

本文转载自: 掘金

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

C# 使用ToolTip控件实现气泡消息提示 前言: 每日一

发表于 2021-11-20

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

前言:

今天我们学习一个新的控件ToolTip控件,这个控件使用频率特别高,博主就想着写一篇文章来教大家一起用这个控件,这个控件的效果是我们的鼠标停留在那个位置实现提示操作,比如我们在做登录操作密码方面或者账号方面的提示鼠标停留的提示操作,这个操作效果在我们前端网页用的特别多,只不过我们做的是窗体应用。

每日一遍,欢乐一整天

image-20211120195755872

1.创建窗体文件

image-20211120171631746

2.设计界面

把需要拖的控件从工具箱拖过来,特别是toolTip控件

image-20211120172041556

3.配置toolTip控件

对触发时间设置还有停留时间设置,默认的也可以用。

image-20211120185254133

1
2
3
4
5
6
sql复制代码Active属性:确定工具指示是否是活动的。只有在激活工具提示后才会显示提示信息
AutiPopDealy属性:确定当指针在工具提示区域内保持静止时,工具提示窗口保持可见的时间长度
InitialDealy属性:确定在提示窗口显示之前,指针必须在工具提示区域内保持静止时间的长度
IsBallon属性:这个属性指示控件是否以气球的形式出现。默认为false
OnwerDraw属性:这个属性指示系统是否可以绘制子项、如果是那则是为False
ReShowDealy属性:确定当一个指针从从一个工具提示区域移动到另一个工具区域提示时,后面的工具提示窗口将在多长时间内显示
  1. 对需要设置提示的地方设置提示

我们拖了toolTip控件就会生成一个属性,我们可以在属性里面看到,在里面输入你需要提示的信息

image-20211120190349429

4.1 简单效果演示

这个只是简单演示默认的样式和气泡样式,下面会有其他设置

4.1.1 默认提示样式

4.1.2 使用气泡样式

就在toolTip属性将IsBallon属性改为true就可以了

20212 00_00_00-00_00_30

4.2 对提示设置图标和标题

image-20211120191055356

image-20211120191719619

注:这里我们展示了标题和图标我们发现其他的控件提示标题和图标也是一样的,我们需要修改的话需要使用代码动态设置

4.3 使用代码对图标和标题动态设置

右击toolTip控件属性,在事件里面双击生成函数,对toolTip代码设置

image-20211120192041074

代码设置展示

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
ini复制代码using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace 实现提示操作
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void toolTip1_Popup(object sender, PopupEventArgs e)
{
ToolTip tool = (ToolTip)sender;
if(e.AssociatedControl.Name == "textBox1")//e代表我们要触发的事件,我们是在textBox1触发
{
tool.ToolTipTitle = "提示信息";//修改标题
tool.ToolTipIcon = ToolTipIcon.Info;//修改图标
}
else
{
tool.ToolTipTitle = "警告信息";
tool.ToolTipIcon = ToolTipIcon.Warning;
}
}
}
}

整体效果展示

提示的标题和提示的图标是不一样的,默认是相同的需要我们自己修改

20213 00_00_00-00_00_30

总结

这个toolTip控件在窗体应用里面使用频率比较高,在做一些窗体应用时候我们经常需要提示别人,博主只是简单教大家一下,大家可以自己拖一拖控件,敲一敲,实践里面出真知,有什么问题我们可以一起探讨一下,对于C#的窗体学习博主也只是自己的兴趣而言,将来也没打算从事这方面的工作,因为博主感觉这方面的工作太少了,可能会转语言学习。好了,大家一起努力,点赞关注评论收藏哦!!!

520112

本文转载自: 掘金

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

Redis 主从架构之深扒配置文件(二)

发表于 2021-11-20

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

Redis 的配置文件配置比较多,我们将分几个章节依次击破,大家记得查看连载呦!我们接着上文中的配置信息继续往下说。

replica-serve-stale-data yes

当 replica 与 master 失去连接或者主从复制在进行时,replica 可以有两种不同的设置:

  • replica-serve-stale-data:yes(默认值),则 replica 仍将响应客户端请求,可能会有过期数据,或者如果这是第一次同步,则数据集可能为空。
  • replica-serve-stale-data:no , replica 将对所有请求命令(但不包含 INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, HOST: and LATENCY)返回 SYNC with master in progress 的错误。

repl-diskless-sync

复制同步策略:磁盘(disk)或套接字(socket),默认为 no 使用 disk 。

新的 replicas 和重新连接的 replicas 如果因为接收到差异而无法继续复制过程,则需要执行“完全同步”。RDB 文件从 master 传送到 replicas,传输可以通过两种不同的方式进行:

  1. Disk-backed:Redis master 节点创建一个新的进程并将 RDB 文件写入磁盘,然后文件通过父进程增量传输给 replicas 节点;
  2. Diskless:Redis master 节点创建一个新的进程并直接将 RDB 文件写入到 replicas 的 sockets 中,不写到磁盘。
  • 当进行 disk-backed 复制时, RDB 文件生成完毕,多个 replicas 通过排队来同步 RDB 文件。
  • 当进行 diskless 复制时,master 节点会等待一段时间(下边的repl-diskless-sync-delay 配置)再传输以期望会有多个 replicas 连接进来,这样 master 节点就可以同时同步到多个 replicas 节点。如果超出了等待时间,则需要排队,等当前的 replica 处理完成之后在进行下一个 replica 的处理。

硬盘性能差,网络性能好的情况下 diskless 效果更佳

警告:无盘复制目前处于试验阶段

repl-diskless-sync-delay

当启用 diskless 复制后,可以通过此选项设置 master 节点创建子进程前等待的时间,即延迟启动数据传输,目的可以在第一个 replica 就绪后,等待更多的 replica 就绪。单位为秒,默认为5秒。

repl-ping-replica-period

Replica 发送 PING 到 master 的间隔,默认值为 10 秒。

Redis 的配置文件配置比较多,我们将分几个章节依次击破,大家记得查看连载呦!如果你有不同的意见或者更好的idea,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!

本文转载自: 掘金

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

选择排序,插入排序,快速排序

发表于 2021-11-20

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

选择排序

选择排序(Select Sort) 是直观的排序,通过一个中间量从带排序的的数中找出最大或最小的交换到对应位置。再选择次之。选择排序和冒泡排序一样,都有两层循环。

让我们通过动态图来看一下选择排序的过程图:
在这里插入图片描述
让我们通过动态图来看一下选择排序的过程图:

这个动态图演示了一个无序数组使用选择排序转变为一个从大到小的有序数组,让我们来观察一下,在进入内循环之前,会记录一个值,这个值是当前外层循环A[i]的值,之后拿着这个值,在内循环遍历数组的时候,跟数组的元素一个一个的比较,如果这个值比当前下标的元素的大,那么就双方的值继续交换,内循环完成后,将这个值再赋值给A[i],经过内循环怎么一折腾,最后A[i]一定是拿到最小或者最大的值。

和冒泡排序不一样的是冒泡排序如果两个不一样当场就交换,而选择排序是将值给了一个中间变量,让这个中间变量加入内循环,等内循环结束,把真正最大或者最小的值赋值给外出循环的A[i]。


选择排序原理总结

[1]记录数组第一个元素的值(数组长度n)。

[2]遍历n-1次,用该值与其他元素比较,找到最大或者最小的一个数交换。

[3]记录数组第二个元素的值。

[4]遍历n-2次,用该值与其他元素比较,找到最大或者最小的一个数交换。

[5]重复上述步骤,遍历N次,直到没有要比较的数。


代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cpp复制代码  int array[] = { 7,10,12,3,9,5 };
for (int i = 0; i < 6; i++)
{
int minkey = i;
for (int j = i + 1; j < 6; j++)
{
if (array[minkey] > array[j])
{
minkey = j;
}
}
int tamp = array[i];
array[i] = array[minkey];
array[minkey] = tamp;
}

插入排序

百度百科上面有一个不错的例子是这样描述插入排序的,插入排序的工作方式像许多人排序一手扑克牌。开始时,我们的左手为空并且桌子上的牌面向下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。这样,拿在左手上的牌总是排序好的。

用一个动态图演示插入排序过程:
在这里插入图片描述
首先把数组第一个元素a[0]看作是一个有序数列,然后a[1]开始与a[0]做比较,发现10比7大,那么a[1]的值需要插入在a[0]的前面,表示下来就是a[0]的值往后移到a[1],然后a[1]的值移向a[0],然后再拿a[2]与之前的有序数列a[0],a[1]做比较,发现12大于10和7,那么原来的a[0]的值和a[1]的值,继续向右移动一个单位,把a[2]的值插入a[0],如此,a[0],a[1],a[2]就是一个有序数列,如此往复,直到之后排序完成。


插入排序原理

1、将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;

2、取出下一个元素,在已经排序的元素序列中从后向前扫描;

3、如果该元素(已排序)大于新元素,将该元素移到下一位置;

4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

5、将新元素插入到该位置后;

6、重复步骤2~5。


代码

1
2
3
4
5
6
7
8
9
10
11
cpp复制代码	int arr[] = { 1,5,69,8,10 };
int j;
for (int i = 1; i < 5; i++)
{
int temp = arr[i];
for (j = i - 1;j >= 0 && arr[j] < temp;j--)
{
arr[j + 1] = arr[j];
}
arr[j+1]= temp;
}

快速排序

快速排序是对冒泡排序的一种改进。它的基本思想是:通过一次排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归或者非递归进行,以此达到整个数据变成有序序列。

快速排序过程图:
hgfh
快速排序和归并排序有一些相同点和一些不同点,比如两种都使用了分治的思想,都是递归+排序,不同点在于归并排序是先递归后排序,而快速排序是先排序,后递归。


快速排序原理

1)设置两个变量i、j,排序开始的时候:i=0,j=N-1; [1]
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0]; [1]
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]的值交换; [1]
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换; [1]
5)重复第3、4步,直到 i 等于 j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外, i 等于 j 这一过程一定正好是i+或j-完成的时候,此时令循环结束)。


代码

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
cpp复制代码void func(int * a, int s, int e)
{
//快速排序使用的是分治法,我们可以使用递归来重复操作

if (s == e)return;
//排序
int i = s;
int j = e;
int data = a[s];
int arrint = s;
while (j != i)
{
//从右往左遍历
for (int f = j; f >=s; f--)
{
if (j == i) break;
if (data <= a[f])
{
int temp = a[f];
a[f] = a[arrint];
a[arrint] = temp;
arrint = f;
j = f - 1;
break;
}
j = f - 1;
}
//从左往右遍历
for (int f = i; f <= e; f++)
{
if (j == i) break;
if (data >= a[f])
{
int temp = a[f];
a[f] = a[arrint];
a[arrint] = temp;
arrint = f;
i = f+1;
break;
}
i = f + 1;
}
}
int min = s + (e - s) / 2;
func(a, s, min);
func(a, min + 1, e);
}

int main()
{
//归并的参数值 和快速参数值的不同作用
int a[] = { 8,7,10,12,4,5,6,9 };
func(a, 0, 7);

for (int i = 0; i <8; i++)
{
cout << a[i] << " ";
}
return 0;
}

本文转载自: 掘金

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

线上出问题?远程DeBug会吗? Java随笔记

发表于 2021-11-20

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


相关文章

Java随笔记:Java随笔记


  • 唉。。。又是陈年老文章。。水的我老脸都不知道往哪里放了。。。
  • 本地我们启动一个jar包怎么启动?
    • 简单 java -jar 包名即可。
    • 那服务器上呢?java -jar 是控制台启动哦,当我们关闭进程的时候,服务同样也暂停了。
    • 当然我们也可以使用nohub来进行后台启动。但是,shell脚本不是更香嘛!
    • ./start.sh 一键启动jar包,完美!

1)服务器目录结构如下:

目录结构

2)打开start.sh文件,代码如下:

1
bash复制代码 nohup java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8989,suspend=n -jar sdkclient-0.0.2-SNAPSHOT.jar -Dspring.config.location=application.yml &
  • 以下都为同目录级别下设置:
+ nohup :日志设置,运行时将日志保存在nohup.out文件下。
+ java -jar 【jar包名】 :启动该jar包服务
+ Xdebug :设置支持远程debug,设置端口后,可在本地idea上打断点,查看断点日志

3) 远程DeBug

  • 远程断点方法:edit configuration –> Remote –> Host 【设置远程服务器IP】–> Port 【端口号】–> ok
  • 如下图所示: 设置图
+ address = 【端口号】 :该端口设置后,在本地可用idea远程该服务器服务
  • 远程DeBug的用处真的挺多的,有时候线上和开发环境并不是完全一致的。
  • 我们在开发环境中一切都是好好地,但是一到线上就出问题,这个时候我们看日志可能定位较慢,但是想快速定位该怎么办?
  • 远程DeBug来一手,瞬间解决问题!
  • Dspring.config.location = 【application.yml】 设置服务端所使用的yml文件依赖

4)启动

  • 进入该脚本目录下,输入:./start.sh 启动该脚本

5)本地也可以这样启动

  • 在该脚本目录下,鼠标右键,使用终端打开:
  • 输入:java -jar 【jar包名】 如下: 启动该项目服务 运行成功显示效果
+ 出现以上代码,表示启动成功。接下来就可以访问该服务了。

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

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

本文转载自: 掘金

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

Python matplotlib 绘制3D图 前言 1

发表于 2021-11-20

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

前言

在matplotlib模块最初的时候只支持绘制2D图形,例如我们之前也学习过绘制折线图、柱状图、直方图等统计图表,同时也学习绘制多个子图、常见图形等。如下例举往期文章,可供参考

  • matplotlib 底层结构:matplotlib模块分为脚本层、美工层和后端,协同工作流程的学习
  • matplotlib 绘制折线图:pyplot.plot()绘制折线图相关属性进行汇总说明
  • matplotlib 绘制柱状图:pyplot.bar()绘制绍柱状图相关属性进行汇总说明
  • matplotlib 绘制直方图:pyolot.hist()绘制直方图相关属性进行汇总说明
  • matplotlib 绘制图形:对常见的矩形、圆形等图形绘制方法介绍

随着技术发展,matplotlib模块在2D绘制的基础,封装出实用的3D绘制工具包mplot3d来支持我们更快地绘制3D图形。

image.png

本期,我们将详细学习3D绘制模块mplot3d相关绘制方法,Let’s go~

  1. mplot3d 概述

mplot3d库是matplotlib模块中专门针对绘制3D图像所提供的2D投影的Axes对象,为Matplotlib 增加了可以绘制散点图、曲面图、折线图、网格等。

  • mplot3d 特点
+ mplot3d 允许用户创建matplotlib 2D绘图的简单3D图形
+ mplot3d 方法易上手,直接可以使用2D方法进行绘制
+ mplot3d 绘制3D图像与2D图像共用同的渲染引擎,可能会出现Z轴投影出现偏差
+ mplot3d 样式可以通过更改参数来进行个性化修改
  • mplot3d 使用

我们在使用mplot3d工具包时,需要额外使用from…import来导入Axes3D类

1
python复制代码from  mpl_toolkits.mplot3d import Axes3D

PS:

  • pyplot类无法进行3D绘图添加内容、处理3D附加信息等,必须要使用Axes3D对象来进行创建
  • mplot3d 绘制3D图形方法还不太成熟,对于复杂的3D图场景,建议使用Mayavi
  1. 绘制3D图形步骤

在 matplotlib 模块中提供绘制3D图形需要mplot3d中Axes3D对象与pyplot方法结合使用,因此绘制3D图形有如下步骤

  • 导入matplotlib.pyplot和mpl_toolkits.mplot3d中Axes3D类
1
2
python复制代码import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
  • 使用Pyplot.figure创建一个fig画布对象
1
python复制代码fig = plt.figure()
  • 在fig画布中创建一个带3D坐标的Axes对象
+ 方式一:使用创建子图通过projection='3D'来创建
1
python复制代码ax = fig.add_subplot(projection='3d')
+ 方式二:调用Axes3D类来创建对象
1
python复制代码ax = Axes3D(fig)
  • 调用numpy.random或者numpy.arange()等方法准备x,y轴数据
1
2
python复制代码x = np.arange(-5,5,0.25)
y = np.arange(-5,5,0.25)
  • 调用numpy.meshgrid()方法对x,y进行映射矩阵
1
python复制代码x,y = np.meshgrid(x,y)
  • 根据x,y矩阵数据按需求进行计算求出Z轴数据,例如调用numpy.sin()、cos()函数
1
2
python复制代码R = np.sqrt(x**2+y**2)
z = np.cos(R)
  • Axes对象调用pyplot绘制图表、图形的方法绘制,例如调用等高线图contour、曲面plot_surface()
1
2
3
python复制代码ax.plot_surface(x,y,z,rstride=1,cstride=1,alpha=0.5,cmap=cm.coolwarm)

ax.contour(x,y,z,zdir='z',offset=-2)
  • Axes对象调用xlim,ylim,zlim方法设置x,y,z轴取值范围
1
python复制代码ax.set_zlim(-2,2)
  • 最后调用pyplot.show()展示出绘制的图像
1
python复制代码plt.show()
  • 展示的效果如图

image.png

  1. 绘制3D散点图

  • 使用numpy.arange()|numpy.random.randint()准备x,y,z轴的数据
  • Axes对象调用scatter散点图方法绘制散点图
1
2
3
4
5
6
7
8
9
10
python复制代码x = np.arange(0,200)
y = np.arange(0,100)

x,y = np.meshgrid(x,y)

z = np.random.randint(0,200,size=(100,200))

yc = np.arctan2(x,y)

ax.scatter(x,y,z,c=yc,s=50,marker=".")

image.png

  1. 绘制3D折线图

  • 使用np.linspace()和np.sin()对x,y轴数据准备
  • Axes对象调用plot()折线方法绘制折线图
1
2
3
python复制代码x = np.linspace(0,1,100)
y = np.sin(x*2*np.pi)/2+0.5
ax.plot(x,y,zs=0,zdir="z")

image.png

  1. 绘制3D柱状图

1
2
3
4
5
6
7
8
9
10
python复制代码for z in range(0,3):

x = np.arange(1,13)
y = 1000*np.random.rand(12)
color = plt.cm.Set2(random.choice(range(plt.cm.Set2.N)))
ax.bar(x,y,zs=z,zdir="y",color=color,alpha=0.8)

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")

image.png

总结

本期,对matplotlib 模块提供mplot3d库绘制3D图形的步骤进行学习,同时手动实操散点图、折线、柱状图3D图。

对于,绘制3D图形必现要使用Axes3D对象进行绘制,对于复杂的3D图形matplotlib渲染引擎渲染比较差,官方建议使用Mayavi进行处理。

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

本文转载自: 掘金

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

「Rust 重写 sqlite」元数据操作命令

发表于 2021-11-20

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


区分命令

回到项目中,正如我上面提到的,第一件事是能够区分 MetaCommand 和 SQLCommand。可以看到在 main.rs 通过调用 get_command_type(command: &String) 来处理这个问题,它返回一个 rep::CommandType 类型的枚举,有 rep::CommanType::SQLCommand(String) 和 rep::CommanType::MetaCommand(String) 两种选项。这样我就可以很容易地区分这两种类型的输入,并对它们分别采取适当的行动:

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
rust复制代码fn main() -> rustyline::Result<()> {
let _matches = App::new("Rust-SQLite")
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.get_matches();

let config = get_config();
let helper = REPLHelper::default();
let mut repl = Editor::with_config(config);
repl.set_helper(Some(helper));
// 循环接收命令
loop {
let p = format!("rust-lite> ");
repl.helper_mut()
.expect("No helper found")
.colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
// <http://bixense.com/clicolors/>

let readline = repl.readline(&p);
match readline {
Ok(command) => {
repl.add_history_entry(command.as_str());
// Parsing repl::CommandType
match get_command_type(&command.trim().to_owned()) {
CommandType::SQLCommand(_cmd) => {
// process_command -> {tokenizing, parsing and executing}
let _ = match process_command(&command) {
Ok(response) => println!("{}",response),
Err(err) => println!("An error occured: {}", err),
};
}
CommandType::MetaCommand(cmd) => {
// handle_meta_command parses and executes the MetaCommand
let _ = match handle_meta_command(cmd) {
Ok(response) => println!("{}",response),
Err(err) => println!("An error occured: {}", err),
};
}
}
}
Err(ReadlineError::Interrupted) => {
break;
}
Err(ReadlineError::Eof) => {
break;
}
Err(err) => {
println!("An error occured: {:?}", err);
break;
}
}
}

Ok(())

Meta Commands

先看看 meta_command:

首先是枚举类型的定义,为了改善用户体验,我添加了一个 Unknown 选项,以匹配任何尚未定义的MetaCommands。

之后,我们有一个实现 fmt::Display trait的block,它帮助我们定义自定义类型如何被输出到终端上。例如,我们想在 println! 中使用它们。

然后在后面,你会看到另一个 impl block,里面有一个 fn new(),作为我们的 MetaCommand 类型的构造函数。我这么说是因为Rust不是一种面向对象的语言,所以 fn new() 并不像Java等语言中的构造函数,事实上,你可以随心所欲地调用它而不是new。

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
rust复制代码#[derive(Debug, PartialEq)]
pub enum MetaCommand {
Exit,
Help,
Open(String),
Unknown,
}

// 负责将类型翻译成格式化文本
impl fmt::Display for MetaCommand {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MetaCommand::Exit => f.write_str(".exit"),
MetaCommand::Help => f.write_str(".help"),
MetaCommand::Open(_) => f.write_str(".open"),
MetaCommand::Unknown => f.write_str("Unknown command"),
}
}
}

impl MetaCommand {
pub fn new(command: String) -> MetaCommand {
let args: Vec<&str> = command.split_whitespace().collect();
let cmd = args[0].to_owned();
match cmd.as_ref() {
".exit" => MetaCommand::Exit,
".help" => MetaCommand::Help,
".open" => MetaCommand::Open(command),
_ => MetaCommand::Unknown,
}
}
}
// 处理
pub fn handle_meta_command(command: MetaCommand) -> Result<String> {
match command {
MetaCommand::Exit => std::process::exit(0),
MetaCommand::Help => {
Ok(format!("{}{}{}{}",
".help - Display this message\n",
".open <FILENAME> - Reopens a persistent database.\n",
".ast <QUERY> - Show the AST for QUERY.\n",
".exit - Quits this application"))
},
MetaCommand::Open(args) => Ok(format!("To be implemented: {}", args)),
MetaCommand::Unknown => Err(SQLRiteError::UnknownCommand(format!("Unknown command or invalid arguments. Enter '.help'"))),
}
}

本文转载自: 掘金

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

1…263264265…956

开发者博客

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