由于本人在准备找golang的春招实习,所以开此贴方便记录
图床有些问题,最近准备搭建一个个人博客,到时候会放出链接
由于近期看到百度文库等抄袭搬运猖獗,故只放出部分内容,如需全部请私聊。
G:一个G代表一个goroutine,协程的本质是用户态的线程,用户对其有控制权限,内存占用少,切换代价低。
M:内核态线程,一个M代表了一个内核线程,等同于系统线程,所有的G都要放在M上才能运行。
P:处理器,用来管理和执行goroutine,一个P代表了M所需的上下文环境。P的个数取决于设置的GOMAXPROCS,go新版本默认使用最大内核数,比如你有8核处理器,那么P的数量就是8
三者关系:G需要绑定在M上才能运行,M需要绑定P才能运行。
M的数量和P不一定匹配,可以设置很多M,M和P绑定后才可运行,多余的M处于休眠状态。
系统会通过调度器从全局队列找到G分配给空闲的M,P会选择一个M来运行,M和G的数量不等,P会有一个本地队列表示M未处理的G,M本身有一个正在处理的G,M每次处理完一个G就从本地队列里取一个G,并且更新P的schedtick字段,如果本地队列没有G,则从全局队列一次性取走G/P个数的G,如果全局队列里也没有,就从其他的P的本地队列取走一半。
这要分为以下四种情况
1、由于原子、互斥量或通道操作调用导致Goroutine阻塞
该情况的G阻塞不会阻塞内核线程M,因此不会导致M的上下文切换而只涉及到G的切换。
2、由于网络请求和文件IO操作导致Goroutine阻塞
该情况的G阻塞不会阻塞内核线程M,因此不会导致M的上下文切换而只涉及到G的切换。
3、由于调用time.Sleep或者ticker计时器导致Goroutine阻塞
这种情况和情况2相似,不会阻塞当前M。
4、由于调用阻塞的系统调用时导致的goroutine阻塞
这种情况会导致当前M阻塞,内核会进行M的切换。而与当前M关联的当前P不会等待M的阻塞,因为这意味当前P下的所有G都无法执行,所以此时P会与当前M解除关联,转而关联到另一个内核线程M2,M2可能时新创建的内核线程,也可能时之前空闲的内核线程被唤醒来执行P的G。
对应问题如何阻塞一个goroutine?
方法1:从一个不发送数据channel中接收数据
方法2:向不接收数据的channel中发送数据
方法3:从空的channel中接收数据
方法4:向空channel中发送数据
方法5:使用select
make:返回类型是引用类型,只用于slice、map以及channel的初始化,无可替代
new:返回类型是指针,用于类型内存分配(初始化值为0),不常用
1.多个defer出现的时候,它是一个“栈”的关系,也就是先进后出。一个函数中,写在前面的defer会比写在后面的defer调用的晚。
2.return之后的语句先执行,defer后的语句后执行
参考链接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c6f73CJj-1652680479685)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401163745250.png)]
由panic引发异常以后,程序停止执行,然后调用延迟函数(defer),就像程序正常退出一样。另外recover也是要写在延迟函数中的,如果发生异常延迟函数就不执行了,那就永远无法recover了。
协程是轻量级线程,多个协程可以由一个或多个线程管理。协程无需上下文切换,没有线程之间切换的开销。协程的调度不需要多线程的锁机制,因为只有一个线程,不存在同时写变量冲突,执行效率比多线程高很多。
1.数组定长,定义的时候就需要确定。切片长度不定,append时会自动扩容
2.相同大小数组可以赋值,会拷贝全部内容。slice赋值和指针一样。数组和slice之间不能相互赋值。当然slice有自己的copy函数
3.数组也可以进行切片,返回值是一个slice,改变slice时会同步修改数组内容,相当于取得了这个数组的指针
4.slice的底层数据是数组,slice是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。
5.切片的类型和长度无关,而数组的长度是数组类型的一部分。
数组和切片的理解
二维数组
vararr[4][4]int
arr[2][3]=4
fmt.Println(arr)
二维切片
node:=make([][]int,N)
fori:=0;i<N;i++{
node[i]=make([]int,N)
}
结构体比较时
1如果两个结构体内部成员类型不同,则不能比较
2如果两个结构体内部成员类型相同,但是顺序不同,则不能比较
3如果两个结构体内含有无法比较类型,则无法比较
4如果两个结构体类型,顺序相同,且不含有无法比较类型(slice,map)则可以进行比较
select是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。
select随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。
packagemain
import"fmt"
funcmain(){
varc1,c2,c3chanint
vari1,i2int
select{
casei1=<-c1:
fmt.Printf("received",i1,"fromc1\n")
casec2<-i2:
fmt.Printf("sent",i2,"toc2\n")
casei3,ok:=(<-c3)://sameas:i3,ok:=<-c3
ifok{
fmt.Printf("received",i3,"fromc3\n")
}else{
fmt.Printf("c3isclosed\n")
}
default:
fmt.Printf("nocommunication\n")
}
}
每个case都必须是一个通信
所有channel表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行,其他被忽略。
如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
否则:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6f7iyXn-1652680479686)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401161739511.png)]
swich
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-byp9covY-1652680479687)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401162317867.png)]
map
map可以通过赋值扩容
map使用前必须make,而切片不必
使用数组+链表的形式实现的,使用拉链法消除hash冲突
hmap是Gomap的底层实现,每个hmap内都含有多个bmap(buckets桶、oldbuckets旧桶、overflow溢出桶),既每个哈希表都由多个桶组成。
当有两个或以上数量的键被哈希到了同一个bucket时,我们称这些键发生了冲突。Go使用链地址法来解决键冲突。
负载因子=键数量/bucket数量
负载因子>6.5时,也即平均每个bucket存储的键值对达到6.5个,触发扩容
扩容分为增量扩容和等量扩容
增量扩容:当负载因子过大时,就新建一个bucket,新的bucket长度是原来的2倍,然后旧bucket数据搬迁到新的bucket。
等量扩容:实际上并不是扩大容量,buckets数量不变,重新做一遍类似增量扩容的搬迁动作,把松散的键值对重新排列一次,以使bucket的使用率更高,进而保证更快的存取。
packagemain
import"fmt"
typeStudentstruct{
Namestring
}
var list map[string]Student
funcmain(){
list=make(map[string]Student)
student:=Student{"Aceld"}
list["student"]=student
list["student"].Name="LDB"
fmt.Println(list["student"])
}
map[string]Student
的value是一个Student结构值,所以当list["student"]=student
,是一个值拷贝过程。而list["student"]
则是一个值引用。那么值引用的特点是只读
。所以对list["student"].Name="LDB"
的修改是不允许的。
packagemain
import"fmt"
typeStudentstruct{
Namestring
}
varlistmap[string]*Student
funcmain(){
list=make(map[string]*Student)
student:=Student{"Aceld"}
list["student"]=&student
list["student"].Name="LDB"
fmt.Println(list["student"])
}
我们将map的类型的value由Student值,改成Student指针。
这样,我们实际上每次修改的都是指针所指向的Student空间,指针本身是常指针,不能修改,只读
属性,但是指向的Student是可以随便修改的,而且这里并不需要值拷贝。只是一个指针的赋值。
packagemain
import(
"fmt"
)
typestudentstruct{
Namestring
Ageint
}
funcmain(){
//定义map
m:=make(map[string]*student)
//定义student数组
stus:=[]student{
{Name:"zhou",Age:24},
{Name:"li",Age:23},
{Name:"wang",Age:22},
}
//将数组依次添加到map中
for_,stu:=rangestus{
m[stu.Name]=&stu
}
//打印map
fork,v:=rangem{
fmt.Println(k,"=>",v.Name)
}
}
oreach中,stu是结构体的一个拷贝副本,所以m[stu.Name]=&stu
实际上一致指向同一个指针,最终该指针的值为遍历的最后一个struct的值拷贝
。
正确写法
packagemain
import(
"fmt"
)
typestudentstruct{
Namestring
Ageint
}
funcmain(){
//定义map
m:=make(map[string]*student)
//定义student数组
stus:=[]student{
{Name:"zhou",Age:24},
{Name:"li",Age:23},
{Name:"wang",Age:22},
}
//遍历结构体数组,依次赋值给map
fori:=0;i<len(stus);i++{
m[stus[i].Name]=&stus[i]
}
//打印map
fork,v:=rangem{
fmt.Println(k,"=>",v.Name)
}
}
1、GoV1.3之前的标记-清除(markandsweep)算法
第一步,暂停程序业务逻辑,分类出可达和不可达的对象,然后做上标记。
第二步,开始标记,程序找出它所有可达的对象,并做上标记。
第三步,标记完了之后,然后开始清除未标记的对象.结果如下。
操作非常简单,但是有一点需要额外注意:markandsweep算法在执行的时候,需要程序暂停!即STW(stoptheworld),STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大,所以STW也是一些回收机制最大的难题和希望优化的点。所以在执行第三步的这段时间,程序会暂定停止任何工作,卡在那等待回收执行完毕。
第四步,停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。
2.GoV1.5的三色并发标记法
go采用三色标记法回收内存,程序开始创建的对象全部为白色,gc扫描后将可到达的对象标记为灰色,再从灰色对象中找到其引用的其他对象,将其标记为灰色,将自身标记为黑色,重复上述步骤,直到找不到灰色对象为止。最后对所有白色对象清除。
3.GoV1.8的混合写屏障(hybridwritebarrier)机制
强三色不变式
不存在黑色对象引用到白色对象的指针
弱三色不变式
所有被黑色对象引用的白色对象都处于灰色保护状态
插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;
删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。
规则:
1、GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW),
2、GC期间,任何在栈上创建的新对象,均为黑色。
3、被删除的对象标记为灰色。
4、被添加的对象标记为灰色。
Golang中的混合写屏障满足弱三色不变式,结合了删除写屏障和插入写屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间。
最常见的垃圾回收算法有标记清除(Mark-Sweep) 和引用计数(Reference Count),Go 语言采用的是标记清除算法。并在此基础上使用了三色标记法和写屏障技术,提高了效率。
标记清除收集器是跟踪式垃圾收集器,其执行过程可以分成标记(Mark)和清除(Sweep)两个阶段:
标记清除算法的一大问题是在标记期间,需要暂停程序(Stop the world,STW),标记结束之后,用户程序才可以继续执行。为了能够异步执行,减少 STW 的时间,Go 语言采用了三色标记法。
三色标记算法将程序中的对象分成白色、黑色和灰色三类。
标记开始时,所有对象加入白色集合(这一步需 STW )。首先将根对象标记为灰色,加入灰色集合,垃圾搜集器取出一个灰色对象,将其标记为黑色,并将其指向的对象标记为灰色,加入灰色集合。重复这个过程,直到灰色集合为空为止,标记阶段结束。那么白色对象即可需要清理的对象,而黑色对象均为根可达的对象,不能被清理。
三色标记法因为多了一个白色的状态来存放不确定对象,所以后续的标记阶段可以并发地执行。当然并发执行的代价是可能会造成一些遗漏,因为那些早先被标记为黑色的对象可能目前已经是不可达的了。所以三色标记法是一个 false negative(假阴性)的算法。
三色标记法并发执行仍存在一个问题,即在 GC 过程中,对象指针发生了改变。比如下面的例子:
A (黑) -> B (灰) -> C (白) -> D (白)
正常情况下,D 对象最终会被标记为黑色,不应被回收。但在标记和用户程序并发执行过程中,用户程序删除了 C 对 D 的引用,而 A 获得了 D 的引用。标记继续进行,D 就没有机会被标记为黑色了(A 已经处理过,这一轮不会再被处理)。
A (黑) -> B (灰) -> C (白)
↓
D (白)
为了解决这个问题,Go 使用了内存屏障技术,它是在用户程序读取对象、创建新对象以及更新对象指针时执行的一段代码,类似于一个钩子。垃圾收集器使用了写屏障(Write Barrier)技术,当对象新增或更新时,会将其着色为灰色。这样即使与用户程序并发执行,对象的引用发生改变时,垃圾收集器也能正确处理了。
一次完整的 GC 分为四个阶段:
总结
GoV1.3-普通标记清除法,整体过程需要启动STW,效率极低。
GoV1.5-三色标记法,堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通
GoV1.8-混合写屏障,混合写屏障机制,栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。
参考链接
当一个程序启动的时候,只有一个goroutine来调用main函数,称它为主goroutine,新的goroutine通过go语句进行创建。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35SgnIYe-1652680479687)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401162531461.png)]
这一题选择B无任何输出
因为一旦main函数执行结束,就意味着该Go程序运行的结束,所以sendData,getData没有执行整个程序便结束了。
可以通过三种方式来保证sendData,getData的执行:
time.Sleep,这种方式不可控,因为并不知道sendData,getData会执行多久,如果Sleep时间过短的话sendData,getData仍然会没有执行完
通过channel。syncChan会一直阻塞直到线程执行完毕,起到了等待的作用。
使用sync.WaitGroup
1.本质就是数据结构-队列
2.数据是先进先出
3.线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的。
4.channel有类型的,一个string的channel只能存放string类型数据。
注意事项
Channel读写特性(15字口诀)
给一个nilchannel发送数据,造成永远阻塞
从一个nilchannel接收数据,造成永远阻塞
给一个已经关闭的channel发送数据,引起panic
从一个已经关闭的channel接收数据,如果缓冲区中为空,则返回一个零值
无缓冲的channel是同步的,而有缓冲的channel是非同步的
空读写阻塞,写关闭异常,读关闭空零
负载均衡
什么是负载均衡?
负载均衡的四种方式:
1.HTTP重定向实现负载均衡
1.1调度策略
调度服务器收到用户的请求后,究竟选择哪台后端服务器处理请求,这由调度服务器所使用的调度策略决定。
由于轮询策略需要调度者维护一个值用于记录上次分配的服务器IP,因此需要额外的开销;此外,由于这个值属于互斥资源,那么当多个请求同时到来时,为了避免线程的安全问题,因此需要锁定互斥资源,从而降低了性能。而随机分配策略不需要维护额外的值,也就不存在线程安全问题,因此性能比轮询要高。
1.2优缺点分析
采用HTTP重定向来实现服务器集群的负载均衡实现起来较为容易,逻辑比较简单,但缺点也较为明显。
在HTTP重定向方法中,调度服务器只在客户端第一次向网站发起请求的时候起作用。当调度服务器向浏览器返回响应信息后,客户端此后的操作都基于新的URL进行的(也就是后端服务器),此后浏览器就不会与调度服务器产生关系,进而会产生如下几个问题
2.DNS负载均衡
什么是DNS?
什么是DNS负载均衡?
2.1调度策略
一般DNS提供商会提供一些调度策略供我们选择,如随机分配、轮询、根据请求者的地域分配离他最近的服务器。
2.2优缺点分析
2.3动态NDS
动态DNS能够让我们通过程序动态修改DNS服务器中的域名解析。从而当我们的监控程序发现某台服务器挂了之后,能立即通知DNS将其删掉。
2.4总结
DNS负载均衡是一种粗犷的负载均衡方法,这里只做介绍,不推荐使用。
3.反向代理负载均衡
反向代理服务器是一个位于实际服务器之前的服务器,所有向我们网站发来的请求都首先要经过反向代理服务器,服务器根据用户的请求要么直接将结果返回给用户,要么将请求交给后端服务器处理,再返回给用户。
我们知道,所有发送给我们网站的请求都首先经过反向代理服务器。那么,反向代理服务器就可以充当服务器集群的调度者,它可以根据当前后端服务器的负载情况,将请求转发给一台合适的服务器,并将处理结果返回给用户。
3.1优缺点
优点
缺点
3.2粘滞会话
反向代理服务器会引起一个问题:若某台后端服务器处理了用户的请求,并保存了该用户的session或存储了缓存,那么当该用户再次发送请求时,无法保证该请求仍然由保存了其Session或缓存的服务器处理,若由其他服务器处理,先前的Session或缓存就找不到了。
4.2层或者3层做负载均衡
2层负载均衡
3层负载均衡
什么是Nginx?
Nginx是一个轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万并发,现在中国使用nginx网站用户有很多,例如:新浪、网易、腾讯等。
Nginx的优缺点?
优点
缺点
packagemain
import(
"fmt"
)
typePeopleinterface{
Speak(string)string
}
typeStduentstruct{}
func(stu*Stduent)Speak(thinkstring)(talkstring){
ifthink=="love"{
talk="Youareagoodboy"
}else{
talk="hi"
}
return
}
funcmain(){
varpeoPeople=Stduent{}
think:="love"
fmt.Println(peo.Speak(think))
}
继承与多态的特点
在golang中对多态的特点体现从语法上并不是很明显。
我们知道发生多态的几个要素:
1、有interface接口,并且有接口定义的方法。
2、有子类去重写interface的接口。
3、有父类指针指向子类的具体对象
那么,满足上述3个条件,就可以产生多态效果,就是,父类指针可以调用子类的具体方法。
所以上述代码报错的地方在varpeoPeople=Stduent{}
这条语句,Student{}
已经重写了父类People{}
中的Speak(string)string
方法,那么只需要用父类指针指向子类对象即可。
所以应该改成varpeoPeople=&Student{}
即可编译通过。(People为interface类型,就是指针类型)
packagemain
import(
"fmt"
)
typePeopleinterface{
Show()
}
typeStudentstruct{}
func(stu*Student)Show(){
}
funclive()People{
varstu*Student
returnstu
}
funcmain(){
iflive()==nil{
fmt.Println("AAAAAAA")
}else{
fmt.Println("BBBBBBB")
}
}
结果
BBBBBBB
空接口与非空接口
空接口emptyinterface
varMyInterfaceinterface{}
非空接口non-emptyinterface
typeMyInterfaceinterface{
function()
}
这两种interface类型分别用两种struct
表示,空接口为eface
,非空接口为iface
.
空接口eface
typeefacestruct{//空接口
_type*_type//类型信息
dataunsafe.Pointer//指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
_type属性:是GO语言中所有类型的公共描述,Go语言几乎所有的数据结构都可以抽象成_type,是所有类型的公共描述,**type负责决定data应该如何解释和操作,**type的结构代码如下:
type_typestruct{
sizeuintptr//类型大小
ptrdatauintptr//前缀持有所有指针的内存大小
hashuint32//数据hash值
tflagtflag
alignuint8//对齐
fieldalignuint8//嵌入结构体时的对齐
kinduint8//kind有些枚举值kind等于0是无效的
alg*typeAlg//函数指针数组,类型实现的所有方法
gcdata*byte
strnameOff
ptrToThistypeOff
}
**data属性:**表示指向具体的实例数据的指针,他是一个unsafe.Pointer
类型,相当于一个C的万能指针void*
。
非空结构iface
typeifacestruct{
tab*itab
dataunsafe.Pointer
}
iface结构中最重要的是itab结构(结构如下),每一个itab
都占32字节的空间。itab可以理解为pair
。itab里面包含了interface的一些关键信息,比如method的具体实现。
typeitabstruct{
inter*interfacetype//接口自身的元信息
_type*_type//具体类型的元信息
link*itab
badint32
hashint32//_type里也有一个同样的hash,此处多放一个是为了方便运行接口断言
fun[1]uintptr//函数指针,指向具体类型所实现的方法
}
其中值得注意的字段,个人理解如下:
interfacetype
包含了一些关于interface本身的信息,比如packagepath
,包含的method
。这里的interfacetype是定义interface的一种抽象表示。type
表示具体化的类型,与eface的type类型相同。hash
字段其实是对_type.hash
的拷贝,它会在interface的实例化时,用于快速判断目标类型和接口中的类型是否一致。另,Go的interface的Duck-typing机制也是依赖这个字段来实现。fun
字段其实是一个动态大小的数组,虽然声明时是固定大小为1,但在使用时会直接通过fun指针获取其中的数据,并且不会检查数组的边界,所以该数组中保存的元素数量是不确定的。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wou1JGcI-1652680479688)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220414135834790.png)]
所以,People拥有一个Show方法的,属于非空接口,People的内部定义应该是一个iface
结构体
typePeopleinterface{
Show()
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfExc0Hl-1652680479688)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220414135929956.png)]
funclive()People{
varstu*Student
returnstu
}
stu是一个指向nil的空指针,但是最后returnstu
会触发匿名变量People=stu
值拷贝动作,所以最后live()
放回给上层的是一个Peopleinsterface{}
类型,也就是一个ifacestruct{}
类型。stu为nil,只是iface
中的data为nil而已。但是ifacestruct{}
本身并不为nil.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8mjWdsAn-1652680479689)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220414140400241.png)]
funcFoo(xinterface{}){
ifx==nil{
fmt.Println("emptyinterface")
return
}
fmt.Println("non-emptyinterface")
}
funcmain(){
varp*int=nil
Foo(p)
}
结果
non-emptyinterface
不难看出,Foo()
的形参xinterface{}
是一个空接口类型efacestruct{}
。
在执行Foo(p)
的时候,触发xinterface{}=p
语句,所以此时x结构如下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5kdnecp-1652680479689)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220414140811946.png)]
所以x结构体本身不为nil,而是data指针指向的p为nil。
typeSstruct{
}
funcf(xinterface{}){
}
funcg(x*interface{}){
}
funcmain(){
s:=S{}
p:=&s
f(s)//A
g(s)//B
f(p)//C
g(p)//D
}
B、D两行错误
B错误为:cannotuses(typeS)astype*interface{}inargumenttog:
*interface{}ispointertointerface,notinterface
D错误为:cannotusep(type*S)astype*interface{}inargumenttog:
*interface{}ispointertointerface,notinterface
看到这道题需要第一时间想到的是Golang是强类型语言,interface是所有golang类型的父类函数中funcf(xinterface{})
的interface{}
可以支持传入golang的任何类型,包括指针,但是函数funcg(x*interface{})
只能接受*interface{}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-si2pEWRu-1652680479689)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220407150920945.png)]
切片是围绕动态数组的概念进行构建的
type SliceHeader struct{
Data uintptr
Len int
Cap int
}
切片的结构由三个信息组成:
1、开放定址法:我们在遇到哈希冲突时,去寻找一个新的空闲的哈希地址。
1.1线性探测法
当我们的所需要存放值的位置被占了,我们就往后面一直加1并对m取模直到存在一个空余的地址供我们存放值,取模是为了保证找到的位置在0~m-1的有效空间之中。
1.2平方探测法
2.拉链法
将所有哈希地址相同的记录都链接在同一链表中
3.再哈希法:同时构造多个不同的哈希函数,等发生哈希冲突时就使用第二个、第三个……等其他的哈希函数计算地址,直到不发生冲突为止。虽然不易发生聚集,但是增加了计算时间
4、建立公共溢出区:将哈希表分为基本表和溢出表,将发生冲突的都存放在溢出表中。
packagemain
import(
"sync"
//"time"
)
constN=10
varwg=&sync.WaitGroup{}
funcmain(){
fori:=0;i<N;i++{
gofunc(iint){
wg.Add(1)
println(i)
deferwg.Done()
}(i)
}
wg.Wait()
}
结果
结果不唯一,代码存在风险,所有go未必都能执行到
这是使用WaitGroup经常犯下的错误!请各位同学多次运行就会发现输出都会不同甚至又出现报错的问题。这是因为go
执行太快了,导致wg.Add(1)
还没有执行main函数就执行完毕了。改为如下试试
packagemain
import(
"sync"
)
constN=10
varwg=&sync.WaitGroup{}
funcmain(){
fori:=0;i<N;i++{
wg.Add(1)
gofunc(iint){
println(i)
deferwg.Done()
}(i)
}
wg.Wait()
}
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OFhr7Io0-1652680479690)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220507102353376.png)]
byte等同于int8,常用来处理ascii字符
rune等同于int32,常用来处理unicode或utf-8字符
1.int转换为字符串:Itoa()
2.string转换为int:Atoi()
string是不可修改值的,但是可以改为byte数组修改
packagemain
import"fmt"
//golang的字符串是不可变的
//修改时,可以将字符串转换为[]byte进行修改
//[]byte和sting可以通过强制类型转换互换
funcmain(){
angel:="Herosneverdie"
angelBytes:=[]byte(angel)
fori:=4;i<=8;i++{
angelBytes[i]='x'
}
fmt.Println(string(angelBytes))
}
for{
_,err:=fmt.Scan(&m,&n)
iferr==io.EOF{
break
}
fmt.Prinltn(m,n)
}
math.MaxInt得到int的最大值
iota是golang语言的常量计数器,只能在常量的表达式中使用。
iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。
packagemain
import"fmt"
const(
i=1<<iota
j=3<<iota
k
l
)
fmt.Prinltn(i,j,j,l) //161224
packagemain//必须有个main包
import"fmt"
funcmain(){
//如果超过原来的容量,通常以2倍容量扩容
s:=make([]int,0,1)//长度为0,容量为1
oldCap:=cap(s)
fori:=0;i<20;i++{
s=append(s,i)
ifnewCap:=cap(s);oldCap<newCap{
fmt.Printf("cap:%d===>%d\n",oldCap,newCap)
oldCap=newCap
}
}
}
cap:1===>2
cap:2===>4
cap:4===>8
cap:8===>16
cap:16===>32
匿名函数可以直接赋值给一个变量或者直接执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8l5bg2Wj-1652680479691)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220412172028098.png)]
同一个网络中的主机可以直接进行通信,属于直接交付!>不同网络中的主机进行通信,需要通过路由器中转,属于间接交付。
什么是网关?
大家都知道,从一个房间走到另一个房间,必然要经过一扇门。同样,从一个网络向另一个网络发送信息,也必须经过一道“关口”,这道关口就是网关。顾名思义,网关就是一个网络连接到另一个网络的“关口”。也就是网络关卡。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Scpc7int-1652680479691)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413170025713.png)]
UDP(UserDatagramProtocol) | TCP(TransmissionControlProtocol) |
---|---|
无连接 | 面向连接 |
支持一对一,一对多,多对一,多对多通信 | 因为双方需要提前建立连接,因此只能一对一进行通信(全双工:双方随时都可以发送和接收数据) |
面向报文段(对应用层发送的报文段直接添加首部并传送到IP) | 面向字节流 |
不保证可靠交付:尽最大努力交付,不使用流量控制和拥塞控制 | 可靠传输,使用确认序号,滑动窗口,流量控制以及拥塞控制等来实现可靠传输 |
首部只有8字节 | 首部最小有20自己,最大有60字节 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l6SG95Ok-1652680479692)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413170125645.png)]
**序号:**用于对字节流进行编号,例如序号为301,表示第一个字节的编号为301,如果携带的数据长度为100字节,那么下一个报文段的序号应为401。
**确认号:**期望收到的下一个报文段的序号。例如B正确收到A发送来的一个报文段,序号为501,携带的数据长度为200字节,因此B期望下一个报文段的序号为701,B发送给A的确认报文段中确认号就为701。
**数据偏移:**指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
**控制位:**八位从左到右分别是CWR,ECE,URG,ACK,PSH,RST,SYN,FIN。
**CWR:**CWR标志与后面的ECE标志都用于IP首部的ECN字段,ECE标志为1时,则通知对方已将拥塞窗口缩小;
**ECE:**若其值为1则会通知对方,从对方到这边的网络有阻塞。在收到数据包的IP首部中ECN为1时将TCP首部中的ECE设为1;
**URG:**该位设为1,表示包中有需要紧急处理的数据,对于需要紧急处理的数据,与后面的紧急指针有关;
**ACK:**该位设为1,确认应答的字段有效,TCP规定除了最初建立连接时的SYN包之外该位必须设为1;
**PSH:**该位设为1,表示需要将收到的数据立刻传给上层应用协议,若设为0,则先将数据进行缓存;
**RST:**该位设为1,表示TCP连接出现异常必须强制断开连接;
**SYN:**用于建立连接,该位设为1,表示希望建立连接,并在其序列号的字段进行序列号初值设定;
**FIN:**该位设为1,表示今后不再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN位置为1的TCP段。
每个主机又对对方的FIN包进行确认应答之后可以断开连接。不过,主机收到FIN设置为1的TCP段之后不必马上回复一个FIN包,而是可以等到缓冲区中的所有数据都因为已成功发送而被自动删除之后再发FIN包;
**窗口:**窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
TCP是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的。
为什么是三次握手,不是两次和四次?
两次握手无法放置历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数
为什么挥手需要四次:
(1)序号(sequencenumber):Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认号(acknowledgementnumber):Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
(3)标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。
2、四次挥手
为什么客户端在time-wait阶段要等2msl?
当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。MSL指的是MaximumSegmentLifetime:一段TCP报文在传输过程中的最大生命周期。2MSL即是服务器端发出为FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长。
服务器端在1MSL内没有收到客户端发出的ACK确认报文,就会再次向客户端发出FIN报文;
如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时
否则客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。
慢启动:每次翻倍增加,涨到慢启动门限为止。
拥塞避免:当超过慢启动门限之后就开始拥塞避免算法,每次都是线性增长
拥塞发生:(如何判断拥塞何时发生:出现超时重传(重传计时器超时)以及快速重传(收到3个重复的ACK))
快速重传:快速重传的工作方式是当收到三个相同的ACK报文时,会在定时器过期之前,重传丢失的报文段。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H4vycpso-1652680479692)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220408195438875.png)]
源IP地址、目的IP地址、源端口、目的端口
五元组
源IP地址、目的IP地址、协议号、源端口、目的端口
七元组
源IP地址、目的IP地址、协议号、源端口、目的端口,服务类型以及接口索引
tcp需要进行三次握手,建立会话需要时间,tcp在网络拥塞的情况下会进行tcp全局同步,根据网络带宽调整tcp滑动窗口大小,引起tcp传输速度下降,甚至有可能会导致tcp报文没有带宽可用,导致tcp饿死,而视频传输对带宽的需求比较大,对时延要求比较高,对丢包率要求不是那么高,udp是面向无连接的传输协议,不需要进行三次握手,也没有tcp的滑动窗口,报文也比tcp小,正好满足了对视频传输的要求。
1.试图与一个主机上的不存在的端口建立连接
这符合触发发送RST分节的条件,目的为某端口的SYN分节到达,而端口没有监听,那么内核会立即响应一个RST,表示出错。客户端TCP收到这个RST之后则放弃这次连接的建立,并且返回给应用程序一个错误。正如上面所说的,建立连接的过程对应用程序来说是不可见的,这是操作系统帮我们来完成的,所以即使进程没有启动,也可以响应客户端。
2.试图与一个不存在的主机上的某端口建立连接
这也是一种比较常见的情况,当某台服务器主机宕机了,而客户端并不知道,仍然尝试去与其建立连接。根据上面的经验,这次主机已经处于未启动状态,操作系统也帮不上忙了,那么也就是连RST也不能响应给客户端,此时服务器端是一种完全没有响应的状态。那么此时客户端的TCP会怎么办呢?据书上介绍,如果客户端TCP没有得到任何响应,那么等待6s之后再发一个SYN,若无响应则等待24s再发一个,若总共等待了75s后仍未收到响应就会返回ETIMEDOUT错误。这是TCP建立连接自己的一个保护机制,但是我们要等待75s才能知道这个连接无法建立,对于我们所有服务来说都太长了。更好的做法是在代码中给connect设置一个超时时间,使它变成我们可控的,让等待时间在毫秒级还是可以接收的。
3.Server进程被阻塞
由于某些情况,服务器端进程无法响应任何请求,比如所在主机的硬盘满了,导致进程处于完全阻塞,通常我们测试时会用gdb模拟这种情况。上面提到过,建立连接的过程对应用程序是不可见的,那么,这时连接可以正常建立。当然,客户端进程也可以通过这个连接给服务器端发送请求,服务器端TCP会应答ACK表示已经收到这个分节(这里的收到指的是数据已经在内核的缓冲区里准备好,由于进程被阻塞,无法将数据从内核的缓冲区复制到应用程序的缓冲区),但永远不会返回结果。
即超文本传输协议和超文本传输安全协议
运行在TCP之上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gGEdri9o-1652680479692)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413172215330.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ri3d4xHo-1652680479693)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413172234095.png)]
HTTP协议主要由三大部分组成:
起始行(startline)
:描述请求或响应的基本信息;头部字段(header)
:使用key-value形式更详细地说明报文;消息正文(entity)
:实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uu0r4TeG-1652680479693)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413173248187.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ND8Okz0U-1652680479693)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413173305348.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehjKEkVb-1652680479693)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413173017199.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QWmbx1Ql-1652680479694)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413173046983.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTTNnRE9-1652680479694)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413173107486.png)]
浏览器中输入URL到返回页面的全过程
DNS服务器间进行域传输的时候用TCP53
客户端查询DNS服务器时用UDP53
DNS查询超过512字节
TC标志出现,使用TCP发送
arp协议在TCP/IP模型中属于IP层(网络层),在OSI模型中属于链路层。
计算机中会维护一个ARP缓存表,这个表记录着IP地址与MAC地址的映射关系,我们可以通过在电脑的控制台通过arp-a指令查看一下我们自己计算机的ARP缓存表。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GkjgW57Y-1652680479694)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220411151053731.png)]
静态映射需要管理员手工建立和维护
动态映射不需要管理员手工建立和维护
mac地址又称物理地址,以太网地址。
ARP协议是地址解析协议,是通过解析IP地址
得到MAC地址
的。
每当有一个数据要发送的时候都需要在通过ARP协议
将IP地址转换成MAC地址
ARP协议的主要工作就是建立、查询、更新、删除ARP表项。
工作原理
(1)每台主机都会在自己的ARP缓冲区建立一个ARP列表,以表示IP地址和MAC地址的对应关系。当源主机需要将一个数据包发送到目的主机时,会先检查自己的ARP列表中是否存在该IP地址对应的MAC地址,如果有,就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址,硬件地址,以及目的主机的IP地址。
(2)网络中所有主机收到这个ARP请求之后,会检查数据包中的目的IP是否和自己的IP地址一致,如果不一致就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP列表中已经存在该IP的信息,则将其覆盖,然后将自己的MAC地址写入ARP响应数据包中发送给源主机,告诉源主机自己是它想要找的MAC地址。给源主机发送一个ARP响应数据包
(3)源主机收到这个ARP响应数据包之后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
报文格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLe1RqTW-1652680479695)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220411151655873.png)]
反向地址转换协议
ARP(地址解析协议)是设备通过自己知道的IP地址来获得自己不知道的物理地址的协议。
工作原理
ARP把逻辑(IP)地址映射为物理地址。RARP把物理地址映射为逻辑(IP)地址。
1.桥接模式,虚拟系统和外部系统通讯,但是容易造成IP冲突
2.NAT模式,网络地址转换模式,虚拟系统可以和外部系统通讯,不造成IP冲突
3.主机模式,独立的系统
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CQqq5W6y-1652680479695)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220411205333679.png)]
ls 显示文件或目录
-l列出文件详细信息l(list)
-a列出当前目录下所有文件及目录,包括隐藏的a(all)
mkdir创建目录
-p创建目录,若无父目录,则创建p(parent)
cd切换目录
touch创建空文件
echo创建带有内容的文件。
cat查看文件内容
cp拷贝
mv移动或重命名
rm删除文件
-r递归删除,可删除子目录及文件
-f强制删除
find在文件系统中搜索某文件
wc统计文本中行数、字数、字符数
grep在文本文件中查找某个字符串
rmdir删除空目录
tree树形结构显示目录,需要安装tree包
pwd显示当前目录
ln创建链接文件
more、less分页显示文本文件内容
head、tail显示文件头、尾内容
ctrl+alt+F1命令行全屏模式
stat显示指定文件的详细信息,比ls更详细
who显示在线登陆用户
whoami显示当前操作用户
hostname显示主机名
uname显示系统信息
top动态显示当前耗费资源最多进程信息
ps显示瞬间进程状态ps-aux
du查看目录大小du-h/home带有单位显示目录信息
df查看磁盘大小df-h带有单位显示磁盘信息
ifconfig查看网络情况
ping测试网络连通
netstat显示网络状态信息
man命令不会用了,找男人如:manls
clear清屏
alias对命令重命名如:aliasshowmeit="ps-aux",另外解除使用unaliaxshowmeit
kill杀死进程,可以先用ps或top命令查看进程的id,然后再用kill命令杀死进程。
gzip:
bzip2:
tar:打包压缩
-c归档文件
-x压缩文件
-zgzip压缩文件
-jbzip2压缩文件
-v显示压缩或解压缩过程v(view)
-f使用档名
例:
tar-cvf/home/abc.tar/home/abc只打包,不压缩
tar-zcvf/home/abc.tar.gz/home/abc打包,并用gzip压缩
tar-jcvf/home/abc.tar.bz2/home/abc打包,并用bzip2压缩
当然,如果想解压缩,就直接替换上面的命令tar-cvf/tar-zcvf/tar-jcvf中的“c”换成“x”就可以了。
shutdown
-r关机重启
-h关机不重启
now立刻关机
halt关机
reboot重启
将一个命令的标准输出作为另一个命令的标准输入。也就是把几个命令组合起来使用,后一个命令除以前一个命令的结果。
例:grep-r"close"/home/*|more在home目录下所有文件中查找,包括close的文件,并分页输出。
dpkg(DebianPackage)管理工具,软件包名以.deb后缀。这种方法适合系统不能联网的情况下。
比如安装tree命令的安装包,先将tree.deb传到Linux系统中。再使用如下命令安装。
sudodpkg-itree_1.5.3-1_i386.deb安装软件
sudodpkg-rtree卸载软件
注:将tree.deb传到Linux系统中,有多种方式。VMwareTool,使用挂载方式;使用winSCP工具等;
APT(AdvancedPackagingTool)高级软件工具。这种方法适合系统能够连接互联网的情况。
依然以tree为例
sudoapt-getinstalltree安装tree
sudoapt-getremovetree卸载tree
sudoapt-getupdate更新软件
sudoapt-getupgrade
将.rpm文件转为.deb文件
.rpm为RedHat使用的软件格式。在Ubuntu下不能直接使用,所以需要转换一下。
sudoalienabc.rpm
im三种模式:命令模式、插入模式、编辑模式。使用ESC或i或:来切换模式。
命令模式下:
:q退出
:q!强制退出
:wq保存并退出
:setnumber显示行号
:setnonumber隐藏行号
/apache在文档中查找apache按n跳到下一个,shift+n上一个
yyp复制光标所在行,并粘贴
h(左移一个字符←)、j(下一行↓)、k(上一行↑)、l(右移一个字符→)
/etc/passwd存储用户账号
/etc/group存储组账号
/etc/shadow存储用户账号的密码
/etc/gshadow存储用户组账号的密码
useradd用户名
userdel用户名
adduser用户名
groupadd组名
groupdel组名
passwdroot给root设置密码
suroot
su-root
/etc/profile系统环境变量
bash_profile用户环境变量
.bashrc用户环境变量
suuser切换用户,加载配置文件.bashrc
su-user切换用户,加载配置文件/etc/profile,加载bash_profile
sudochown[-R]owner[:group]{File|Directory}
例如:还以jdk-7u21-linux-i586.tar.gz为例。属于用户hadoop,组hadoop
要想切换此文件所属的用户及组。可以使用命令。
sudochownroot:rootjdk-7u21-linux-i586.tar.gz
操作系统为了跟踪每个进程的活动状态,维护了一个进程表。进程表的内部列出了每个进程的状态以及每个进程使用的资源等。
进程:正在执行程序的一个实例,是资源分配的基本单位。(进程控制块(processcontrolblock)描述进程的基本信息和运行状态,所谓的创建和撤销进程,都是指对PCB的操作)
线程:进程中的单条流向,是程序独立调度的基本单位。
线程和协程推荐在IO密集型的任务(比如网络调用)中使用,而在CPU密集型的任务中,表现较差。
进程切换与线程切换的一个最主要区别就在于进程切换涉及到虚拟地址空间的切换而线程切换则不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
消息队列:消息队列是内核中存储消息的链表,它由消息队列标识符进行标识,这种方式能够在不同的进程中间提供全双工通信连接
管道:用于两个相关进程间通信,是一种半双工的通信方式,只能在父子进程中使用,如果需要全双工,需要另外的一个管道。
套接字:与其他通信方式不同,可以用于不同机器之间的进程通信。
信号量:是一个计数器,用于为多个进程提供对共享数据对象的访问
共享内存:使用所有进程的内存来建立连接,不过需要同步进程(信号量)来保护访问。是最快的IPC方式。
消息传递:消息传递是进程间实现通信和同步等待的机制。消息直接由发送方传递给接收方。
远程调用RPC
管道/匿名管道(Pipes):用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
有名管道(NamesPipes):匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(firstinfirstout)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
信号(Signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
消息队列(MessageQueuing):消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺。
信号量(Semaphores):信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
共享内存(Sharedmemory):使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
套接字(Sockets):此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
全双工通信允许数据同时在两个方向上传输,即有两个信道,因此允许同时进行双向传输。全双工通信是两个单工通信方式的结合,要求收发双方都有独立的接收和发送能力。
半双工通信允许信号在两个方向上传输,但某一时刻只允许信号在一个信道上单向传输。半双工通信实际上是一种可切换方向的单工通信。
单工通信只支持信号在一个方向上传输(正向或反向),任何时候不能改变信号的传输方向。
当两个或两个以上的进程/线程处于就绪状态时,如果只有一个CPU可用,那么必须选择接下来哪个进程/线程可以运行,操作系统中有一个叫做调度程序(scheduler)的角色存在就是做这件事的。
不同的系统中,调度算法是不同的,有如下3种情况:
并发:在操作系统中,某一时间段,几个程序在同一个CPU上运行,但在任意一个时间点上,只有一个程序在CPU上运行。
并行:当操作系统有多个CPU时,一个CPU处理A线程,另一个CPU处理B线程,两个线程互相不抢占CPU资源,可以同时进行,这种方式成为并行。
注意点:并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『同时』
一个让人更加容易理解的例子:
共享的:
独享的:
僵尸进程:子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。
孤儿进程:父进程结束了,而它的一个或多个子进程还在运行,那么这些子进程就成为孤儿进程(fatherdied)。子进程的资源由init进程(进程号PID=1)回收。
如何解决僵尸进程和孤儿进程
既然所有进程都必须在退出之后被wait()或waitpid()以释放其遗留在系统中的一些资源,那么应该由谁来处理孤儿进程的善后事宜呢?这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程“凄凉地”结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。这样来看,孤儿进程并不会有什么危害,真正会对系统构成威胁的是僵尸进程。那么,什么情况下僵尸进程会威胁系统的稳定呢?设想有这样一个父进程:它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵尸进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。严格地来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵尸进程时,答案就是把产生大量僵尸进程的那个元凶枪毙掉(通过kill发送SIGTERM或者SIGKILL信号)。枪毙了元凶进程之后,它产生的僵尸进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经“僵尸”的孤儿进程就能瞑目而去了。
池是保证计算机能够正常运行的前提下,能最大限度开采的资源的量,他降低的程序的运行效率,但是保证了计算机硬件的安全,从而让你写的程序可以正常运行。池就是一个量的单位,代表了你所能开采的资源的度,超过这个度,身体就跨了,首先要用,其次要有度。
区别
进程池是主程序结束就自动结束
线程池是主程序结束不影响线程池
io即输入输出,涉及计算机核心与其他设备间数据迁移的过程,就是IO。
应用程序发起的一次IO操作包含两个阶段:
IO调用:应用程序进程向操作系统内核发起调用。
IO执行:操作系统内核完成IO操作。
操作系统内核完成IO操作还包括两个过程:
准备数据阶段:内核等待I/O设备准备好数据
拷贝数据阶段:将数据从内核缓冲区拷贝到用户进程缓冲区
一个完整的IO过程包括以下几个步骤:
应用程序进程向操作系统发起IO调用请求
操作系统准备数据,把IO外部设备的数据,加载到内核缓冲区
操作系统拷贝数据,即将内核缓冲区的数据,拷贝到用户进程缓冲区
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTUqI4FS-1652680479701)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413204254032.png)]
阻塞IO:
假设应用程序的进程发起IO调用,但是如果内核的数据还没准备好的话,那应用程序进程就一直在阻塞等待,一直等到内核数据准备好了,从内核拷贝到用户空间,才返回成功提示,此次IO操作,称之为阻塞IO。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1a3hoywq-1652680479701)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413204457463.png)]
阻塞IO比较经典的应用就是阻塞socket、JavaBIO。阻塞IO的缺点就是:如果内核数据一直没准备好,那用户进程将一直阻塞,浪费性能,可以使用非阻塞IO优化。
非阻塞IO:
如果内核数据还没准备好,可以先返回错误信息给用户进程,让它不需要等待,而是通过轮询的方式再来请求。这就是非阻塞IO。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sDbvJJbK-1652680479702)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413204601337.png)]
流程:
非阻塞IO模型,简称NIO,Non-BlockingIO。它相对于阻塞IO,虽然大幅提升了性能,但是它依然存在性能问题,即频繁的轮询,导致频繁的系统调用,同样会消耗大量的CPU资源。可以考虑IO复用模型,去解决这个问题。
既然NIO无效的轮询会导致CPU资源消耗,我们等到内核数据准备好了,主动通知应用进程再去进行系统调用,那不就好了嘛。
文件描述符fd(FileDescriptor),它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
IO复用模型核心思路:系统给我们提供一类函数(如我们耳濡目染的select、poll、epoll函数),它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用进程再发起recvfrom系统调用。
应用进程通过调用select函数,可以同时监控多个fd,在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时应用进程再发起recvfrom请求去读取数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvynsVrR-1652680479702)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413204941609.png)]
非阻塞IO模型(NIO)中,需要N(N>=1)次轮询系统调用,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。
但是呢,select有几个缺点:
监听的IO最大连接数有限,在Linux系统上一般为1024。
select函数返回后,是通过遍历fdset,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)
因为存在连接数限制,所以后来又提出了poll。与select相比,poll解决了连接数限制问题。但是呢,select和poll一样,还是需要通过遍历文件描述符来获取已经就绪的socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。
epoll先通过epoll_ctl()来注册一个fd(文件描述符),一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉了遍历文件描述符的坑爹操作,而是采用监听事件回调的机制。这就是epoll的亮点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J68gXLpq-1652680479702)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413205151267.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vYhccGLw-1652680479702)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413205221360.png)]
epoll明显优化了IO的执行效率,但在进程调用epoll_wait()时,仍然可能被阻塞。
IO模型之信号驱动模型
信号驱动IO不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号(调用sigaction的时候建立一个SIGIO的信号),然后应用用户进程可以去做别的事,不用阻塞。当内核数据准备好后,再通过SIGIO信号通知应用进程,数据准备好后的可读状态。应用用户进程收到信号之后,立即调用recvfrom,去读取数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j55CMJN9-1652680479703)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413205435750.png)]
信号驱动IO模型,在应用进程发出信号后,是立即返回的,不会阻塞进程。它已经有异步操作的感觉了。但是你细看上面的流程图,发现数据复制到应用缓冲的时候,应用进程还是阻塞的。回过头来看下,不管是BIO,还是NIO,还是信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的。
IO模型之异步IO(AIO)
前面讲的BIO,NIO和信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的,因此都不算是真正的异步。AIO实现了IO全流程的非阻塞,就是应用进程发出系统调用后,是立即返回的,但是立即返回的不是处理结果,而是表示提交成功类似的意思。等内核数据准备好,将数据拷贝到用户进程缓冲区,发送信号通知用户进程IO操作执行完毕。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5QIWkFt1-1652680479703)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413205541082.png)]
异步IO的优化思路很简单,只需要向内核发送一次请求,就可以完成数据状态询问和数据拷贝的所有操作,并且不用阻塞等待结果。日常开发中,有类似思想的业务场景:
比如发起一笔批量转账,但是批量转账处理比较耗时,这时候后端可以先告知前端转账提交成功,等到结果处理完,再通知前端结果即可。
阻塞、非阻塞、同步、异步IO划分
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8sL29xYz-1652680479703)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413205614217.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zbJFDOvo-1652680479704)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413205729465.png)]
一个通俗例子读懂BIO、NIO、AIO
同步阻塞(blocking-IO)简称BIO
同步非阻塞(non-blocking-IO)简称NIO
异步非阻塞(asynchronous-non-blocking-IO)简称AIO
一个经典生活的例子:
小明去吃同仁四季的椰子鸡,就这样在那里排队,等了一小时,然后才开始吃火锅。(BIO)
小红也去同仁四季的椰子鸡,她一看要等挺久的,于是去逛会商场,每次逛一下,就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上椰子鸡了。(NIO)
小华一样,去吃椰子鸡,由于他是高级会员,所以店长说,你去商场随便逛会吧,等下有位置,我立马打电话给你。于是小华不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的椰子鸡(AIO)
死锁一般发生在多线程(两个或两个以上)执行的过程中。因为争夺资源造成线程之间相互等待,这种情况就产生了死锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-960mje8A-1652680479704)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413202418326.png)]
比如你有资源1和2,以及线程A和B,当线程A在已经获取到资源1的情况下,期望获取线程B持有的资源2。与此同时,线程B在已经获取到资源2的情况下,期望获取现场A持有的资源1。
那么线程A和线程B就处理了相互等待的死锁状态,在没有外力干预的情况下,线程A和线程B就会一直处于相互等待的状态,从而不能处理其他的请求。
1、互斥:多个线程不能同时使用一个资源。比如线程A已经持有的资源,不能再同时被线程B持有。如果线程B请求获取线程A已经占有的资源,那线程B只能等待这个资源被线程A释放。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B38fv2Zg-1652680479704)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413202651577.png)]
2、持有并等待:当线程A已经持有了资源1,又提出申请资源2,但是资源2已经被线程C占用,所以线程A就会处于等待状态,但它在等待资源2的同时并不会释放自己已经获取的资源1。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VFKASsdc-1652680479705)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413202657952.png)]
3、线程A获取到资源1之后,在自己使用完之前不能被其他线程(比如线程B)抢占使用。如果线程B也想使用资源1,只能在线程A使用完后,主动释放后再获取。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4eicFOX5-1652680479705)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413202703240.png)]
4、发生死锁时,必然会存在一个线程,也就是资源的环形链。比如线程A已经获取了资源1,但同时又请求获取资源2。线程B已经获取了资源2,但同时又请求获取资源1,这就会形成一个线程和资源请求等待的环形图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J7b3lrXQ-1652680479705)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413202709025.png)]
并发场景下一旦死锁,一般没有特别好的方法,很多时候只能重启应用。因此,最好是规避死锁,那么具体怎么做呢?答案是:至少破坏其中一个条件(互斥必须满足,你可以从其他三个条件出发)。
持有并等待:我们可以一次性申请所有的资源,这样就不存在等待了。
不可剥夺:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可剥夺这个条件就破坏掉了。
循环等待:可以靠按序申请资源来预防,也就是所谓的资源有序分配原则,让资源的申请和使用有线性顺序,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样的线性化操作就自然就不存在循环了。
同步是阻塞模式,异步是非阻塞模式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0rUdqVO-1652680479705)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413203359868.png)]
epoll是一种更加高效的IO多路复用的方式,它可以监视的文件描述符数量突破了1024的限制(十万),同时不需要通过轮询遍历的方式去检查文件描述符上是否有事件发生,因为epoll_wait返回的就是有事件发生的文件描述符。本质上是事件驱动。
具体是通过红黑树和就绪链表实现的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EaGVtX3z-1652680479706)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220413203550548.png)]
在Linux中fork函数是非常重要的函数,它的作用是从已经存在的进程中创建一个子进程,而原进程称为父进程。
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTIlrTPg-1652680479706)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220414154346040.png)]
调用fork(),当控制转移到内核中的fork代码后,内核开始做:
1.分配新的内存块和内核数据结构给子进程。
2.将父进程部分数据结构内容拷贝至子进程。
3.将子进程添加到系统进程列表。
4.fork返回开始调度器,调度。
type LRUCachestruct{
size int
capacity int
cachemap[int]*DLinkedNode
head,tail*DLinkedNode
}
type DLinkedNodestruct{
key,value int
prev,next* DLinkedNode
}
func initDLinkedNode(key,valueint) *DLinkedNode{
return &DLinkedNode{
key:key,
value:value,
}
}
func Constructor(capacityint) LRUCache{
l:=LRUCache{
cache:map[int]*DLinkedNode{},
head:initDLinkedNode(0,0),
tail:initDLinkedNode(0,0),
capacity:capacity,
}
l.head.next=l.tail
l.tail.prev=l.head
return l
}
func(this*LRUCache)Get(keyint)int{
if_,ok:=this.cache[key];!ok{
return -1
}
node:=this.cache[key]
this.moveToHead(node)
return node.value
}
func(this*LRUCache)Put(keyint,valueint){
if_,ok:=this.cache[key];!ok{
node:=initDLinkedNode(key,value)
this.cache[key]=node
this.addToHead(node)
this.size++
if this.size>this.capacity{
removed:=this.removeTail()
delete(this.cache,removed.key)
this.size--
}
}else{
node:=this.cache[key]
node.value=value
this.moveToHead(node)
}
}
func(this*LRUCache)addToHead(node*DLinkedNode){
node.prev=this.head
node.next=this.head.next
this.head.next.prev=node
this.head.next=node
}
func(this*LRUCache)removeNode(node*DLinkedNode){
node.prev.next=node.next
node.next.prev=node.prev
}
func(this*LRUCache)moveToHead(node*DLinkedNode){
this.removeNode(node)
this.addToHead(node)
}
func(this*LRUCache)removeTail()*DLinkedNode{
node:=this.tail.prev
this.removeNode(node)
return node
}
注意不要遗漏以下几点。
1、cache:map[int]*DLinkedNode{}的括号
2、node.value=value覆盖后给node赋新值
3、this.cache[key]=node插入链表时,将初始化的链表赋给哈希表
4、delete(this.cache,removed.key)超出容量后删除哈希表
func levelOrder(root*TreeNode)[][]int{
num:=[][]int{}
if root==nil{
return num
}
start:=[]*TreeNode{root} //结构体类型切片
for i:=0;len(start)>0;i++{ //开始遍历
replace:=[]*TreeNode{}
num=append(num,[]int{}) //向二维数组切片中增加一个空数组
for j:=0;j<len(start);j++{ //遍历该层
node:=start[j]
num[i]=append(num[i],node.Val)
if node.Left!=nil{
replace=append(replace,node.Left)
}
if node.Right!=nil{
replace=append(replace,node.Right)
}
}
start=replace //替换成下一层结点
}
return num
}
funcreverse(array[]int){
head:=0
end:=len(array)-1
for;head<end;{
array[head],array[end]=array[end],array[head]
head++
end--
}
}
funczigzagLevelOrder(root*TreeNode)[][]int{
result:=[][]int{}
ifroot==nil{
returnresult
}
tree:=[]*TreeNode{root}
fori:=0;len(tree)>0;i++{
newtree:=[]*TreeNode{}
result=append(result,[]int{})
forj:=0;j<len(tree);j++{
node:=tree[j]
result[i]=append(result[i],node.Val)
ifnode.Left!=nil{
newtree=append(newtree,node.Left)
}
ifnode.Right!=nil{
newtree=append(newtree,node.Right)
}
}
tree=newtree
}
fori:=0;i<len(result);i++{
ifi%2==1{
reverse(result[i])
}
}
returnresult
}
funcinorder(root*TreeNode,result*[]int){
ifroot!=nil{
inorder(root.Left,result)
*result=append(*result,root.Val)
inorder(root.Right,result)
}
}
funcinorderTraversal(root*TreeNode)[]int{
result:=[]int{}
inorder(root,&result)
returnresult
}
美团最优二叉树
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XKl3I923-1652680479709)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401203811557.png)]
package main
import(
"fmt"
)
varans[301][301][301]int
varnums[301]int
func main(){
var N int
fmt.Scanln(&N)
for i:=1;i<=N;i++{
fmt.Scan(&nums[i])
}
result:=dp(1,N,0) //0为虚拟节点
fmt.Println(result)
}
funcdp(headint,endint,fatherint)int{
ifhead>end{
return 0
}
ifans[head][end][father]!=0{
returnans[head][end][father]
}
ret:=999999999
fori:=head;i<=end;i++{ //每个节点都可能做根
left:=dp(head,i-1,i) //i做节点时,左子树的开销
right:=dp(i+1,end,i) //i做节点时,右子树的开销
ret=min(ret,left+right+nums[i]*nums[father])//总开销要加上根节点*父节点
}
ans[head][end][father]=ret
returnans[head][end][father]
}
funcmin(a,bint)int{
ifa>b{
returnb
}
returna
}
func rightSideView(root *TreeNode) []int {
num:=[][]int{}
res:=[]int{}
if root==nil{
return res
}
start:=[]*TreeNode{root}
for i:=0;len(start)>0;i++{
newstart:=[]*TreeNode{}
num=append(num,[]int{})
for j:=0;j<len(start);j++{
node:=start[j]
num[i]=append(num[i],node.Val)
if node.Left!=nil{
newstart=append(newstart,node.Left)
}
if node.Right!=nil{
newstart=append(newstart,node.Right)
}
}
start=newstart
}
for i:=0;i<len(num);i++{
res=append(res,num[i][len(num[i])-1])
}
return res
}
func reverse(head*ListNode){
var pre *ListNode
curr:=head
for curr!=nil{
next:=curr.Next
curr.Next=pre
pre=curr
curr=next
}
}
func reverseBetween(head*ListNode,leftint,rightint)*ListNode{
frist:=&ListNode{Val:-1}
frist.Next=head
pre:=frist
for i:=1;i<left;i++{
pre=pre.Next
}
leftNode:=pre.Next
rightNode:=head
for j:=1;j<right;j++{
rightNode=rightNode.Next
}
end:=rightNode.Next
pre.Next=nil
rightNode.Next=nil
reverse(leftNode)
pre.Next=rightNode
leftNode.Next=end
return frist.Next
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rajIj8hf-1652680479709)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401154206637.png)]
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
插入排序(InsertionSorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kP6ffSOH-1652680479709)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401154340880.png)]
funcQuickSort(headint,endint,array[]int){
left:=head //定义首尾
right:=end
pivot:=array[head] //取中点为划分点也可取首尾
for;left<right;{ //当首尾碰面后,代表结束
for;array[left]<pivot;{ //找到一个需要交换的点
left++
}
for;array[right]>pivot;{
right--
}
ifleft>=right{ //相遇则结束本次循环
break
}
array[left],array[right]=array[right],array[left]
ifarray[left]==pivot{ //当有值和pivot相同时,左右指针无法碰面
right--
}
ifarray[right]==pivot{
left++
}
}
ifleft==right{ //走一步,切分
right--
left++
}
ifhead<right{
QuickSort(head,right,array)
}
ifend>left{
QuickSort(left,end,array)
}
}
functhreeSum(nums[]int)[][]int{
result:=[][]int{}
n:=len(nums)
ifn<3{
returnresult
}
sort.Ints(nums)
fori:=0;i<n;i++{
ifi>0&&nums[i]==nums[i-1]{
continue
}
k:=n-1
forj:=i+1;j<n;j++{
ifj>i+1&&nums[j]==nums[j-1]{
continue
}
forj<k&&nums[i]+nums[j]+nums[k]>0{
k--
}
ifj==k{
break
}
ifnums[i]+nums[j]+nums[k]==0{
result=append(result,[]int{nums[i],nums[j],nums[k]})
}
}
}
returnresult
}
何为动态规划?避免重复节点的计算,来加速节点的计算过程。空间换时间。带备忘录的递归。递归树的剪枝。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TaQU58w1-1652680479709)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220409153338917.png)]
1.暴力枚举(时间复杂度指数)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WT2iYXn2-1652680479710)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220409153618386.png)]
2.剪枝(用字典)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vTwVVoWn-1652680479710)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220409153751691.png)]
3.迭代(逆序思想)从后往前,类似数学归纳法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7o8MUKMr-1652680479710)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220409154044976.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oc32CUFy-1652680479710)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220409154313276.png)]
动态规划思想顺序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BL15Ibip-1652680479711)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220409154350560.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kl9TH4EA-1652680479711)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220325210637389.png)]
f(i)=max{f(i−1)+nums[i],nums[i]}
funcmaxSubArray(nums[]int)int{
n:=len(nums)
max:=nums[0]
fori:=1;i<n;i++{
ifnums[i-1]>0{//计算f(i)
nums[i]+=nums[i-1]
}
ifmax<nums[i]{
max=nums[i]
}
}
returnmax
}
funcmax(a,bint)int{
ifa>b{
returna
}
returnb
}
funcmin(a,bint)int{
ifa<b{
returna
}
returnb
}
funcmaxProfit(prices[]int)int{
result:=0
minvalue:=999999
n:=len(prices)
fori:=0;i<n;i++{
result=max(result,prices[i]-minvalue)
minvalue=min(minvalue,prices[i])
}
returnresult
}
暴力解法,从0开始递增检查每一段长度为n的子串是否为回文串
funclongestPalindrome(sstring)string{
n:=len(s)
ifn<2{
returns
}
dp:=make([][]bool,n)
fori,_:=rangedp{
dp[i]=make([]bool,n)
}
max:=""
forl:=0;l<n;l++{//l为本次循环遍历的子串长度
fori:=0;i<n-l;i++{
j:=i+l//j到达回文子串的最右边
ifl==0{
dp[i][j]=true//单个字符肯定是回文串
}elseifl==1{
dp[i][j]=(s[i]==s[j])
}else{
dp[i][j]=(s[i]==s[j]&&dp[i+1][j-1])//内部是回文串,加上外部一个条件
}
ifdp[i][j]&&l+1>len(max){//如果i到j是回文子串且子串长度大于max
max=s[i:j+1]
}
}
}
returnmax
}
暴力解
func lengthOfLIS(nums []int) int {
res:=0
dp:=make([]int,len(nums))
for i:=0;i<len(nums);i++{
dp[i]=1
for j:=0;j<i;j++{
if nums[i]>nums[j] && dp[j]+1>dp[i]{
dp[i]=dp[j]+1
}
}
if dp[i]>res{
res=dp[i]
}
}
return res
}
二分查找
func lengthOfLIS(nums []int) int {
dp:=[]int{}
for _,num:=range nums{
if len(dp)==0 || dp[len(dp)-1]<num{
dp=append(dp,num) //dp是一个单调递增的序列
}else{
l,r:=0,len(dp)-1
pos:=r
for l<=r{ //二分查找,将值替换到合适的位置
mid:=(l+r)/2
if dp[mid]>=num{
pos=mid
r=mid-1
}else{
l=mid+1
}
}
dp[pos]=num
}
}
return len(dp)
}
funchasCycle(head*ListNode)bool{
ifhead==nil||head.Next==nil{
returnfalse
}
slow,fast:=head,head.Next
forslow!=fast{
iffast==nil||fast.Next==nil{
returnfalse
}
slow=slow.Next
fast=fast.Next.Next
}
returntrue
}
通过哈希表的键占位来实现目的
funchasCycle(head*ListNode)bool{
seen:=map[*ListNode]struct{}{}
forhead!=nil{
if_,ok:=seen[head];ok{
returntrue
}
seen[head]=struct{}{}
head=head.Next
}
returnfalse
}
funcisValid(sstring)bool{
n:=len(s)
ifn%2==n{
returnfalse
}
dic:=map[byte]byte{
')':'(',
']':'[',
'}':'{',
}
stack:=[]byte{}
fori:=0;i<n;i++{
ifdic[s[i]]>0{
iflen(stack)==0||stack[len(stack)-1]!=dic[s[i]]{
returnfalse
}
stack=stack[:len(stack)-1]
}else{
stack=append(stack,s[i])
}
}
returnlen(stack)==0
}
funcaddStrings(num1string,num2string)string{
add:=0
ans:=""
fori,j:=len(num1)-1,len(num2)-1;i>=0||j>=0||add!=0;i,j=i-1,j-1{
varx,yint
ifi>=0{
x=int(num1[i]-'0')
}
ifj>=0{
y=int(num2[j]-'0')
}
result:=x+y+add
ans=strconv.Itoa(result%10)+ans //得到其个位 切记ans要在后面
add=result/10 //进位
}
returnans
}
长度:去掉一层括号剩下的是几部分。
深度:去掉几层括号可以到最后一部分。
比如:例如E((a,(a,b),((a,b),c)))的长度和深度分别为1和4
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R56Jxxsi-1652680479711)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401202326272.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0KjoB3P-1652680479711)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401202337520.png)]
解题思想:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBSdZYZQ-1652680479712)(C:\Users\杰\AppData\Roaming\Typora\typora-user-images\image-20220401202443391.png)]
func main(){
var T,N,M,X,Yint
fmt.Scanln(&T)
fori:=0;i<T;i++{
fmt.Scanln(&N,&M)
node:=make([][]int,N)
fori:=0;i<N;i++{
node[i]=make([]int,N)
}
forj:=0;j<M;j++{
fmt.Scanln(&X,&Y)
node[X-1][Y-1]=1
node[Y-1][X-1]=1
}
list:=[]int{}
fori:=0;i<N;i++{
list=append(list,i)
}
ifask(node,list){
fmt.Println("Yes")
}else{
fmt.Println("No")
}
}
}
fun cask(node[][]int,list[]int)bool{
iflen(list)<3{
returntrue
}
newlist:=[]int{}
newlist=append(newlist,list[0])
list=list[1:]
fori:=len(list)-1;i>=0;i--{ //这里要倒序,因为list是变化的
ifnode[list[i]][newlist[0]]==0{
newlist=append(newlist,list[i])
ifi==len(list)-1{
list=append(list[:i])
}else{
list=append(list[:i],list[i+1:]...) //注意越界
}
}
}
fori:=0;i<len(newlist);i++{
forj:=0;j<len(list);j++{
ifnode[list[j]][newlist[i]]==0{
returnfalse
}
}
}
returnask(node,list)
}
func numIslands(grid [][]byte) int {
rows:=len(grid)
columns:=len(grid[0])
res:=0
for i:=0;i<rows;i++{
for j:=0;j<columns;j++{
if grid[i][j]=='1'{
res++
dfs(grid,i,j)
}
}
}
return res
}
func dfs(grid[][]byte, i,j int) {
rows:=len(grid)
columns:=len(grid[0])
if i>=rows ||i<0 || j<0|| j>=columns {
return
}
if grid[i][j]=='0' {
return
}
grid[i][j]='0'
dfs(grid,i-1,j)
dfs(grid,i+1,j)
dfs(grid,i,j-1)
dfs(grid,i,j+1)
}
func myAtoi(s string) int {
if len(s)<1{
return 0
}
for s[0]==' '&&len(s)>1{
s=s[1:]
}
flag:=false
if s[0]=='-'{
flag=true
s=s[1:]
}else if s[0]=='+'{
s=s[1:]
}
if len(s)<1{
return 0
}
if s[0]<'0'||s[0]>'9'{
return 0
}
n:=len(s)
for i:=1;i<n;i++{
if s[i]<'0'||s[i]>'9'{
s=s[:i]
break
}
}
n=len(s)
flag2:=false
res:=0
for i:=0;i<n;i++{
res*=10
res+=int(s[i]-'0')
if res>math.MaxInt32{
flag2=true
break
}
}
if flag==true{
res=-res
}
if flag2==true{
if flag==true{
return math.MinInt32
}else{
return math.MaxInt32
}
}
return res
}
go循环输入
func main() {
var s int
for {
_,err:=fmt.Scan(&s)
if err!=nil {
break
}
}
fmt.Println(s)
}
go输入带有空格的字符串(大坑)
方法一:读入一行
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
res, _, err := reader.ReadLine()
if err!= nil {
fmt.Println(err)
}
fmt.Println(string(res))
}
方法二:读到回车结束
这个还是蛮好用的,可以指定读到什么结束
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
res,err := reader.ReadString('\n')
if err!= nil {
fmt.Println(err)
}
fmt.Println(string(res))
}
func addStrings(num1 string, num2 string) string {
add:=0 //进位
var res string
for i,j:=len(num1)-1,len(num2)-1;i>=0 ||j>=0||add!=0; i,j=i-1,j-1{
var x,y int
if i>=0{
x=int(num1[i]-'0')
}
if j>=0{
y=int(num2[j]-'0')
}
result:=x+y+add
res=strconv.Itoa(result%10)+res
add=result/10
}
return res
}
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
if root==nil{
return nil
}
if root.Val==p.Val || root.Val==q.Val{
return root
}
left:=lowestCommonAncestor(root.Left,p,q)
Right:=lowestCommonAncestor(root.Right,p,q)
if left!=nil&&Right!=nil{
return root
}
if left==nil {
return Right
}
return left
}
var res [][] int
func permute(nums []int) [][]int {
res=[][]int{}
backtrack(nums,len(nums),[]int{})
return res
}
func backtrack(nums []int,numslen int,path []int) {
if len(nums)==0{
p:=make([]int,len(path)) //path是地址引用,会改变
copy(p,path)
res=append(res,p)
}
for i:=0;i<numslen;i++{ //每次删完元素,添加完之后要加回去
cur:=nums[i]
path=append(path,cur)
nums=append(nums[:i],nums[i+1:]...)
backtrack(nums,len(nums),path)
nums=append(nums[:i],append([]int{cur},nums[i:]...)...)
path=path[:len(path)-1]
}
}
func spiralOrder(matrix [][]int) []int {
rows:=len(matrix) //行
columns:=len(matrix[0]) //列
res:=make([]int,rows*columns) //定义新数组
left,right,top,bottom:=0,columns-1,0,rows-1 //定义初始边界
index:=0
for left<=right && top<=bottom {
for i:=left;i<=right;i++{
res[index]=matrix[top][i]
index++
}
for j:=top+1;j<=bottom;j++{
res[index]=matrix[j][right]
index++
}
if left<right &&top<bottom{
for i:=right-1;i>left;i--{
res[index]=matrix[bottom][i]
index++
}
for j:=bottom;j>top;j--{
res[index]=matrix[j][left]
index++
}
}
left++
right--
top++
bottom--
}
return res
}
func deleteDuplicates(head *ListNode) *ListNode {
if head==nil{
return nil
}
dummy:= &ListNode{0,head}
curr:=dummy
for curr.Next!=nil && curr.Next.Next!=nil{
if curr.Next.Val==curr.Next.Next.Val{
x:=curr.Next.Val
for curr.Next!=nil && curr.Next.Val==x{
curr.Next=curr.Next.Next
}
}else{
curr=curr.Next
}
}
return dummy.Next
}
func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode {
if list1==nil{
return list2
}
if list2==nil{
return list1
}
if list1.Val<list2.Val{
list1.Next=mergeTwoLists(list1.Next,list2)
return list1
}else{
list2.Next=mergeTwoLists(list1,list2.Next)
return list2
}
}
func sortList(head *ListNode) *ListNode {
return sort(head,nil)
}
func sort(head,tail *ListNode) *ListNode{ //分解成最小单位,然后组成有序链表
if head==nil { //空链表直接返回
return head
}
if head.Next==tail{ //两个节点进行拆分
head.Next=nil
return head
}
slow:=head
fast:=head
for fast!=tail{
slow=slow.Next
fast=fast.Next
if fast!=tail{
fast=fast.Next
}
}
curr:=slow
return merge(sort(head,curr),sort(curr,tail))
}
func merge(list1 *ListNode,list2 *ListNode) *ListNode{
if list1==nil{
return list2
}
if list2==nil{
return list1
}
if list1.Val<list2.Val {
list1.Next=merge(list1.Next,list2)
return list1
}else{
list2.Next=merge(list1,list2.Next)
return list2
}
}
func nextPermutation(nums []int) []int {
n:=len(nums)
if n<2 {
return nums
}
num1:=0
for n>1{
if nums[n-1]>nums[n-2]{
num1=n-2
break
}
n--
}
num2:=0
flag:=false
for i:=len(nums)-1;i>=0;i--{
if nums[num1]<nums[i]{
num2=i
flag=true
break
}
}
nums[num1],nums[num2]=nums[num2],nums[num1]
if flag==false{
sort.Ints(nums)
}else{
sort.Ints(nums[num1+1:])
}
return nums
}
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
var head,tail *ListNode
add:=0
for l1!=nil || l2!=nil{
n1,n2:=0,0
if l1!=nil{
n1=l1.Val
l1=l1.Next
}
if l2!=nil{
n2=l2.Val
l2=l2.Next
}
sum:=n1+n2+add
add=sum/10
sum,add=sum%10,add
if head==nil{
head=&ListNode{Val:sum}
tail=head
}else{
tail.Next=&ListNode{Val:sum}
tail=tail.Next
}
}
if add>0{
tail.Next=&ListNode{Val:add}
}
return head
}
func merge(intervals [][]int) [][]int {
res:=[][]int{}
n:=len(intervals)
if n<=1{
return intervals
}
sort.Slice(intervals,func(i,j int) bool{
return intervals[i][0]<intervals[j][0]
})
for i:=0;i<n-1;i++{
if intervals[i][1]>=intervals[i+1][0]{
big:=max(intervals[i][1],intervals[i+1][1])
small:=min(intervals[i][0],intervals[i+1][0])
intervals[i+1]=[]int{small,big}
}else{
res=append(res,intervals[i])
}
if i==n-2{
res=append(res,intervals[n-1])
}
}
return res
}
func max(a,b int)int {
if a>b{
return a
}
return b
}
func min(a,b int) int{
if a<b{
return a
}
return b
}