dream

一个菜鸟程序员的成长历程

0%

安装

直接使用 composer 安装 ES 包就可以了,这里使用官方的 elasticsearch/elasticsearch 这个包。

1
composer require elasticsearch/elasticsearch

安装好以后,创建一个客户端。hosts如果是多个节点的集群,那么可以配置一个二维数组。

1
2
3
4
5
6
7
8
9
10
$hosts = [
'host' => '127.0.0.1',
'port' => '9200',
'scheme' => 'http',
'user' => '',
'pass' => ''
];
$client = ClientBuilder::create() //创建客户端
->setHosts($hosts) //hosts连接地址
->build();

如果想跳过ssl证书校验,可以添加一些curl的参数放进客户端

1
2
3
4
5
6
7
8
$curlParams = [//不校验ssl
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_SSL_VERIFYHOST => 0,
];
$client = ClientBuilder::create()
->setConnectionParams(['client' => ['curl' => $curlParams]]) //设置curl参数
->setHosts($hosts)
->build();

分词

简单的增删改查在 elasticsearch 的文档中有介绍了,就不说了,可以看(https://www.elastic.co/guide/cn/elasticsearch/php/current/_quickstart.html)[文档]。

分词的话隐藏的比较深,文档中没有介绍,他放在了indices这个 namespace 下面。如果看源码,可以在Endpoints/Indices 目录下面发现 Analyze.php 文件,当然了,除了分词,这里面还有其他功能,可以自己看。

这个文件也很简单啊,只有几个函数,就是设置请求的API地址,参数这些。

使用起来是这样的,我们用上面创建好的客户端。

1
2
3
4
5
6
7
$parmas['index'] = 'test' //这个是分词的index,也可以不加,加了请求的API就是 $index/_analyze
//请求体 这个就和你直接写DSL没区别了,参数啥的都一样,可以在 kibana里面试试参数
$parmas['body'] = [
'text' => 'php开发', //要分词的文字
'analyzer' => 'ik_smart', //分词器,可以不写
];
$client->indices()->analyze($params);

扩展

indices函数返回的就是 indices 文件夹的 namespace,对应文件在namespace/IndicesNamespace.php,然后后面的函数就相当于文件名,她会拼接在 indices 后面,像我们上面请求的文件就是indices/Analyze.php。具体的拼接就是在 namespace/IndicesNamespace.php 这个里面做的,有一个函数如下

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
/**
* $params['index'] = (string) The name of the index to scope the operation
* $params['body'] = (array) Define analyzer/tokenizer parameters and the text on which the analysis should be performed
*
* @param array $params Associative array of parameters
* @return array
* @see https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-analyze.html
*/
public function analyze(array $params = [])
{
//获取$params['index'], $params['body']
$index = $this->extractArgument($params, 'index');
$body = $this->extractArgument($params, 'body');

//设置endpoints处理类
$endpointBuilder = $this->endpoints;
//$endpoint 就相当于 indices/Analyze.php 这个文件了
$endpoint = $endpointBuilder('Indices\Analyze');
//设置参数,index, 请求体
$endpoint->setParams($params);
$endpoint->setIndex($index);
$endpoint->setBody($body);
//发起请求
return $this->performRequest($endpoint);
}

$this->endpoints 是一个函数,外面传进来的,函数内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
$this->endpoint = function ($class) use ($serializer) {
//拼接处理类
$fullPath = '\\Elasticsearch\\Endpoints\\' . $class;
//反射获取
$reflection = new ReflectionClass($fullPath);
$constructor = $reflection->getConstructor();
//执行
if ($constructor && $constructor->getParameters()) {
return new $fullPath($serializer);
} else {
return new $fullPath();
}
};

go学习第一章

在go现在的版本里面可以使用go mod来管理依赖了

使用 go mod 意味着不需要设置多个 GOPATH

go mod 对应的环境变量 GO111MODULE 有三个值,默认``

  • on 模块支持,go命令行会使用modules,而一点也不会去GOPATH目录下查auto找
  • off 无模块支持,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
  • auto 只要当前目录或者父目录有go.mod文件,那么就以on的形式工作。

标准输入输出

使用fmt包进行标准输出

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("hello")
}

使用flag 包进行标准输入

有两种方式

  • 一种是传入变量地址的StringVar,第二个参数是输入的变量名,第三个是默认值,第四个是描述
  • 还有一种是String,除了第一个参数不同,是通过返回值接收参数,其他都一样
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"flag"
)
func main() {
var name string
flag.StringVar(&name, "name", "default", "desc")
name = flag.String("name","default","desc")
flag.Parse()
fmt.Println(name)
}

使用的时候go run hello.go -name=123

-name就是你输入的名称,前面要加- 或者 -- 没有或者有三个都是不行的。每个输入默认自带-h 来获取命令帮助,帮助里面会显示描述和默认值

go mod 下的本地包拆分

如果我们想把代码分散到不同的文件中怎么做呢?

假设现在的目录如下

  • main.go
  • test
    • test.go

我们想要在main.go引用test.go的代码

首先在go里面通过首字母大小写决定访问权限首字母大写 = public首字母小写 = protected or private

假设test.go代码如下

1
2
3
4
5
package test //包名和目录保持一致

func Test() {

}

main.go代码如下

1
2
3
4
5
package main

func main() {

}

我们想使用test这个包里面的东西需要使用go mod

执行go mod init name,name 表示当前包的名称,可以随便取

这个时候会生成go.mod文件,假设刚才执行的是go mod init hello,那么我们的go.mod文件如下

1
2
3
module hello

go 1.17

这个时候我们就可以在main.go引入test.go了,main.go代码如下

1
2
3
4
5
6
7
package main

import "hello/test" //引入hello这个包下面的test包

func main() {
test.Test() //这里可以使用test包的Test方法了
}

当然了,包名test也可以和目录test不一致,比如test.go文件内容如下

1
2
3
4
5
package test5 //包名和目录不一致

func Test() {

}

我们在main.go依然可以引入,但是需要更改一下调用,或者加个别名

1
2
3
4
5
6
7
package main

import test5 "hello/test" //别名,不用也可以,不过有的会报错

func main() {
test5.Test() //这里可以使用test5包的Test方法
}

变量

变量的声明有两种方式

  • var 可以使用在任何地方,不可以重复声明
    • var [name] [type]
    • type在变量声明的时候如果有赋值,那么可以省略
    • var [name] = 1
    • var 在外面的声明,可以在使用之后声明
    • var 在局部声明,必须在使用之前声明
  • := 只能使用在函数等代码块里面,当有新的参数在左边声明的时候可以重声明

比如

