JAVA-spring-IOC
spring
IOC控制反转
这里先说一下IOC
,再说IOC
在spring
框架中的使用。
IOC的概念
IOC
这个缩写有很多意思,比如
- 智慧城市智能运行中心(IOC)
- 奥林匹克运动的领导机构
但是呢,我们这里说的是面向对象编程中的一种设计原则
。他的全称是Inversion Of Control
即控制反转。这里有两个单词控制
和反转
。这两个单词单独拿出来会发现,都缺少主语。比如
- 谁控制了谁?
- 什么东西发生反转了呢?
控制
这里说一下第一个问题,谁控制了谁呢?看下面的代码
1 |
|
很显然,在这里,Person类的person对象的一切都控制在main函数里面。main函数创建它,使用它,销毁它。所以在当前上下文中,main控制了person。
反转
上面的写法main函数控制了person对象,这是一种紧耦合的关系,如果person发生了改变,我们就需要改变main。反转
是控制权的反转。现在person的控制权在main这里,我们将它反转一下。不在用main控制它。那么我们加一个简单工厂看一下呢。
1 |
|
这里可以看到person的控制权转交给了Factory
工厂,而main只有使用权了。当然了作为示例代码,这里的控制只做了创建。
现在,由main控制person改为了Factory控制person。如果person发生了改变,我们只需要改变Factory,而不需要动业务逻辑。
当然了,这种程度的解耦依然不够,因为main还是和Factory有耦合关系,他还控制了Factory。我们可以扩大这个简单工厂,扩大后的简单工厂就不再是工厂了,而叫做容器
。我们把所有的控制权都交给容器,让容器控制所有的类,对象。而在使用的时候我们去告诉容器我们要使用哪个对象,让容器给我们提供就可以了。
DI
依赖注入 DI
全称Dependency Injection
就可以实现我们告诉容器我们需要的对象,然后容器把对象注入给我们的功能。
具体的依赖注入实现方式每个语言,每个框架可能都不一样,这里以spring为例
1 | public class main{ |
可以看到,我们通过@Autowired
注解告诉容器,我们需要一个Person类型的对象,然后让容器把这个对象注入到我们的person属性中。这里仅做示例使用,具体情况请以实际开发中为准,实际开发中应使用接口类型。
容器中的实现方式就类似刚才工厂中的,假设你需要Person类型的对象,就new Person
返回,当然了,要更加复杂,比如可以根据名称来反射创建对象,可以更好的管理对象的生命周期,可以实现单例对象等等。
spring中的IOC
Spring
中的IOC实现也是通过容器,但是怎么把类注入到容器中呢,也就是怎么告诉容器,你需要实例化哪些类呢?有两种方式,一种是XML配置方式
,一种是注解方式
。
XML配置方式
先创建一个XML文件,比如:
touch bean.xml
接下来编辑它。
vim bean.xml
无参数构造方式
重点在这个配置,通过bean
这个标签来告诉容器我要把哪些类注册到容器中,其中id就是注册后的唯一标识,我们获取的时候也可以通过指定id来从容器中获取对象。而class
是告诉容器,我们具体要注入的类的路径。但是这时候没有指定参数,也就是说类似于Person person = new Person()
这样的注册,需要有无参构造器。
1 | <?xml version="1.0" encoding="UTF-8"?> |
使用的时候需要借助于Spring
提供的容器获取,可以看一下效果是一样的。
1 | public class main{ |
构造器构造参数方式
同样在上面的XML文件中进行修改,但这次我们需要加上参数,并且告诉容器是通过构造器来构造参数的,而不是set
的方式。
我们只需要在原来的bean
标签中,加入我们要传给构造器的参数就可以了。
使用constructor-arg
标签,name
就是参数名,value
是对应的值。
1 | <?xml version="1.0" encoding="UTF-8"?> |
我们还需要改造一下原来的Person
类,增加一个构造函数.
1 | class Person{ |
看一下现在的main函数
1 | public class main{ |
但是如果我们需要构造一个其他类的对象作为参数该怎么配置呢,毕竟我们总不能在value
上面写new Class()
吧哈哈。但是我们知道一个bean
就是一个对象,那我们可以传一个bean
进来就可以了。
来看一下配置方式.不再使用value
了,因为value
只能传基本类型这些,而其他的对象需要使用ref
来传参。ref
的值就是其他bean
的id
。
1 | <?xml version="1.0" encoding="UTF-8"?> |
同样需要改造一下构造函数,增加一个avatar
参数。
1 | class Avatar{ |
执行一下main
函数。
1 | public class main{ |
set方式传参
除了通过构造器传参,我们还可以写set
函数来传参,比如setName
和setAvatar
。
1 | class Avatar{ |
修改XML配置文件,不在使用constructor-arg
标签,而是换成property
。不过除了标签名变了,其他的属性name
,value
,ref
都是不变的。
1 |
|
执行一下main
函数。
1 | public class main{ |
自动注入
可以通过配置XML文件来使用自动注入,就不需要手动增加<property name="url" ref="avatar"></property>
或<constructor-arg name="avatar" ref="avatar"></constructor-arg>
的标签了,只需要配置一个属性autowire
就可以了。但是这种的只适用于注入其他的bean
。
autowire
只有两个值,一个是byName
,是通过bean的名称进行注入,比如你的属性名是avatar
,就会查找id=avatar
这个类。还有一个是byType
,是通过bean的类型进行注入,比如类型是Avatar
,那么就会查找class=Avatar
的bean进行注入。
1 | <?xml version="1.0" encoding="UTF-8"?> |
XML读取外部配置文件
通过XML可以读取外部的配置文件,这样的话像数据库,redis连接这些就可以把host
,name
,password
这些写到外部的配置文件中。
配置文件使用.properties
后缀。比如spring.properties
。
增加一个配置文件spring.properties
。
1 | spring.person.name=tony |
修改XML直接从配置中读取person.name
来注入。读取的时候还需要在XML中增加context
命名空间。并通过context命名空间来读取配置文件
1 | <?xml version="1.0" encoding="UTF-8"?> |
注解方式
注解方式要比XML
方式简单的多,其中原理就是不再手动配置,而是通过注解告诉Spring
我是一个bean
。快来注册我吧。
主要是这4个注解告诉Spring
- @Component 单纯的说我是一个
bean
- @Service 和上面的一样,不过一般用在service类中,更加语义化
- @Controller 和上面的一样,一般用在controller类中
- @Repository 我也是一个
bean
接下来我们告诉Spring
,你需要扫描出所有带上面注解的类,把他们注册到容器中。这一步需要修改XML文件,需要配置<context:component-scan>
标签,并且通过base-package
属性告诉Spring
我们要扫描哪个目录
1 |
|
在类上面增加注解
1 |
|
还可以自己指定扫描哪些注解,通过context:include-filter
标签来指定。type
类型写注解,expression
指定扫描哪个注解。把标签放在context:component-scan
这个里面就可以了。还需要在context:component-scan
标签中指定,禁用默认的扫描方式。指定use-default-filters
的属性为false.
1 | <?xml version="1.0" encoding="UTF-8"?> |
还可以排除一些注解不进行扫描,通过context:exclude-filter
标签来指定。type
同样写注解,expression
指定排除的注解。
1 | <?xml version="1.0" encoding="UTF-8"?> |
把类注册到容器中以后,我们还需要在使用的时候告诉容器,我们需要从容器中获取这个类,有5个注解
- @Autowired Spring提供的,基于类型注入的,可以放在setter方法上
- @Qualifier Spring提供的,基于名称注入的,一般和@Autowired配合使用来通过value参数指定名称
- @Resource Java提供的,可以基于类型或名称注入的,可以通过name参数来指定名称,可以放在setter方法上
- @RequiredArgsConstructor lombok提供的,基于类型注入,通过增加一个构造函数来注入。
- @Value Spring提供的,注入基本类型的注解,一般用来从配置文件取值。
@RequiredArgsConstructor
是lombok提供的,兼容性较差,像写单元测试的时候就用不了,它会给你的类增加一个构造方法,而且只会给final
类型的属性进行注入。
1 |
|
这个时候可以编译完以后查看.class
文件,看到的是这样的
1 |
|
@Autowired
是spring提供的,在spring中不管是写业务还是写单元测试都可以使用,它可以放在要注入的属性上面,也可以放在setter方法上面。使用他的时候不需要final
修饰。
1 |
|
@Qualifier
注解配合@Autowired
使用,比如我们有一个头像的接口
1 |
|
这个时候我们在注入的时候如果只根据IAvatar
来注入,容器就不知道我们需要哪个实现类了,所以我们需要指定类名.
1 |
|
@Resource
更像是上面两个的合体,并且是由java提供的。也是可以放在属性和setter上面,并且不需要final修饰。
1 |
|
同样的,如果我们有多个实现类,需要指定可以通过它的name
参数来指定。比如
1 |
|
@Value
可以注入基本类型,比如字符串这种,但是更多的是从配置文件中取值。比如
1 | @Component |
高等数学笔记
高等数学笔记
$y = x^(m/n) 相当于 y^n = x^m$
三角函数
- sinx,tanx,cotx,cscx是奇函数
- cosx,secx是偶函数
- tanx = sinx/cosx
- cotx = cosx/sinx
- $cos^2x + sin^2x = 1$
- $1 + tan^2x = sec^2x$
- $cot^2x + 1 = csc^2x$
- $cos^2x = (1 + cosx) / 2$
- $sin^2x = (1 - cosx) / 2$
1 | sin(a+b) = sina cosb + cosa sinb |
单位圆上
1 | sinx = y/r |
余弦定理
$$c^2 = a^2 + b^2 -2ab * cosx$$
反函数
- $sec^-1 X = cos^-1 (1/X)$
- $csc^-1 X = sin^-1 (1/x)$
- $cot^-1 X = π/2 - tan^-1X$
对数
- ln(a * b) = lna + lnb
- ln(a / b) = lna - lnb
- lnx^n = n * lnx
- ln(ⁿ√x)=lnx/n
- lne = 1
- ln1 = 0
- logab = logcb / logca
- a^x = logaX = e^xlna
- e^lnx = x
- lne^x = x
1 | lnx = 3t+5 |
幂函数
- a^m * a^n = a^m+n
- a^m / a^n = a^m-n
- (a^m)^n = a^mn
- (a^m * a^n)^p = a^mp * b^np
求切线方程
公式 y-y0=m(x-x0),m为斜率,也就是导数。代入点到x0,y0处求方程,比如y=2^x在(0,1)点的切线方程
1 | y = 2^x的导数为ln2 * 2^x,x = 0代入为ln2 |
导数
四则运算
1 | d/dx (a+b) = d/dx a + d/dx b |
链式法则
d/dx f(g(x)) = d/dx f(g) * d/dx g(x)
隐函数微分法
对于不像y=2x
这种直接的函数。比如x^2 + y^2 = 1
这种函数,可以直接对每一项求导。在使用链式法则就可以得到y的导数
1 | x^2 + y^2 = 1 对每一项求导后 x^2 = 2x, y^=2y , 1 = 0,因为y是函数,在对y使用链式法则,得: |
对数微分法
常见导数
- 常数导数为0
- $sinx = cosx$
- $cosx = -sinx$
- $tanx = sec^2x$
- $1/x = -1/x^2$
- $x^a = a*x^a-1$
- $a^x = lna(a^x)$
- $lnx = 1/x$
线性近似
f(x) = f(x) + d/dx f(x) (x - x0)
当x=0时:
f(x) = f(0) + d/dx f(0) * x
lnx的线性近似,当x = 1时:
lnx = ln1 + d/dx ln1 (x - 1)
lnx = 0 + 1 * (x - 1) = x - 1
当x = 0时:
1 | lnx = ln(1 + x) = 1 + x - 1 = x |
二阶近似
f(x) = f(x) + d/dx f(x) (x - x0) + f(x)’’/2 * (x - x0)^2
当x=0时:
f(x) = f(0) + f(0)’ * x + f(0)’’/2 * x^2
1 | sinx = x |
曲线构图
- if f’ > 0, f 是递增的
- if f’ < 0, f 是递减的
- if f’’ > 0, f’ 是递增的
- if f’’ < 0, f’ 是递减的
if f(x0)’ = 0, 则 x0 为临界点, y0 = f(x0) 为临界点值 。
if f(x0)’’ = 0,则 x0为 拐点。
画图
- 描点
- 找出不连续的点
- 找出最远端的点
- 找出一些简单的点
- 求出导数为0的点
- 标出临界点的值
- 判断f’在每个区间的正负性
- 判断f’’的正负性,以判断凹凸性
- 求出f0’’,算出拐点
- 组合所有信息
最大最小值
只需要求出临界点,最远端的点和不连续的点就可以找出最大最小值
牛顿迭代法
用来求函数f(x)在x轴上的交点x,对y点做一切线,切线交于X轴的点为X1,求出X1点,并对X1点的y点做切线交于X轴为X2点,不断重复,求出X点
Xn+1 = Xn - f(Xn)/f’(Xn)
x^2 = 5
x = 根号5
X1 = X0 - (X0^2 - 5/2X0)
X1 = X0 - 1/2 * X0 + 5/2X0
X1 = 1/2 * X0 + 5/2X0
X点的误差在
E1 = |X - X1|
E2 = |X - X2|
En = |根号5 - Xn-1|
E2 约等于 E1^2
f’不能太小 f’’不能太大并且X0要在X的附近
中值定理
(f(b) - f(a)) / (b - a) = f(c)’ 要求x在a < x < b 之间可微,在a <= x <= b之间连续
比如:一辆车从北京到上海,在路上,一定有一段时间的速度等于平均速度
如果f’ > 0 则 f 增长
如果f’ < 0 则 f 递减
如果f’ = 0 则 f 是常数
重要不等式
e^x > 1+x
e^x > 1+x+1/2*x^2
微分
y = f(x) 的微分记作 dy = f(x)’dx
下面的例子,求解出来是fx = y + dy,其实就是线性近似 fx = fa + f’(x - a), x - a其实就是dx,f’ * dx就是dy,fa就是y
1 | 例子1 |
反导数(不定积分)
一阶导数微分的解就是函数,二阶导数微分的解就是一阶导数。式子 f’ = f + C
G(x) = 积分 g(x) dx, Gx 就是 gx 的反导数
积分sinx dx = -cosx,因为 -cosX的一阶导数是 sinX所以 积分sinX * dx = -cosX
不定积分
的不定就是可以在后面加上一个常数C,也就是
1 | Gx = 积分 sinX * dx = -cosX + C也成立 |
重要积分
- x^a的不定积分 = (1/a+1 * X ^ a+1) + C 当 a 不等于 - 1时成立,因为a = -1分母为0
- 1/X的不定积分 = (ln|X|) + C
- sec2X 的不定积分 = tanX + C
- 1/根号 1-X^2 的不定积分 = sin-1X + C
- 1/1+X^2的不定积分 = tan-1X + C
积分换元法
1 | 例子1 |
提前猜测
例子2
1 | 求解 e^6x 的积分 |
例子3
1 | 求 x * e^-x^2的积分 |
例子4
1 | 求 sinx cosx的积分 |
高级猜测
例子4 求 (d/dx + x) * y = 0
1 | (d/dx + x) * y = 0 |
分离变量法
1 | dy/dx = f(x) * g(y) = -x * y |
定积分
几何意义是求函数曲线下的面积
- 划分成多个矩形 所有矩形的底边一样长,都是b/n
例子1 y = x^2 的定积分 a = 0, b = n
1 | 划分成多个矩形后,第一个矩形的面积 = 底 * 高 = b/n * f(x) = b/n * (b/n)^2 |
定积分
- x^2 = b^3/3
- x = b^2/2
- 1 = b
微积分第一基本定理
if F(x)’ = f(x) , than 从a到b f(x) dx的定积分 = F(b) - F(a) = b的积分 - a的积分
例子1 x^2
1 | 从a到b x^2 dx 的定积分 = F(b) - F(a) = b^3/3 - a^3/3 |
运算法则
- 积分(fx + gx) = 积分fx + 积分gx
- 积分(c * fx) = c * 积分fx
- 如果 a < b < c 则 a到b的积分 + b到c的积分 = a到c的积分
- a到a的积分 = 0
- a到b的积分 = -(b到a的积分)
- 如果 fx <= gx,那么从a到b fx的积分 <= gx的积分 a < b
微积分第二基本定理
if f 是连续的函数,并且 G(x) = 从a到x的积分 f(t) dt, than G(x)’ = f(x)
平均公式
1/(b - a) * 从a到b的积分f(x) dx
加权平均公式
从a到b的积分 f(x) w(x) dx / 从a到b的积分 w(x) dx
圆盘法
先求一个圆盘的体积,也就是 面积 * 高 = πr^2 * dx,然后积分
壳层法
先求竖着的圆柱的体积,绕一圈在展开变成长方体,求体积就是 长 * 宽 * 高 = 圆的周长 * dx * f(x),然后积分
数值积分
三角替换
- tanx的积分 = ln(cosx) + C
- secx的积分 = ln(secx + tanx) + C
例题1
求secX的4次方的积分
1 | 因为 sexX^2 = 1 + tanX^2,所以secx^4 dx的积分 = (1 + tanx^2)sexX^2 dx的积分 |
例题2
1/(x^2根号下1+x^2)的积分
例题3
tan (arc cscx) = 1/ (根号x^2 - 1)
被积函数 | 三角替换 | 结果 |
---|---|---|
根号下a^2 - x^2 | x = acosx or y = asinx | asinx or acosx |
根号下a^2 + x^2 | x = atanx | asecx |
根号下x^2 - a^2 | x = asecx | atanx |
例题4
dx/根号x^2+4x
部分分式
如果分子项数 < 分母项数,可以用掩盖法
- 对分母因式分解成 x/(x+1)(x-2) 的形式
- 设置未知数 变成 A/x+1 和 B/x-2
- 掩盖法解A,B,先同乘以一个分母 比如 x+1 则变成 A + B/(x-2) * (x + 1),令x = -1则 B这项为0从而解出A,同理解出B
如果分子项数 >= 分母项数, 用直接除法变成 分子项数 < 分母项数的形式
常用积分
被积函数 | 结果 |
---|---|
lnx的积分 | xlnx - x + C |
(lnx)^2 | x(lnx)^2 - 2(xlnx - x) + C |
tanx的积分 | ln(cosx) + C |
cotx的积分 | ln(sinx) + C |
secx的积分 | ln(secx + tanx) + C |
php实现归并排序算法
php实现归并排序算法
归并排序算法的复杂度是O(nlogn)。
代码如下,完整代码在github上面,只需要clone下来执行composer install
然后执行 php artisan test:mergeSort
就可以看到结果了
1 | /** |
归并排序原理
归并排序和快排刚好相反,是先将整个数组左右打散,然后在逐一合并进行排序,最终完成整个数组的排序,排序示意图如下:
首先将整个数组左右打散,变成单个元素,因为单个元素可以被认为是有序的。
对应代码
1 | if (($hi - $lo) < 2) return [$a[$lo]]; |
接下来对左右两个有序数组进行排序,假设有一个数组$a分成了两个数组[3,4] [2,8],逐一比较,3and2,取出来2然后3and8取出来3然后4and8取出来4,最后取出来8,对应代码:
1 | $lb = $mi - $lo; //$b数组的边界 |
示意图如下:
无序数组去重算法
无序数组去重算法
无序数组去重算法的复杂度是O(n2)。
代码如下,首先进行外层循环,复杂度O(n),然后查找这个元素之前的元素中有没有重复的,复杂度O(n),如果有就删除,复杂度O(1),没有就下一个元素,复杂度O(1)。加起来复杂度O(n2)。
完整代码在github上面,只需要clone下来执行composer install
然后执行 php artisan test:unsortDeduplicate
就可以看到结果了
1 | /** |
有序数组去重算法
有序数组去重算法
有序数组去重算法的复杂度是O(n)。
代码如下,只进行一次循环,复杂度O(n)
完整代码在github上面,只需要clone下来执行composer install
然后执行 php artisan test:sortDeduplicate
就可以看到结果了
1 | /** |
PHP-ES包分词功能实现
安装
直接使用 composer 安装 ES 包就可以了,这里使用官方的 elasticsearch/elasticsearch
这个包。
1 | composer require elasticsearch/elasticsearch |
安装好以后,创建一个客户端。hosts如果是多个节点的集群,那么可以配置一个二维数组。
1 | $hosts = [ |
如果想跳过ssl证书校验,可以添加一些curl的参数放进客户端
1 | $curlParams = [//不校验ssl |
分词
简单的增删改查在 elasticsearch 的文档中有介绍了,就不说了,可以看(https://www.elastic.co/guide/cn/elasticsearch/php/current/_quickstart.html)[文档]。
分词的话隐藏的比较深,文档中没有介绍,他放在了indices
这个 namespace 下面。如果看源码,可以在Endpoints/Indices
目录下面发现 Analyze.php
文件,当然了,除了分词,这里面还有其他功能,可以自己看。
这个文件也很简单啊,只有几个函数,就是设置请求的API地址,参数这些。
使用起来是这样的,我们用上面创建好的客户端。
1 | $parmas['index'] = 'test' //这个是分词的index,也可以不加,加了请求的API就是 $index/_analyze |
扩展
indices函数返回的就是 indices 文件夹的 namespace,对应文件在namespace/IndicesNamespace.php
,然后后面的函数就相当于文件名,她会拼接在 indices 后面,像我们上面请求的文件就是indices/Analyze.php
。具体的拼接就是在 namespace/IndicesNamespace.php
这个里面做的,有一个函数如下
1 | /** |
$this->endpoints 是一个函数,外面传进来的,函数内容如下
1 | $this->endpoint = function ($class) use ($serializer) { |
go学习第一章
go学习第一章
在go现在的版本里面可以使用go mod
来管理依赖了
使用 go mod
意味着不需要设置多个 GOPATH
了
go mod
对应的环境变量 GO111MODULE
有三个值,默认auto
- on 模块支持,go命令行会使用modules,而一点也不会去GOPATH目录下查找
- off 无模块支持,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
- auto 只要当前目录或者父目录有
go.mod
文件,那么就以on的形式工作。
标准输入输出
使用fmt
包进行标准输出
1 | package main |
使用flag
包进行标准输入
有两种方式
- 一种是传入变量地址的
StringVar
,第二个参数是输入的变量名,第三个是默认值,第四个是描述 - 还有一种是
String
,除了第一个参数不同,是通过返回值接收参数,其他都一样
1 | package main |
使用的时候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 | package test //包名和目录保持一致 |
main.go
代码如下
1 | package main |
我们想使用test
这个包里面的东西需要使用go mod
了
执行go mod init name
,name
表示当前包的名称,可以随便取
这个时候会生成go.mod
文件,假设刚才执行的是go mod init hello
,那么我们的go.mod
文件如下
1 | module hello |
这个时候我们就可以在main.go
引入test.go
了,main.go
代码如下
1 | package main |
当然了,包名test
也可以和目录test
不一致,比如test.go
文件内容如下
1 | package test5 //包名和目录不一致 |
我们在main.go
依然可以引入,但是需要更改一下调用,或者加个别名
1 | package main |
变量
变量的声明有两种方式
- var 可以使用在任何地方,不可以重复声明
- var [name] [type]
- type在变量声明的时候如果有赋值,那么可以省略
- var [name] = 1
- var 在外面的声明,可以在使用之后声明
- var 在局部声明,必须在使用之前声明
- := 只能使用在函数等代码块里面,当有新的参数在左边声明的时候可以
重声明
比如
1 | var a string |
简单指针变量
比如声明一个变量
1 | var a string |
那么会产生一个内存块
内存地址 | 变量内容 |
---|---|
001 | 空 |
这个时候可以通过&
操作符取地址
1 | println(a) //打印变量内容 空 |
如果我们声明一个指针变量
1 | var b *string |
那么内存块
内存地址 | 变量内容 |
---|---|
001 | 空 |
002 | 001 |
这个时候输出,可以通过*
操作符取指针的值
- 第一步先找出指针变量的内容001
- 第二步将001作为内存地址查询对应地址的内容
1 | println(b) //输出的是变量b的内容001 |
变量类型转换和类型断言
可以使用 type
创建新的类型和声明类型的别名
1 | type astring = string //声明一个string类型的别名 |
这个时候astring
和 string
这两个类型是完全一样的,没有任何区别
1 | var a string |
如果是创建新的类型
1 | type astring string |
这个时候这两个类型没啥关系了,但是因为底层都是string 还是可以进行类型转换的,如果底层类型不是string,那么连转换都不行
1 | var a string |
类型转换
类型转换,比如int8转成int16,但是这种转换只适用于int和int之间,string和string之间转换,还有上面的别名和新类型之间底层类型一致的转换
1 | var b int8 |
如果是高类型像低类型转换,那么直接取后面的位数,高位会舍弃,比如
1 | var a int16 // |
当然了,一个int也是可以转成string的,但是会把int值当成一个Unicode
值,如果不是一个Unicode
能表示的,那么会显示成乱码,比如
1 | b := 69 |
类型断言
类型断言,想要判断一个变量是什么类型,就可以使用类型断言。使用之前需要先转成interface
类型,interface
是所有类型的爸爸。
返回两个值,第一个是断言并转换后的值,第二个值表示是否是这个类型,如果ok = true,那么v=转换后的值,如果ok = false, 那么v = nil(空值)
1 | var a astring |
数组和切片
切片的底层是一个数组,切片是对数组的引用。
- 数组 [len]string 数组长度固定不可变
- 切片 []string 切片长度可变,可以看做可变长度的数组
数组和切片都有长度length
和容量cap
的属性
- 数组的长度和容量都是一样的
- 切片的长度表示现在数据的长度,容量表示底层数组的长度也就是切片的最大长度
比如下面,可以看到只修改c[0]的值,但是其他的值也变了,因为是修改了底层数组a的值,所以底层数组和其他引用的值都变了。
1 | a := [3]int{1,2,3} //3长度的数组 |
切片的扩容
切片的容量变化,如果切片b
现在变成一个5长度的会怎么样呢,底层会进行一个扩容
,会创建一个新的底层数组,然后一个新的切片,返回这个新的切片给b。
扩容以后,容量如果小于1024,每次容量会乘以2,比如b的容量3乘以2变成6,如果大于1024,那么每次会乘以1.25,但是计算完以后还会进行一个内存对齐的操作。
字典map
字典是一个hash表,声明方式如下,有着hash的优势,比如key-value是O(1)的复杂度,但是map是无序的,每次遍历的顺序不一定。
1 | var m1 map[int]string //key是int,value是string的map,但是这样声明的map值是nil,并且不能赋值 |
channel
channel是一个并发安全的类型。channel分为带缓冲区的和不带缓冲区的。声明方式如下
1 | package main |
把数据传给channel,使用ch2 <- 数据
的方式把数据传给ch2这个channel,channel的数据传递全部都是浅拷贝,下面的例子可以发现,修改s的值会使得s1的值也被修改。
1 | ch2 := make(chan []string, 1) |
数据接收使用变量 := <- ch2
来接收ch2的数据到一个变量中
1 | s := <- ch2 |
单向channel
单向channel可以限制函数的行为,比如chan<-
类型的只能发送数据到channel中,<-chan
类型的只能从channel中获取数据。
1 | func getChan(ch <-chan) { |
函数
go
中的函数是一等公民
可以作为type类型,可以作为参数,可以作为返回值,可以赋值给变量,可以和nil做比较等等
函数的声明
1 | func name(arg1 int, arg2 int) (r int, err error) { |
函数的类型,声明一个类型 afunc afunc的底层类型是一个接受一个string参数,返回一个int参数和一个error类型参数的函数,函数签名
是函数的参数列表和返回值列表,如果参数列表的类型一致并且返回值列表的参数类型一致就可以认为是一样的函数。
1 | type afunc func(string) (int, error) //声明一个类型 afunc afunc的底层类型是一个接受一个string参数,返回一个int参数和一个error类型参数的函数 |
结构体
结构体的声明
1 | type as struct{ //声明一个名称叫 as 的结构体 |
从上面的声明可以看出来,可以把struct简单的类比成class,这个as的结构体有两个属性,一个方法
使用结构体
1 | func main() { |
结构体的组合,也可以类比成class的继承,不过组合比继承更有优势。组合进来以后,asT结构体就拥有了as类的属性和方法,但是由于asT有a,as也有a属性,asT的就把as的覆盖了
1 | type asT struct { |
结构体可以组合多个结构体,也可以类比成多继承。但是这样有一个问题,比如组合的两个结构体内有同样名称的属性或者方法就会报错。声明的时候不会报错,只有使用的时候会报错,因为不知道使用哪个,如果指定相应的结构体进行使用就不会报错了。
1 | type asD struct{ |
还有一种方法,比如在新的结构体中定义一个同名的属性,就会覆盖其他的,所以就不会报错了
1 | type asD struct{ |
还有结构体中的覆盖是通过名称
来判断覆盖的,跟数据类型没有关系,方法的覆盖也是一样,跟参数列表和返回值没有关系
结构体中指针的使用
1 | func(this asT) String() string{ |
这个是有定义的时候有区别,因为在调用的时候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 | package main |
接口类型的nil值,接口只有声明的时候和赋值nil字面量的时候才是真正的nil值,看下面,输出结果a1是2,因为ai1不是真正的nil,ai1的动态值是nil,但是动态类型是*as,所以ai1不是nil
1 | var as2 *as //as2是nil |
goroutine
goroutine 是一个 go的用户级线程,也叫协程。
使用的话就是下面这样 go
后面跟上协程需要执行的函数代码。首先启动go程序的时候,会启动一个主进程。然后主进程生成一个主线程来执行go程序的main函数。执行的时候是一个for循环。
- 执行第一次循环的时候i = 0
- 然后执行到了
go func
代码 - 由go的runtime查找是否有空闲的协程。如果没有那么创建一个协程。
- 然后把
go func
的代码放入创建好的协程。 - 最后把这个包含了
go func
代码的协程放入协程的等待队列中 - 直到有空闲的线程,从等待队列中取出一个协程,执行这个协程的代码
可以看到下面这段代码的执行结果,是什么也不会发生。
1 | func main() { |
因为在for循环执行以后goroutine的代码还没有得到执行机会的时候,主线程main函数执行完了。那么这个时候系统的主线程就会关闭了,主进程也会关闭了。所以协程并没有执行。
看下面的代码,增加了定时器,这个时候会输出10个10,因为主线程执行到定时器的时候线程挂起,然后协程就有执行的时间了,但是协程开始执行的时候,for循环已经执行完了,这个时候变量i的值是10,所以10个协程打印出来的变量i的值都是10
1 | func main() { |
看下面的代码,go func(i int){}(i),增加了入参i int类型,并且在调用的时候把变量i传入了进去,那这个时候呢,执行的结果就是输出0-9的乱序,因为我们无法保证协程的执行顺序,但是由于传了当时的变量i,而go是浅拷贝,所以协程中的变量i的值被固定了。
1 | func main() { |
for循环
for循环和别的语言一样
1 | for i:=0; i< 10; i++ { |
还有 range 可以循环数组,切片,map。这里range会复制一个 numbers3 来进行循环,也就是说如果numbers3是数组,那么修改numbers3[0] 的值不会影响到 numbers3[0] 的值,因为数组是值类型。如果是切片那么会影响到。
1 | numbers2 := []int{1, 2, 3, 4, 5, 6} |
switch
switch语句不需要使用 break 了,因为go的switch只执行一个case,并且case后面可以跟多个结果,用逗号分隔,只要命中一个结果就执行这个case,所以case后面的结果也不能重复。
1 | var a int |
如果case后面重复,那么会报错
1 | switch 3 { |
因为switch结果和case结果会进行判等的,所以他们两个的类型要是一样的,不然也会报错
1 | switch 3 { |
ElasticSearch十七--ES-bool查询
bool查询
一个bool查询里面可以包含多个查询子句
must | 必须匹配,贡献算分 |
should | 选择性匹配,算分 |
must_not | filter context 查询子句,必须不能匹配,不算分 |
filter | filter context 必须匹配,但是不算分 |
- 子查询可以任意顺序出现
- 可以嵌套多个查询
- 如果bool查询中,没有must条件,should中必须满足一个查询
- should是一个数组
查询语句的结构,会对相关度算分产生影响
- 同一层级下的竞争字段,具有相同的权重
- 通过嵌套bool查询,可以改变对算分的影响
单字符串 多字段查询
可以通过 bool
的 should
来实现
1 | POST test_home/_search |
但是这样实现的算分过程可能不是我们想要的,算分过程如下
- 查询 should 语句中的两个查询
- 加和两个查询的评分
- 乘以匹配语句的总数
- 除以所有语句的总数
我们还可以使用最高算分 dis_max
- queries
1 | POST test_home/_search |
但是有的时候,最高评分是一样的,那怎么办呢,可以通过tie_breaker
参数来调整
- 获得最佳匹配语句的评分
- 将其他匹配语句的评分与
tie_breaker
相乘 - 对以上评分求和并规范化
- tie_breaker 是一个 0 - 1的浮点数,0代表最佳匹配,1代表所有语句同等重要。
三种场景
- 最佳字段 best_fields 相当于 dis_max 查询
- 当字段之间相互竞争,又相互关联。例如 title 和 body 这样的,评分来自最佳字段
- 多数字段 most_fields
- 处理英文内容时:一种常见的手段是,在主字段,抽取词干,加入同义词,以匹配更多的文档。相同的文本,加入子字段,以提供更加精确的匹配。其他字段作为匹配文档提高相关度的信号。匹配字段越多则越好
- 混合字段 cross field
- 对于某些实体,例如人名,地址,图书,需要在多个字段中确定信息,单个字段只能作为整体的一部分。希望在任何这些列出的字段中找到尽可能多的词
multi_match
极客时间 ES 学习笔记
ElasticSearch十六--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 | PHP首席架构师 |
如果我们想降低首席
这个词在搜索结果中的算分占比,可以使用boosting
1 | GET job_ik/_search |
降低后的结果
1 | PHP架构师 |
如果把negative_boost
提升为2,那么结果如下
1 | PHP首席架构师 |
极客时间 ES 学习笔记