1
2
3
4
5
var a string
var b = a //b也是string
var a = "123" // 报错,因为a已经声明过了
a, c := "123","456" //不会报错,因为:=可以重声明,但是必须有一个新变量,比如c
a, c := "456", "123" //报错,因为a,c都已经声明过了,都是旧变量

简单指针变量

比如声明一个变量

1
var a string

那么会产生一个内存块

内存地址 变量内容
001

这个时候可以通过&操作符取地址

1
2
println(a) //打印变量内容 空
println(&a) //打印变量内存地址 001

如果我们声明一个指针变量

1
2
var b *string
b = &a //指针变量只能存储内存地址

那么内存块

内存地址 变量内容
001
002 001

这个时候输出,可以通过*操作符取指针的值

  • 第一步先找出指针变量的内容001
  • 第二步将001作为内存地址查询对应地址的内容
1
2
3
println(b)  //输出的是变量b的内容001
println(*b) //输出的是变量b的内容001作为内存地址的内容空
println(&b) //输出的是变量b的内存地址002

变量类型转换和类型断言

可以使用 type 创建新的类型和声明类型的别名

1
type astring = string  //声明一个string类型的别名

这个时候astringstring 这两个类型是完全一样的,没有任何区别

1
2
3
4
5
6
7
8
var a string
func main() {
a = "1234"
as = a //可以赋值
as = astring(a) //可以类型转换
as, ok := interface{}(a).(astring) //可以类型断言
fmt.Println(as, ok, as == a) //可以比较
}

如果是创建新的类型

1
type astring string

这个时候这两个类型没啥关系了,但是因为底层都是string 还是可以进行类型转换的,如果底层类型不是string,那么连转换都不行

1
2
3
4
5
6
7
8
var a string
func main() {
a = "1234"
as = a //不可以赋值
as = astring(a) //可以类型转换
as, ok := interface{}(a).(astring) //类型断言失败
fmt.Println(as, ok, as == a) //不可以比较
}

类型转换

类型转换,比如int8转成int16,但是这种转换只适用于int和int之间,string和string之间转换,还有上面的别名和新类型之间底层类型一致的转换

1
2
3
var b int8
b = 1
int16(b) //直接转换

如果是高类型像低类型转换,那么直接取后面的位数,高位会舍弃,比如

1
2
3
var a int16 //
a = 3000 //这个时候a在计算机存储的二进制 = 0000 1011 1011 1000‬
b := uint8(a) //如果转换成8位int,那么是取后面的8位 1011 1000‬ b = 184

当然了,一个int也是可以转成string的,但是会把int值当成一个Unicode值,如果不是一个Unicode能表示的,那么会显示成乱码,比如

1
2
b := 69
t := string(b) //t = E

类型断言

类型断言,想要判断一个变量是什么类型,就可以使用类型断言。使用之前需要先转成interface类型,interface是所有类型的爸爸。
返回两个值,第一个是断言并转换后的值,第二个值表示是否是这个类型,如果ok = true,那么v=转换后的值,如果ok = false, 那么v = nil(空值)

1
2
var a astring
v, ok := interface{}(a).(astring) //判断a是不是一个astring类型

数组和切片

切片的底层是一个数组,切片是对数组的引用。

  • 数组 [len]string 数组长度固定不可变
  • 切片 []string 切片长度可变,可以看做可变长度的数组

数组和切片都有长度length容量cap的属性

  • 数组的长度和容量都是一样的
  • 切片的长度表示现在数据的长度,容量表示底层数组的长度也就是切片的最大长度

比如下面,可以看到只修改c[0]的值,但是其他的值也变了,因为是修改了底层数组a的值,所以底层数组和其他引用的值都变了。

1
2
3
4
5
6
7
a := [3]int{1,2,3} //3长度的数组
b := a[0,2] //2长度 3容量的切片
c := a[0,1] //1长度 3容量的切片
d := b[0,1] //1长度 3容量的切片
// b c d的底层都是数组a
c[0] = 100
fmt.Println(a,b,c,d) //a [100 2 3] b [100 2] c [100] d [100]

切片的扩容

切片的容量变化,如果切片b现在变成一个5长度的会怎么样呢,底层会进行一个扩容,会创建一个新的底层数组,然后一个新的切片,返回这个新的切片给b。

扩容以后,容量如果小于1024,每次容量会乘以2,比如b的容量3乘以2变成6,如果大于1024,那么每次会乘以1.25,但是计算完以后还会进行一个内存对齐的操作。

字典map

字典是一个hash表,声明方式如下,有着hash的优势,比如key-value是O(1)的复杂度,但是map是无序的,每次遍历的顺序不一定。

1
2
3
4
var m1 map[int]string //key是int,value是string的map,但是这样声明的map值是nil,并且不能赋值
m1[1] = "2" //报错
m2 := map[int]int{1:1,2:2} //key是int,value也是int的map
m3 := make(map[int]int, 5) //创建一个key是int,value也是int,长度为5的map

channel

channel是一个并发安全的类型。channel分为带缓冲区的和不带缓冲区的。声明方式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
)

func main() {
// 示例1。
go func() {
fmt.Println(123)
var ch1 chan int //里面可以传输int的channel,默认值nil,如果这样声明会造成一个永久阻塞的channel,后面的代码不会执行
fmt.Printf("ch1:%s", ch1)
}()

// ch1 <- 1
ch2 := make(chan int, 1) //声明有一个缓冲区的channel
ch3 := make(chan int, 0) //声明不带缓冲区的channel
ch2 <- 1
// ch2 <- 2
// m1["2"] = 3
fmt.Println(<-ch2, ch3)
}

把数据传给channel,使用ch2 <- 数据的方式把数据传给ch2这个channel,channel的数据传递全部都是浅拷贝,下面的例子可以发现,修改s的值会使得s1的值也被修改。

1
2
3
4
5
6
7
8
   ch2 := make(chan []string, 1)
s1 := []string{"1", "2"}
ch2 <- s1
// ch2 <- 2
// m1["2"] = 3
s := <-ch2
s[1] = "34"
fmt.Println(s, s1)

数据接收使用变量 := <- ch2 来接收ch2的数据到一个变量中

1
s := <- ch2

单向channel

单向channel可以限制函数的行为,比如chan<-类型的只能发送数据到channel中,<-chan类型的只能从channel中获取数据。

1
2
3
4
5
6
7
func getChan(ch <-chan) {
//函数里面只能从 ch 这个 channel中获取数据而无法发送数据,这样限制了这个函数里面的行为
}

func setChan(ch chan<-) {
//函数里面只能往 ch 这个 channel中发送数据而无法获取数据
}

函数

go中的函数是一等公民可以作为type类型,可以作为参数,可以作为返回值,可以赋值给变量,可以和nil做比较等等

函数的声明

1
2
3
func name(arg1 int, arg2 int) (r int, err error) {
//....
}

函数的类型,声明一个类型 afunc afunc的底层类型是一个接受一个string参数,返回一个int参数和一个error类型参数的函数,函数签名是函数的参数列表和返回值列表,如果参数列表的类型一致并且返回值列表的参数类型一致就可以认为是一样的函数。

1
2
3
4
type afunc func(string) (int, error) //声明一个类型 afunc afunc的底层类型是一个接受一个string参数,返回一个int参数和一个error类型参数的函数
var a afunc //可以声明一个变量,类型是 afunc 的变量
a = name //可以把函数签名一致的函数赋值给这个函数变量
a(1,2) //等于 name(1,2)

结构体

结构体的声明

1
2
3
4
5
6
7
8
type as struct{ //声明一个名称叫 as 的结构体
a string //as 有 一个string的属性 a
b int //一个int的属性b
}
//声明一个属于as结构体的方法String
func(this as) String() string{
return fmt.Sprintf(this.a) //访问as的属性a
}

从上面的声明可以看出来,可以把struct简单的类比成class,这个as的结构体有两个属性,一个方法

使用结构体

1
2
3
4
5
6
7
8
func main() {
as1 := as{ //初始化as这个结构体
a: "1",
b: 1,
}
as2 := as{} //也可以不初始化
fmt.Println(as1, as2)
}

结构体的组合,也可以类比成class的继承,不过组合比继承更有优势。组合进来以后,asT结构体就拥有了as类的属性和方法,但是由于asT有a,as也有a属性,asT的就把as的覆盖了

1
2
3
4
5
6
7
8
9
10
11
12
13
type asT struct {
a string //覆盖了as的a属性
as //把as组合,嵌入进asT结构体,可以类比成 asT类继承了as类
}
as2 := asT{}
as2.a = "3" //修改的是as2的a属性
as2.as.a = "2" //修改的是as2.as.a属性 可以类比成修改了父类的a属性
as2.String() //可以调用as2.String方法,因为as有这个方法,他组合进来也拥有了这个方法
//这样可以定义asT的String方法,这样的话上面的代码就会访问这个方法了,可以类比成重写了String方法
func(this asT) String() string{
return fmt.Sprintf(this.a) //访问asT的属性a
}
as2.as.String() //就算覆盖了,依然可以这样调用as的String方法

结构体可以组合多个结构体,也可以类比成多继承。但是这样有一个问题,比如组合的两个结构体内有同样名称的属性或者方法就会报错。声明的时候不会报错,只有使用的时候会报错,因为不知道使用哪个,如果指定相应的结构体进行使用就不会报错了。

1
2
3
4
5
6
7
type asD struct{
as
asT
}
as3 := asD{}
as3.a := "3" //报错
as3.as.a := "4" //正常

还有一种方法,比如在新的结构体中定义一个同名的属性,就会覆盖其他的,所以就不会报错了

1
2
3
4
5
6
7
type asD struct{
a int
as
asT
}
as3 := asD{}
as3.a := 3 //正常

还有结构体中的覆盖是通过名称来判断覆盖的,跟数据类型没有关系,方法的覆盖也是一样,跟参数列表和返回值没有关系

结构体中指针的使用

1
2
3
4
5
6
7
8
9
func(this asT) String() string{
this.a = 5 //这里不可以赋值,因为this是一个值类型,这个赋值并不会真正的改变asT结构体的值,只是会改变当前this变量的值而已
return fmt.Sprintf(this.a) //访问asT的属性a
}

func(this *asT) String() string{
this.a = 5 //这里可以赋值,因为this是一个指针类型
return fmt.Sprintf(this.a) //访问asT的属性a
}

这个是有定义的时候有区别,因为在调用的时候go会自动转换,比如

1
asT.String() //如果接受的是一个*asT类型的值,这里go会转换成(&asT).String()的调用

接口

接口是interface和一般语言的接口没啥区别,但是go的接口是一种无侵入式实现,比如下面的代码我们声明了一个ai的接口,声明了一个as的结构体,这个结构体的方法和ai的方法一样,那么就算实现了ai的接口,可以赋值给ai接口类型的变量。

这里需要方法名称方法签名这两个全部一致才算实现了这个接口,还有要注意,*as代表SetName是 *as的方法而不是as的方法,所以我们只能把&as1赋值过去,如果赋值as1会报错。因为as1只有一个GetName方法

接口变量具有三个属性

  • 静态类型 ai
  • 动态类型 赋值时候确定,比如赋值了&as1,那么动态类型就是*as
  • 动态值 也就是&as1
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
package main

import "fmt"

type ai interface { //声明一个名称叫 ai 的接口
SetName(string) //该接口有一个SetName方法
GetName() string //有一个GetName方法
}

type as struct { //声明一个名称叫 as 的结构体
a string //as 有 一个string的属性 a
b int //一个int的属性b
}

//声明一个属于as结构体的方法 GetName
func (this as) GetName() string {
return fmt.Sprintf(this.a) //访问as的属性a
}

//声明一个属于as结构体的方法 SetName
func (this *as) SetName(name string) {
this.a = name
}

func main() {
var ai1 ai
as1 := as{}
ai1 = &as1
fmt.Println(as1, ai1)
}

接口类型的nil值,接口只有声明的时候和赋值nil字面量的时候才是真正的nil值,看下面,输出结果a1是2,因为ai1不是真正的nil,ai1的动态值是nil,但是动态类型是*as,所以ai1不是nil

1
2
3
4
5
6
7
8
9
   var as2 *as //as2是nil
ai1 = as2
var a1 int
if ai1 == nil { //ai1不是nil
a1 = 1
} else {
a1 = 2
}
fmt.Println(as1, ai1, a1)

goroutine

goroutine 是一个 go的用户级线程,也叫协程。

使用的话就是下面这样 go 后面跟上协程需要执行的函数代码。首先启动go程序的时候,会启动一个主进程。然后主进程生成一个主线程来执行go程序的main函数。执行的时候是一个for循环。

  • 执行第一次循环的时候i = 0
  • 然后执行到了go func代码
  • 由go的runtime查找是否有空闲的协程。如果没有那么创建一个协程。
  • 然后把go func的代码放入创建好的协程。
  • 最后把这个包含了go func代码的协程放入协程的等待队列中
  • 直到有空闲的线程,从等待队列中取出一个协程,执行这个协程的代码

可以看到下面这段代码的执行结果,是什么也不会发生。

1
2
3
4
5
6
7
func main() {
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i)
}()
}
}

因为在for循环执行以后goroutine的代码还没有得到执行机会的时候,主线程main函数执行完了。那么这个时候系统的主线程就会关闭了,主进程也会关闭了。所以协程并没有执行。

看下面的代码,增加了定时器,这个时候会输出10个10,因为主线程执行到定时器的时候线程挂起,然后协程就有执行的时间了,但是协程开始执行的时候,for循环已经执行完了,这个时候变量i的值是10,所以10个协程打印出来的变量i的值都是10

1
2
3
4
5
6
7
8
func main() {
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Millisecond * 500)
}

看下面的代码,go func(i int){}(i),增加了入参i int类型,并且在调用的时候把变量i传入了进去,那这个时候呢,执行的结果就是输出0-9的乱序,因为我们无法保证协程的执行顺序,但是由于传了当时的变量i,而go是浅拷贝,所以协程中的变量i的值被固定了。

1
2
3
4
5
6
7
8
func main() {
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println(i)
}(i)
}
time.Sleep(time.Millisecond * 500)
}

for循环

for循环和别的语言一样

1
2
3
for i:=0; i< 10; i++ {
//...
}

还有 range 可以循环数组,切片,map。这里range会复制一个 numbers3 来进行循环,也就是说如果numbers3是数组,那么修改numbers3[0] 的值不会影响到 numbers3[0] 的值,因为数组是值类型。如果是切片那么会影响到。

1
2
3
4
5
6
7
8
9
10
11
12
13
   numbers2 := []int{1, 2, 3, 4, 5, 6}
numbers3 := numbers2[0:len(numbers2)]
maxIndex2 := len(numbers2) - 1
// i v对应key value,也可以只有一个key,没有value for i := range numbers3 {}
for i, v := range numbers3 { //range 后面跟一个切片或者数组,map
if i == maxIndex2 {
numbers3[0] += v
} else {
numbers3[i+1] += v
}
v = 1 //这里的v因为是int类型,所以修改他的值不会影响到numbers3切片里面的值
}
fmt.Println(numbers2, numbers3)

switch

switch语句不需要使用 break 了,因为go的switch只执行一个case,并且case后面可以跟多个结果,用逗号分隔,只要命中一个结果就执行这个case,所以case后面的结果也不能重复。

1
2
3
4
5
6
7
8
   var a int
switch 3 {
case 1, 2:
a = 1
case 3, 4:
a = 2
}
fmt.Println(a) //输出2

如果case后面重复,那么会报错

1
2
3
4
5
6
   switch 3 {
case 1, 2:
a = 1
case 2, 3, 4: //报错
a = 2
}

因为switch结果和case结果会进行判等的,所以他们两个的类型要是一样的,不然也会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   switch 3 {
case 1, 2:
a = 1
case "21", 3, 4: //报错
a = 2
}

var a int
var b uint8 = 3
switch b {
case 1, 2:
a = 1
case 3, 40000: //报错,因为40000 不是uint8能表示的
a = 2
}

var b uint8 = 3
switch 40000 {
case 1, 2:
a = 1
case 3, b: //位置换了也是报错
a = 2
}

bool查询

一个bool查询里面可以包含多个查询子句

must 必须匹配,贡献算分
should 选择性匹配,算分
must_not filter context 查询子句,必须不能匹配,不算分
filter filter context 必须匹配,但是不算分
  • 子查询可以任意顺序出现
  • 可以嵌套多个查询
  • 如果bool查询中,没有must条件,should中必须满足一个查询
  • should是一个数组

查询语句的结构,会对相关度算分产生影响

  • 同一层级下的竞争字段,具有相同的权重
  • 通过嵌套bool查询,可以改变对算分的影响

单字符串 多字段查询

可以通过 boolshould 来实现

1
2
3
4
5
6
7
8
9
10
11
POST test_home/_search
{
"query":{
"bool":{
"should":[
{"match":{"title":"php"}}
{"match":{"body":"php"}}
]
}
}
}

但是这样实现的算分过程可能不是我们想要的,算分过程如下

  • 查询 should 语句中的两个查询
  • 加和两个查询的评分
  • 乘以匹配语句的总数
  • 除以所有语句的总数

我们还可以使用最高算分 dis_max- queries

1
2
3
4
5
6
7
8
9
10
11
POST test_home/_search
{
"query":{
"dis_max":{
"queries":[
{"match":{"title":"php"}}
{"match":{"body":"php"}}
]
}
}
}

但是有的时候,最高评分是一样的,那怎么办呢,可以通过tie_breaker参数来调整

  • 获得最佳匹配语句的评分
  • 将其他匹配语句的评分与 tie_breaker相乘
  • 对以上评分求和并规范化
  • tie_breaker 是一个 0 - 1的浮点数,0代表最佳匹配,1代表所有语句同等重要。

三种场景

  • 最佳字段 best_fields 相当于 dis_max 查询
    • 当字段之间相互竞争,又相互关联。例如 title 和 body 这样的,评分来自最佳字段
  • 多数字段 most_fields
    • 处理英文内容时:一种常见的手段是,在主字段,抽取词干,加入同义词,以匹配更多的文档。相同的文本,加入子字段,以提供更加精确的匹配。其他字段作为匹配文档提高相关度的信号。匹配字段越多则越好
  • 混合字段 cross field
    • 对于某些实体,例如人名,地址,图书,需要在多个字段中确定信息,单个字段只能作为整体的一部分。希望在任何这些列出的字段中找到尽可能多的词

multi_match

极客时间 ES 学习笔记

搜索相关性算分

ES 会对搜索结果的相关性进行一个算分,算分结果放到_score 字段中。

算分是为了排序,ES5之前使用TF-IDF算法进行算分,之后使用BM25算法

词频 (TF)

  • term frequency: 检索词在一篇文档中出现的频率

    • 检索词出现的次数除以文档的总字数
  • 相关性:简单将搜索中每一个词的TF进行想加

    • TF(区块链) + TF(的) + TF(作用)
  • stop word: 没什么作用的词

    • 可能会出现多次,但是他对于相关性并没有什么作用,应该不考虑他的词频, 可以作为一个 stop word

逆文档频率 (IDF)

  • DF:检索词在所有文档中出现的频率

    • 区块链 在相对较少的文档中出现
    • 作用 在相对较多的文档
    • 的 作为 stop word 在大量的文档中出现
  • inverse document frequency: 简单说 = log(存储的全部文档数/检索词出现过的文档数)

  • TF-IDF 就是将 TF 求和变成了加权求和

比如,你ES里总共存储了10万条职位信息, 你检索 php,出现 php 的职位数量是1万,那么IDF = log(10万/1万) = log(10) = 1

  • TF-IDF 就是算出分词后所有词的TF和 IDF 并进行处理,比如区块链的作用 = TF(区块链) * IDF(区块链) + TF(的) * IDF(的) + TF(作用) * IDF(作用)

BM25

BM25对之前的TF-IDF 算法进行了一个优化,当TF的词出现的越来越多的时候,如果是TF-IDF 算法,那么分值会增加很多,而如果是 BM25 算法,则会趋于一个极限。

boosting

boosting可以对算分结果进行影响

正常搜索php首席的结果

1
2
3
4
5
6
7
8
PHP首席架构师
PHP架构师
PHP架构师001
首席软件架构师
首席软件架构师
首席科学家(科研副总经理)
首席科学家(工业传动技术)
商家端资深软件开发工程师(Go/PHP)

如果我们想降低首席这个词在搜索结果中的算分占比,可以使用boosting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET job_ik/_search
{
"query": {
"boosting": { //boosting 关键字
"positive": { //positive 关键字
"match": {
"job_name": "php"
}
},
"negative": { //negative 关键字
"match": {
"job_name": "首席"
}
},
"negative_boost": 0.2 // 0 - 1,降低首席排序,1-100,提高首席排序
}
}
}

降低后的结果

1
2
3
4
PHP架构师
PHP架构师001
商家端资深软件开发工程师(Go/PHP)
PHP首席架构师

如果把negative_boost提升为2,那么结果如下

1
2
3
4
PHP首席架构师
PHP架构师
PHP架构师001
商家端资深软件开发工程师(Go/PHP)

极客时间 ES 学习笔记

term query

term是表达语义的最小单位 ,搜索和利用统计语言模型进行自然语言处理都需要处理 term

特点

  • term level query: term query/range query/ exists query / prefix query / wildcard query
  • 在ES里面,term 查询不做分词,把term查询作为一个整体词汇进行查询,进行一个精确匹配,并对匹配结果进行算分
  • 可以通过 Constant Score 将查询转换成一个 filtering ,避免算分,并利用缓存,提高性能

term 查询

我们现在有数据如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"_index" : "test",
"_type" : "_doc",
"_id" : "ID10032_1194_1634175220_535254",
"_score" : 6.151313,
"_source" : {
"user_id" : 10032,
"consultant_company_id" : 1194,
"type" : 9,
"source" : 1,
"operation_at" : 1634175220,
"create_time" : "2021-10-14 09:33:40",
"update_time" : "2021-10-14 09:33:40",
"login_type" : 3,
"true_name" : "张三",
"id" : "ID10032_1194_1634175220_535254"
}
}

如果我们使用match查询

这里ID10032_1194_1634175220_535254大写id10032_1194_1634175220_535254小写都可以

1
2
3
4
5
6
7
8
GET test_user_action_detail/_search
{
"query": {
"match": {
"id": "ID10032_1194_1634175220_535254"
}
}
}

都可以得到搜索结果

如果使用 term 查询,id10032_1194_1634175220_535254可以查询到数据,但是如果是ID10032_1194_1634175220_535254大写,那么term 查询就查询不到数据了

1
2
3
4
5
6
7
8
9
10
GET test_user_action_detail/_search
{
"query": {
"term": {
"id": {
"value": "id10032_1194_1634175220_535254"
}
}
}
}

原因在于 该 id 字段的 mapping 是 text/keyword

1
2
3
4
5
6
7
8
9
"id" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}

text数据类型会进行分词,也就是大写会被分词成小写。所以text的id数据实际会变成小写id10032_1194_1634175220_535254,而term查询区分大小写,会进行精确匹配,如果只有搜索小写才能搜索出来结果。如果不想进行分词,那么可以使用keyword字段类型搜索,把term查询语句改成下面这样

1
2
3
4
5
6
7
8
9
10
GET test_user_action_detail/_search
{
"query": {
"term": {
"id.keyword": { //这里使用keyword字段查询
"value": "ID10032_1194_1634175220_535254"
}
}
}
}

这样就可以得到结果了,因为keyword不进行分词,所以插入的ID10032_1194_1634175220_535254就还是ID10032_1194_1634175220_535254,不过注意,这样的话小写id10032_1194_1634175220_535254就搜索不到结果了

filter 不进行算分

对于term查询来说,其实没必要算分,因为是精确搜索,那么就可以使用filter查询来避免算分并可以有效利用缓存,可以提升性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET test_user_action_detail/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"id.keyword": {
"value": "ID10032_1194_1634175220_535254"
}
}
}
}
}
}

基于全文查询

  • match query / match phrase query / query string query

  • 特点

    • 索引和搜索都会进行分词,查询字符串先传递到一个合适的分词器,然后生成一个供查询的词项列表
    • 查询时,会先分词,然后对每个词都进行查询,最终将结果进行合并。并为每个文档生成一个算分。
    • 例如查询matrx reloaded 会查询到 matrxreloaded两个词的搜索结果,然后进行合并。

range query

range 可以进行范围查询

1
2
3
4
5
range: {
created_at: {
gt:'1889237773'
}
}

exists query

可以判断值是否存在

1
2
3
exists:{
field:"created_at"
}

处理多值字段

比如有一个数组 [北京市,上海市]

如果我们使用term查询

1
2
3
4
5
6
7
8
9
10
11
GET test_user_action_detail/_search
{
"query": {
"term": {
"city.keyword": {
"value": "北京市"
}
}
}
}

就会搜索出现上面的数据,因为对于数组来说,term 查询是只要包含那么就会搜索出来。

极客时间 ES 学习笔记

nginx

nginx的场景

三个主要场景

  • 静态资源:通过本地文件系统提供服务
  • 反向代理:nginx的强大性能,缓存,负载均衡
  • API服务:openresty

nginx的优点

  • 高并发,高性能
  • 可扩展性好
  • 高可靠性
  • 热部署
  • BSD许可证

nginx的组成

  • nginx二进制可执行文件:由各模块原码编译出的一个文件
  • nginx.conf:控制nginx行为
  • access.log
  • error.log

vim nginx语法高亮

如果想要vim操作nginx的时候有语法高亮,那么只需要把nginx文件夹带的vim配置文件复制到自己的vim配置文件下面就可以了

找到nginx源码目录,注意这里是源码目录,下载的tar包解压出来的目录,而不是编译安装后的目录,下面有一个contrib文件夹,这个文件夹里面有vim文件夹,把这个里面的配置文件都复制一下就好了

1
cp -r nginx/contrib/vim/ ~/.vim/

已安装的nginx 增加模块

首先到达源码目录cd /usr/local/src/nginx1.19

然后开始编译

  • –prefix 参数代表你的nginx安装目录,如果是已安装的nginx,那么这个目录就选择已安装的目录,没有安装的可以随便选想安装到的目录
  • –with 开头的代表你想开启的模块
1
sudo ./configure --with-file-aio --with-http_ssl_module --with-http_v2_module --with-http_realip_module --prefix=/usr/local/src/nginx

然后执行make

1
sudo make

接下来就可以复制nginx源文件了,这里只执行make,不需要执行make install

现在假设你已经在nginx源码目录了

1
2
cd ./objs  //移动到objs目录下
cp nginx /usr/local/src/nginx/sbin/ //把objs目录下的nginx复制到你原来的nginx安装目录下面的sbin目录替换原来的nginx源文件

如果是新安装,那么不需要复制,直接执行 sudo make install 就可以了

配置语法

  • 配置文件由指令和指令块构成
  • 每条指令以;分号结尾,指令与参数间用空格分隔
  • 指令块以{}大括号将多条指令组织在一起
  • include 语句允许组合多个配置文件以提升可维护性
  • 使用 #井号添加注释,提高可读性
  • 使用 $符号作为变量
  • 部分指令的参数支持正则表达式

http配置的指令块

  • http
  • server
  • upstream
  • location

nginx 命令行

  • 帮助 -h
  • 指定配置文件 -c
  • 指定配置指令 -g
  • 指定运行目录 -p
  • 发送信号 -s
  • 测试配置文件语法 -t
  • 打印版本信息 -v -V
1
2
3
4
5
nginx             //启动
nginx -s reload // 重启
nginx -s quit //优雅停止
nginx -s stop //直接停止
nginx -s reopen //重新开始记录日志

热部署

当你需要升级nginx或者增加模块的时候可以使用热部署的方式平滑过渡,不需要重启

查看现在nginx进程的id

1
ps -ef|grep nginx

对现在的nginx进程发送USR2信号进行热部署

1
kill -USR2 id

现在可以看到新的nginx进程和老的nginx进程都存在了,这个时候我们需要让老的nginx进程平滑退出

1
kill -WINCH id

接下来就可以看到老的nginx的worker进程全部平滑退出了,但是老的master进程还在,这是为了防止新的有问题,还可以退回老的

如果接下来新的有问题,那么我们退回老的,发送HUP信号,而不是直接reload

1
kill -HUP id

接下来可以看到老的worker进程都起来了

如果热部署后没有问题的话,那么我们直接删除老的master进程就可以了

1
kill -QUIT id

日志切割

如果我们觉得日志太大可以进行日志切割,一种是发送reopen信号,还有一种是发送USR1信号

1
sudo nginx -s reopen

或者

1
kill -USR1 id

但是注意切割之前要把之前的日志备份一下,不然就会被删除啦

反向代理

反向代理配置在http代码块中

首先通过upstream配置反向代理的服务器

test 是一个名称,可以随便取

1
2
3
upstream test{
server 127.0.0.1:8080 # 上游服务器的地址,可以写多个
}

通过 proxy_pass 进行反向代理

1
2
3
4
5
6
server{
listen 80;
location / {
proxy_pass http://test; # 这里填写上面的upstream名字
}
}

这个时候我们访问80端口实际上访问的是8080端口

反向代理的缓存

反向代理之后还可以在反向代理服务器做缓存

nginx.conf文件的http代码块中设置缓存

1
2
# 设置缓存路径 /tmp/nginxCache 缓存名称 my_cache
proxy_cache_path /tmp/nginxCache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;

接下来在我们的反向代理server/location代码块里面使用这个缓存

1
2
3
4
5
6
location / {
proxy_cache my_cache; # 使用上面定义的缓存
proxy_cache_key $host$uri$is_args$args; # 缓存的key值,根据host uri和参数来做key
proxy_cache_valid 200 304 302 1d; # 缓存200 304 302状态 缓存时间1d = 1天
proxy_pass http://test; # 这里填写上面的upstream名字
}

goaccess 日志可视化

goaccess 是一个c语言写的日志可视化工具

直接编译安装

1
2
3
4
5
6
wget http://tar.goaccess.io/goaccess-1.2.tar.gz
tar -xzvf goaccess-1.2.tar.gz
cd goaccess-1.2/
./configure --enable-utf8
make
make install

然后就可以启动了,直接启动就可以

指定日志文件路径 /usr/local/src/nginx/logs/access.log -o 输出到某个html文件中 –real-time-html 代表实时刷新, –log-format 代表日志格式 ,–daemonize 代表守护进程后台执行 仅在 开启了 –real-time-html 实时刷新有效

1
goaccess /usr/local/src/nginx/logs/access.log -o ~/wwwroot/goaccess.html --real-time-html --log-format=COMBINED --daemonize

这里要注意,如果开启了 –real-time-html 实时刷新,那么会启动一个 socket 进程监听 7890 端口,所以 7890 端口要开放才行

可以配置 nginx 文件访问这个输出的 html 文件了,就可以看到一个可视化界面了

ssl

ssl(Secure Sosckets Layer)
tls(Transport Layer Security)

  • 1995年-ssl3.0
  • 1999 - tls1.0
  • 2006 - tls1.1
  • 2008 - tls1.2
  • 2018 - tls1.3

ssl/tls 运行在 ISO7层模型的表示层中,而http协议运行在表示层的上层应用层。在http的数据传输到表示层以后,ssl/tls就会对数据进行加密

  • 握手
  • 交换秘钥
  • 告警
  • 对称加密的应用数据

tls安全套件

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

  • tls代表tls加密
  • ECDHE 代表秘钥交换算法
  • RSA 代表身份验证算法
  • AES 代表对称加密算法
  • 128 代表强度
  • GCM 代表分组模式
  • SHA256 代表签名 hash 算法

nginx

nginx的场景

三个主要场景

  • 静态资源:通过本地文件系统提供服务
  • 反向代理:nginx的强大性能,缓存,负载均衡
  • API服务:openresty

nginx的优点

  • 高并发,高性能
  • 可扩展性好
  • 高可靠性
  • 热部署
  • BSD许可证

nginx的组成

  • nginx二进制可执行文件:由各模块原码编译出的一个文件
  • nginx.conf:控制nginx行为
  • access.log
  • error.log

vim nginx语法高亮

如果想要vim操作nginx的时候有语法高亮,那么只需要把nginx文件夹带的vim配置文件复制到自己的vim配置文件下面就可以了

找到nginx源码目录,注意这里是源码目录,下载的tar包解压出来的目录,而不是编译安装后的目录,下面有一个contrib文件夹,这个文件夹里面有vim文件夹,把这个里面的配置文件都复制一下就好了

1
cp -r nginx/contrib/vim/ ~/.vim/

已安装的nginx 增加模块

首先到达源码目录cd /usr/local/src/nginx1.19

然后开始编译

  • –prefix 参数代表你的nginx安装目录,如果是已安装的nginx,那么这个目录就选择已安装的目录,没有安装的可以随便选想安装到的目录
  • –with 开头的代表你想开启的模块
1
sudo ./configure --with-file-aio --with-http_ssl_module --with-http_v2_module --with-http_realip_module --prefix=/usr/local/src/nginx

然后执行make

1
sudo make

接下来就可以复制nginx源文件了,这里只执行make,不需要执行make install

现在假设你已经在nginx源码目录了

1
2
cd ./objs  //移动到objs目录下
cp nginx /usr/local/src/nginx/sbin/ //把objs目录下的nginx复制到你原来的nginx安装目录下面的sbin目录替换原来的nginx源文件

如果是新安装,那么不需要复制,直接执行 sudo make install 就可以了

配置语法

  • 配置文件由指令和指令块构成
  • 每条指令以;分号结尾,指令与参数间用空格分隔
  • 指令块以{}大括号将多条指令组织在一起
  • include 语句允许组合多个配置文件以提升可维护性
  • 使用 #井号添加注释,提高可读性
  • 使用 $符号作为变量
  • 部分指令的参数支持正则表达式

http配置的指令块

  • http
  • server
  • upstream
  • location

nginx 命令行

  • 帮助 -h
  • 指定配置文件 -c
  • 指定配置指令 -g
  • 指定运行目录 -p
  • 发送信号 -s
  • 测试配置文件语法 -t
  • 打印版本信息 -v -V
1
2
3
4
5
nginx             //启动
nginx -s reload // 重启
nginx -s quit //优雅停止
nginx -s stop //直接停止
nginx -s reopen //重新开始记录日志

热部署

当你需要升级nginx或者增加模块的时候可以使用热部署的方式平滑过渡,不需要重启

查看现在nginx进程的id

1
ps -ef|grep nginx

对现在的nginx进程发送USR2信号进行热部署

1
kill -USR2 id

现在可以看到新的nginx进程和老的nginx进程都存在了,这个时候我们需要让老的nginx进程平滑退出

1
kill -WINCH id

接下来就可以看到老的nginx的worker进程全部平滑退出了,但是老的master进程还在,这是为了防止新的有问题,还可以退回老的

如果接下来新的有问题,那么我们退回老的,发送HUP信号,而不是直接reload

1
kill -HUP id

接下来可以看到老的worker进程都起来了

如果热部署后没有问题的话,那么我们直接删除老的master进程就可以了

1
kill -QUIT id

日志切割

如果我们觉得日志太大可以进行日志切割,一种是发送reopen信号,还有一种是发送USR1信号

1
sudo nginx -s reopen

或者

1
kill -USR1 id

但是注意切割之前要把之前的日志备份一下,不然就会被删除啦

反向代理

反向代理配置在http代码块中

首先通过upstream配置反向代理的服务器

test 是一个名称,可以随便取

1
2
3
upstream test{
server 127.0.0.1:8080 # 上游服务器的地址,可以写多个
}

通过 proxy_pass 进行反向代理

1
2
3
4
5
6
server{
listen 80;
location / {
proxy_pass http://test; # 这里填写上面的upstream名字
}
}

这个时候我们访问80端口实际上访问的是8080端口

反向代理的缓存

反向代理之后还可以在反向代理服务器做缓存

nginx.conf文件的http代码块中设置缓存

1
2
# 设置缓存路径 /tmp/nginxCache 缓存名称 my_cache
proxy_cache_path /tmp/nginxCache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;

接下来在我们的反向代理server/location代码块里面使用这个缓存

1
2
3
4
5
6
location / {
proxy_cache my_cache; # 使用上面定义的缓存
proxy_cache_key $host$uri$is_args$args; # 缓存的key值,根据host uri和参数来做key
proxy_cache_valid 200 304 302 1d; # 缓存200 304 302状态 缓存时间1d = 1天
proxy_pass http://test; # 这里填写上面的upstream名字
}

goaccess 日志可视化

goaccess 是一个c语言写的日志可视化工具

直接编译安装

1
2
3
4
5
6
wget http://tar.goaccess.io/goaccess-1.2.tar.gz
tar -xzvf goaccess-1.2.tar.gz
cd goaccess-1.2/
./configure --enable-utf8
make
make install

然后就可以启动了,直接启动就可以

指定日志文件路径 /usr/local/src/nginx/logs/access.log -o 输出到某个html文件中 –real-time-html 代表实时刷新, –log-format 代表日志格式 ,–daemonize 代表守护进程后台执行 仅在 开启了 –real-time-html 实时刷新有效

1
goaccess /usr/local/src/nginx/logs/access.log -o ~/wwwroot/goaccess.html --real-time-html --log-format=COMBINED --daemonize

这里要注意,如果开启了 –real-time-html 实时刷新,那么会启动一个 socket 进程监听 7890 端口,所以 7890 端口要开放才行

可以配置 nginx 文件访问这个输出的 html 文件了,就可以看到一个可视化界面了

ssl

ssl(Secure Sosckets Layer)
tls(Transport Layer Security)

  • 1995年-ssl3.0
  • 1999 - tls1.0
  • 2006 - tls1.1
  • 2008 - tls1.2
  • 2018 - tls1.3

ssl/tls 运行在 ISO7层模型的表示层中,而http协议运行在表示层的上层应用层。在http的数据传输到表示层以后,ssl/tls就会对数据进行加密

  • 握手
  • 交换秘钥
  • 告警
  • 对称加密的应用数据

tls安全套件

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

  • tls代表tls加密
  • ECDHE 代表秘钥交换算法
  • RSA 代表身份验证算法
  • AES 代表对称加密算法
  • 128 代表强度
  • GCM 代表分组模式
  • SHA256 代表签名 hash 算法

学堂在线C++程序设计第十四章学习笔记

异常处理

1
2
3
4
5
try{
throw //抛出异常
} catch (异常) {
//异常处理
}

异常接口声明

  • 一个函数显式声明可能抛出的异常,有利于函数的调用者为异常处理做好准备
  • void fun() throw(A,B,C)
  • 若无异常接口声明,则可以抛出任何类型的异常
  • 不抛任何类型异常的函数声明如下:
    void fun() throw()

异常处理的构造与析构

自动的析构

  • 找到一个匹配的catch异常处理后
  • 初始化异常参数
  • 将从对应的try块开始到异常被抛出之间构造(且尚未析构)的所有自动对象进行析构
  • 从最后一个catch处理之后开始恢复执行

标准程序库异常处理

基础

  • exception
  • logic_error 表示可以被预先检测到的异常
    • 这类异常可以避免
  • runtime_error 表示难以被预先检测到的异常

学堂在线C++程序设计第十三章学习笔记

流类库与输入输出

IO流的概念

当程序与外界环境进行信息交换时,存在着两个对象,一个是程序中的对象,另一个是文件对象

是信息流动的抽象,负责在数据的生产者消费者之间建立联系,并管理数据的流动

流对象与文件操作

  • 建立流对象
  • 指定这个流对象与某个文件对象建立连接
  • 程序操作流对象
  • 流对象通过文件系统对所连接的文件对象产生作用

提取与插入

  • 读操作从流中提取
  • 写操作向流中写入

输出流

最重要的三个输出流

  • ostream
  • ofstream
  • ostringstream

预先定义的输出流对象

  • cout 标准输出
  • cerr 标准错误输出,没有缓冲,发送给它的内容立即被输出
  • clog 类似cerr,但是有缓冲,缓冲区满时被输出
1
2
3
4
ofstream fout('123.log');
streamBuf* old = cout.rdbuf(fout.rdbuf());
//cout 输出到123.log文件
cout.rdbuf(old); //恢复输出到标准输出

构造输出流对象

  • ofstream 类支持磁盘文件输出
  • 如果在构造函数中指定文件名,当构造这个文件时该文件时自动打开的
    ofstream fout(‘123.log’);
  • 可以在调用默认构造函数后使用open打开文件
    ofstream fout;
    fout.open(123.log);
  • 打开文件可以指定模式
    ofstream fout(‘123.log’, ios_base::out|ios_base::binary);

文件输出流成员函数的三种类型

  • 与操纵符等价的成员函数
  • 执行非格式化写操作的成员函数
  • 其他修改流状态且不同于操纵符或插入运算符的成员函数

成员函数

  • open
    • 把流与磁盘文件关联
    • 需要指定打开模式
  • put
    • 把一个字符写到输出流
  • write
    • 将内存中的一块内容写到一个文件输出,可以写二进制
  • seekp和 tellp
    • 操作文件流的内部指针
  • close
    • 关闭磁盘文件
  • 错误处理函数
    • 在写到一个流时进行错误处理

向文本文件输出

插入运算符 <<

  • 为所有标准C++数据类型设计的,用于传送一个字节到输出流对象

操纵符(manipulator)

  • 插入运算符和操纵符一起
    控制输出格式
  • 很多操纵符都定义在 ios_base类中,如hex(),头文件中,如setprecision()
  • 控制输出宽度
    在流中放入setw操纵符或调用width成员函数
  • setw和 width只影响紧随其后的输出项,但其他流格式操纵符保持有效直到发生改变
  • dec,oct和 hex操纵符设置输入输出的默认进制

精度

  • 浮点数输出精度的默认值是6
  • 改变精度使用 setprecision操纵符,定义在 头文件中
  • 如果不指定 fixed 或 scientifc,精度值表示有效数字位数
  • 如果设置了 ios_base::fixed 定点形式 或 ios_base::scientifc 科学计数法 ,精度值表示小数点之后的位数

向二进制文件输出

二进制文件流

  • 使用ofstream构造函数中的模式指定二进制
  • 以通常方式构造一个流,然后使用setmode成员函数,在文件打开后改变模式
  • 通过二进制文件输出流对象完成输出
1
2
3
4
5
6
7
int main() {
int b[3] ={912,2456,37890};
ofstream file("date.bat", ios_base::binary);
file.write(reinterpret_cast<char *> (&b), sizeof(b));
file.close();
return 0;
}

向字符串输出

  • 用于构造字符串
  • 功能
    • 支持ofstream类的除了open,close外的所有操作
    • str函数可以返回当前已构造的字符串
  • 典型应用
    • 将数值转换为字符串
1
2
3
ostringstream os;
os << 123;
string s = os.str();

输入流

重要的输入流类

  • istream 最适用于顺序文本输入,cin是他的实例
  • ifstream 支持磁盘文件输入
  • istringstream 字符串输入

构造输入流对象

  • 如果在构造函数中指定文件,在构造对象时自动打开
    ifstream ifs(‘data.bat’);
  • 调用默认构造函数后使用open打开
    ifstream ifs;
    ifs.open(‘data.bat’);
  • 打开文件时可以指定模式

输入流相关函数

  • open 把输入流和文件关联
  • get 函数的功能与提取运算符(>>)很像,主要的不同点是get函数在读入数据时包括空白字符
  • getLine 的功能是读取多个字符,并且可以指定终止字符,读取完成后,从读取内容中删除终止字符
  • read 函数从一个文件读字节到内存中的指定区域,由长度确定要读的字节数。当遇到文件结束或在文本模式文件中遇到文件结束标记字符时结束读取。可以读取二进制。
1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
int b[3] ={912,2456,37890};
ofstream file("date", ios_base::binary);
file.write(reinterpret_cast<char *> (b), sizeof(b));
file.close();
int i2;
ifstream mfile("date", ios_base::binary);
mfile.seekg(2 * sizeof(int));
mfile.read(reinterpret_cast<char *>(&i2), sizeof(int));
cout << i2 <<endl;
mfile.close();
return 0;
}

字符串输入

用于从字符串读取数据

输入输出流

输入输出流都是从iostream派生的

  • fstream
  • stringstream

数据库系统原理第十三节

数据库安全和保护

安全性与访问控制

数据库安全:指保护数据库以防止不合法的使用而造成数据泄露,更改或破坏,所以安全性对于任何一个DBMS来说都是至关重要的

  • 身份验证
  • 数据库用户权限确认

查看用户

1
select user from mysql.user

创建用户

1
create user user[identified by [password]'password']

create user ‘test‘@’%’ identified by ‘123’

create user ‘tset‘@’%’ identified by password ‘jdkfjalkdjskflajsdfa’

删除用户

1
drop user user

drop user ‘test‘@’%’

修改用户

1
rename user old_user to new_user

修改密码

1
set password for 'test'@'%' = PASSWORD('pass')

账号权限管理

使用grant 为用户授权

1
2
3
4
5
grant
priv_type[column]
on [object_type] priv_level
to user
[with grant option]

grant select (id, name)
on test.test_table
to test@’localhost’

收回用户权限

1
2
3
4
revoke 
priv_type
on [object_type]
from user

revoke select on test.test_table from ‘test‘@’localhost’

事务与并发控制

所谓事务是用户定义的一个数据操作序列,这些操作可作为一个完整的工作单元,要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务中的操作一般是对数据的更新操作,包括增删改

begin transaction开始
commit 或 rollback结束

事务的特征

  • A 原子性
  • C 一致性
  • I 隔离性
  • D 持续性
并发操作问题
  • 丢失更新
  • 不可重复读
  • 读‘脏’数据
封锁

封锁 是最常用的并发控制技术

基本思想:需要时,事务通过向系统请求对它所希望的数据对象加锁,以确保它不被非预期改变