golang语言面试准备

文章汇总了Golang语言面试必备知识,包括配置管理库Viper、Flask状态监控插件、Linux操作系统命令、并发模型原理及Kubernetes容器技术要点。同时,深入讨论了数据库MySQL与Redis的应用、网络协议理解、以及高并发处理策略,适合准备技术面试的开发者参考。
摘要由CSDN通过智能技术生成

golang语言面试准备

Go语言小知识

viper

用来搞定 Go 应用中配置的库。支持多种配置文件类型、监控并重新加载配置文件、远程读取配置系统等

viper.SetConfigName("config") // 配置文件名,不包括后缀
viper.SetConfigType("yaml") // 配置文件的后缀viper.AddConfigPath("/etc/appname/") // 查找配置文件的目录
viper.AddConfigPath("SHOME/.appname") // 支持查找多个目录
// 异常处理
if err := viper.ReadInConfig(); err != nil {
if_, ok := err.(viper.ConfigFileNotFoundError); ok {// 如果没有找到配置文件
} else {
// 找到了配置文件,但出现了其他错误
}
}

flask-state

一款轻便的机器状态监控 Flask 插件。示例代码:
flask stateinit_app(app)

// npm
import 'echarts';

import 'flask-state/flask-state.min.css'

import finit from 'flask-state'.

// Create a DOM node with ID 'test'. After init()binds the node, click to open the listeningwindow

init({dom:document.getElementByld('test')}):

软链接

ln -s 被链接的源文件 链接文件建立文件的软链接用通俗的方式讲类似于 Windows 下的快捷方式
注意:
1、没有 -s 选项建立的是一个 硬链接文件两个文件占用相同大小的硬盘空间,工作中几乎不会建立文件的硬链接
2、源文件要使用绝对路径不能使用相对路径,样可以方便移动链接文件后,仍然能够正常使用

wait命令 - 等待指令执行完毕语法格式: wait 进程号/作业号常用参数:
NUM 或%NUM进程号 或 作业号
参考实例等待执行的进程结束并输出返回值:
[root@linuxcool~]# wait 12345

与gzip命令相关的Linux命令
bzcat命令 - 读取bzip2压缩文件的内容compress命令 - unix档案压缩命令gunzip命令 - 解压提取文件内容dump命令 -备份文件系统bzip2命令 - bz2文件的压缩程序zip命令 - 压缩文件
znew命令-将.Z压缩包重新转化.gz压缩包zipinfo命令 - 查看压缩文件信息ar命令 - 建立或修改备存文件unzip命令 -解压缩zip格式文件[参数]文件

lsblk命令 - 查看系统的磁盘使用情况

常用的Linux命令

whoami:显示当前登录用户的用户名
groups:显示当前用户所属的用户组。
adduser:创建新用户账户
usermod:修改用户账户的属性
userdel:删除用户账户
passwd:修改用户账户的密码
su:切换到其他用户账户。
chroot:修改根目录并限制用户的访问权限

mount/umount:挂载和卸载文件系统。
ln:创建硬链接或符号链接。
file:识别文件类型

deferpanic、recover三者的用法
defer强数调用的随序是后进先出,当产生panic的时候,会先热行 panic 前面的 defer 虽数后才真的跑出异常。一般的,recover 会在 defer 幽数里执行并捕获异常,防止程序崩溃。

常用的Linux命令
file:识别文件类型
wc:计算文件中的字符数、单词数和行数
tee:将标准输入复制到标准输出和文件中
sed:对文件进行文本替换和编辑
awk:在文本中进行模式匹配和处理
cut:从文本中提取特定列。
sort:对文本进行排序
uniq:去除文本中的重复行
diff: 比较两个文件的差异。
patch:将差异应用到文件中。
rsync:远程同步工具,可在本地和远程系统之间进行文件和目录同步。

常用语句
寻找代码中的错误: govet filename.go
检查代码是否符合规范: golint filename.go
查看代码的内存使用情况:gotoolpprof
显示代码的 CPU 使用情况: go tool pprofhttp=:8080 filename cpu.prof
生成代码的 CPU 使用情况报告: go test -cpuprofile=cpu.prof
查看当前Go环境的版本信息: goversion
显示当前Go环境中所有的环境变量:goenv
查看可用的命令和帮助文档: gohelp
生成代码覆盖率报告:gotest -coverprofile=coV-erage.out

常用的Linux命令
nc/netcat:用于TCP/UDP网络通信的工具,可用于端口扫描、端口监听、数据传输等
dig:用于查询DNS服务器信息,可查看DNS解析、反向解析、SOA记录等
wget:用于从Web服务器下载文件
curl: 用于从Web服务器下载文件,还支持多种协议rsync:用于在本地和远程系统之间同步文件和目录scp:用于在本地系统和远程系统之间复制文件和目录sshfs:用于在本地系统上安装远程文件系统
mount/umount:用于挂载和卸载文件系统
du:查看目录或文件占用磁盘空间的大小。
df:查看磁盘使用情况和可用空间大小。

常用语句
运行代码并生成 CPU profile 报告: go run -cpu-profile=cpu.proffilename.go
生成堆内存分配报告:gofile=mem.prot
test
-mempro
在 Web 浏览器中查看内存分配报告: go toolpprof -http=:8080 filename mem.prof
查看可执行文件中的符号列表: go tool nm filename
检查代码是否有数据竞争问题: go run -race filename.go
启用Go模块支持:export GO111MODULE=on
打开源代码文件:在终端中输入 go toolpproifilename cpu.prof,然后输入 web 命令,就可以在浏览器中打开源代码文件。

Go 语言的代码文件以.go 结尾
Go 语言的注释使用//或者 /**/。
Go语言中的函数名首字母大写表示该函数是可导出的(即可供其他包调用)。
Go 语言中的 main()函数是程序的入口函数
Go 语言支持指针,但是不支持指针运算
Go语言中的 defer 关键字用于延迟执行函数调用,常用于关闭文件、解锁资源等操作。
Go 语言中的 panic))和 recover()函数用于处理异常。
Go 语言中的interface 类型可以表示任何类型。

Go 语言中的 map类型是无序的键值对集合

类似于Python 中的字典
Go语言中的chan 类型是一种用于在协程之间传递数据的通道类型。
Go 语言的并发模型使用协程 (goroutine)实现,协程是一种轻量级的线程,可以在一个线程中并发执行多个任务。
Go语言的内存分配是自动的,使用垃圾回收机制回收不再使用的内存。
Go语言支持多种操作系统和硬件平台
Go 语言的标准库中包含丰富的功能模块,包括网络、文件、加密、图像处理等模块。
Go 语言的函数可以返回多个值,这对于函数返回错误码和错误信息等场景非常有用。

tar:用于打包和解压缩文件
gzip:用于压缩文件
gunzip:用于解压缩文件
bzip2:用于压缩文件
bunzip2:用于解压缩文件。
zip:用于压缩文件。
unzip:用于解压缩zip格式的文件
ssh-keygen:用于生成SSH密钥对
ssh-copy-id:用于将本地SSH公钥复制到远程主机上。

Go语言的切片(slice) 是一个动态数组,支持类似Python 中的切片操作。
Go语言支持函数式编程范式,例如使用函数作为参数传递、使用匿名函数等。
Go语言支持 defer、panic和 recover 等语言级别的错误处理机制,可以让代码更加健壮。
Go语言的函数可以使用可变参数,例如func f(a ...int)。
Go语言支持标准的JSON 编解码库,可以方便地处理JSON格式的数据。
Go语言的字符串是不可变的,如果需要修改字符串,需要将其转换成字节数组后进行修改。

Go语言的反射 (reflect) 库中常用的方法

Method(i int)Method:获取结构体的第i个方法;
MethodByName(string) (Method,bool):根据名称获取结构体的方法;
NumMethod(int:获取结构体的方法个数Name()string:获取结构体的名称;PkgPath()string:获取结构体的包路径size()uintptr:获取结构体占用的内存大小String()string:获取结构体的字符串类型Kind()Kind:获取结构体的具体类型
Implements(u Type) bool: 判断结构体是否实现了某个接口;

AssignableTo(u Type) bool:判断不同类型之间是否可以赋值;
ConvertibleTo(u Type) bool: 判断不同类型之间是否可以转换;
Elem()Type:获取结构体的完整类型信息
FieldBylndex(index  int) StructField:根据index路径获取嵌套的成员;
FieldByName(name string) (StructFieldbool):根据名称获取结构体的成员;
Len()int:获取容器的长度
NumIn()int:获取函数参数的个数
NumOut()int:获取函数返回值的个数

关于 Go 语言的小知识

Go语言中使用:=来定义变量,会自动推导变量类型,例如:i:= 10会被推导为int类型
Go语言中的切片是对数组的一种封装,可以动态地增加或删除元素,可以通过append()函数添加元素,通过copy()函数复制元素
Go语言中的defer关键字用于注册延迟调用即在函数执行结束之前执行一些必要的清理工作,比如关闭文件或释放锁等。defer语句会在函数退出时执行,即使函数发生了错误也会执行。
Go语言中的panic()函数用于引发运行时错误,通常用于不可恢复的错误,例如数组越界或除以零等。recover()函数用于从panic中恢复,可以在defer语句中使用。
Go语言中的range关键字可以用于迭代数组、切片、映射、通道和字符串等类型,它会返回每个元素的索引和值。

Go 语言中的错误处理 (error handling)是通过返回值和错误类型来实现的,通常使用error类型来表示错误,使用nil表示无错误。可以使用if err != nil语句来判断是否发生了错误
Go语言中的结构体(struct) 是一种自定义的复合类型,可以包含不同类型的字段,类似于C 语言中的结构体。
Go 语言中的接口 (interface)定义了一组方法的集合,一个类型只要实现了接口中的所有方法,就被视为实现了该接口。接口可以实现多态的特性。
Go 语言中的类型断言 (type assertion)可以将一个接口类型的值转换为具体的类型。
Go 语言中的空接口(empty interface)in-terfacef可以表示任意类型的值,类似于 C语言中的void*。

Go语言中的包(package) 是组织代码的基本单元,可以通过import关键字引入其他包的功能。
Go语言中的测试(testing)是通过编写测试函数和使用go test命令来实现的,可以对函数、方法和包进行单元测试。
Go语言中的错误处理通常使用error类型来表示错误,可以通过返回error类型的值来表示函数执行的结果是否成功。
Go语言中的文件操作可以通过os和io包来实现,包括文件的打开、读取、写入、关闭等操作。
Go语言中的并发编程可以使用协程(gorou-tine)和通道(channel) 来实现,可以使用go关键字启动一个协程,并使用通道进行协程间的通信。

Go语言中的标准库提供了丰富的功能模块,包括网络编程、文件操作、加密解密、日期时间处理、正则表达式等。
语言中的range关键字可以用于遍历数GO组、切片、映射、通道和字符串等数据结构,它会返回索引和对应的值。
Go语言中的错误类型实现了error接口,可以通过自定义错误类型来实现更丰富的错误信
Go语言中的goto语句可以用来无条件地跳转到代码中的指定标签,但是应该谨慎使用,避免引入混乱的控制流
Go语言中的range关键字在遍历切片或数组时会返回每个元素的副本,而在遍历映射时会返回键和值的副本。

Go语言中的函数可以作为值进行传递和赋值,可以将函数作为参数传递给其他函数,也可以将函数作为返回值。
Go语言中的匿名函数可以在函数内部定义,没有函数名,并且可以直接调用或赋值给变量
Go语言中的指针类型可以通过使用&操作符获取变量的内存地址,使用*操作符来获取指针指向的值。
Go语言中的常量可以通过const关键字定义,常量的值在编译时确定,并且不能被修
Go语言中的包可以通过init()函数来实现包的初始化操作,init)函数会在包被导入时自动调用。

Golang面试总结(六) 

对已经关闭的channel进行读写,会怎么样?

当channel被关闭后,如果继续往里面写数据,程序会直接panic退出。如果是读取关闭后的channel,不会产生panic,还可以读到数据。但是关闭后的channel没有数据可读取时,将得到零值,即对应类型的默认值。

为了能知道当前channel是否被关闭,可以使用下面的写法来判断。

if v, ok := <= ch; !ok {// 从channel中读数据,ok表示是否读到数据,ok为false表示未读到数据
    fmt.Println("channel 已关闭,读取不到数据")
}

还可以使用下面的写法不断的获取channel里的数据:

for data := range ch {
    // get data dosomething
}

这种用法会在读取完channel里的数据后就结束for循环,执行后面的代码。

linux打包压缩命令

tar [-zcxvf] fileName [files]

复制文件或目录-cp

cp [-r] source dest

slice 的扩容机制
如果slice要扩容的容量大于2当前的容量,则直接按想要扩容的容量来new一个新的Slice,否则继续判断当前的长度len,如果len小于1024,则直接按2倍容量来扩容,则一直循环新增1/4,直到大于想要扩容的容量。主要代码如下:

除此之外,还会根据 slice 的类型做一些内存对齐的调整,以确定最终要扩容的容量大小。

waitgroup 原理
waitgroup内部维护了一个计数器,当调用wg.Add(1)方法时,就会增加对应的数量,当调用 wg.Done()时,计数器就会减一。直到计数器的数量减到0时,就会调用 runtime Semrelease 唤起之前因为 wg.Wait() 而阻塞住的goroutine。
gorouinte 泄漏有哪些场景
gorouinte 里有关于 channel的操作,如果没有正确处理 channel 的读取,会导致 channel 一直阳塞住,goroutine不能正常结束

Linux系统信息查看
查看发行版本
cat /etc/issue
cat /proc/version
cat /etc/redhat-release
查看内核版本
uname-a
uname-r 查看核心版本
uname-m查看操作系统的位版本
lsb release-a
查看主机名:hostname
查看当前用户:whoami
查看用户信息:id或finger用户名
查看路由表:route
查看所有进程:psaux
实时查看进程:top

context 是如何一层一层通知子 context
当 ctx,cancel := context.WithCancel(父Context)时,会将当前的 ctx挂到父 context 下,然后开个goroutine协程去监控父 context的channel事件一旦有 channel通知,则自身也会触发自己的 chan-nel去通知它的子context,关键代码如下

map 的 key 为什么得是可比较类型的?
map的 key、value 是存在 buckets 数组里的,每个 bucket又可以容纳8个key和8个value。当要插入一个新的 key-value时,会对 key 进行hash 运算得到一个hash 值,然后根据hash值的低几位(取几位取决于桶的数量,比如一开始桶的数量是5,则取低5位)来决定命中哪个 bucket
在命中某个bucket后,又会根据hash 值的高8位来决定是8个key里的哪个位置。如果不巧,发生了hash 冲突,即该位置上已经有其他 key存在了,则会去其他空位置寻找插入如果全都满了,则使用overflow 指针指向一个新的 bucket重复刚刚的寻找步骤。
从上面的流程可以看出,在判断 hash 冲突,即该位置是否已有其他 key 时,肯定是要进行比较的,所以 key 必须得是可比较类型的。像slice、map、function 就不能作为 key。 

如果文件不存在,如何创建?

io.Writer是golang中接口类型的一种典型的运用Fprintf=>(Printf,SPrintf)
Stringer接口通常用于对于printf或者println等一些列的函数数输出
golang中的数组array为定长数组,如两个数组相比较必须是comparable即长度和类型必须一致
golang语言定义变量或者结构体在编译期就已经复制在运行期赋值,需要重新定义一个函数
golang中flag库需要使用flag.parse)来传递参数buffer channel在channel中的元素个数小于channel
size时为非阻塞的,达到channelsize之后则阻塞当前线程直到元素被取出.
o buff channel即为sync channel.生产者和消费者都会被阻塞.
golang中对比并行程序设计有: 设置返回channel,以经典的生产者消费者模式进行处理.
timer主要是用来设置定时任务tickers主要是用来设置周期任务

channel比较重要的结构
channel有一块缓存环状线性链表,锁,容量,进出的索引值和阻塞的协程列表(分为取或塞的两种协程列表)。chan操作环状线性链表都需加锁,chan通过索引值判断能取或者塞是否能进行,当不能取或塞时,就用阻塞的协程列表保存当前协程。比如当chan中没有可取的元素时,执行取操作,会阻塞当前协程,将协程加入到chan的取阻寨协程列表。当有一个协程执行塞的操作,chan会从取阻塞列表中取一个协程,将其放入就绪协程队列,以此来唤醒协程
channel重要的是阻塞协程列表和索引,用于在常数时间内实现chan的取和塞的操作。一索引,chan缓存是一个线性结构,通过索引*每个元素大小就能在缓存中找到对应的取塞位置。二通过存储阻塞协程,能迅速唤醒协程。

cobra
当下最流行的GOCLI框架。功能强大且极易上手的Go语言 CLI库,可用于快速构建命令行程序,被 K8s、HugoGitHub CLI 等众多知名 Go 项目所采用,支持自动提示自动构建项目、嵌套子命令等功能。

分代收集
该算法基于一个经验:绝大多数的对象生命周期都很短,按照对象生命周期的长短来分代。一般分代GC,分为3代,新生代(Young Generation),年老代(TenuredGeneration),永久代(Permanent Generation)原理如下:
1.新对象放入第0代
2.当内存用量超过一个较小的闯值时,触发0代收集
3.第0代幸存的对象(未被收集)放入第1代
4.只有当内存用量超过一个较高的闯值时,才会触发1代收集,2代同
理。
因为0代中的对象十分少,所以每次收集时遍历都会非常快 (比1代收集快几个数量级)。只有内存消耗过于大的时候才会触发较慢的1代和2代收集。 

给struct设置默认值 

临时粘合两个struct

  

Go语言小知识

Go 语言中的空接口(Empty Interface)interface{}可以表示任意类型,可以在运行时接收和存储任何值。
Go 语言中的go关键字可以用于启动一个新的协程(Goroutine),用于并发执行函数。
Go 语言中的标准库提供了丰富的网络编程支持,包括 net包用于网络操作、http包用于 HTTP 通信、rpc包用于远程过程调用等。
Go 语言中的指针可以使用new()函数来动态分配内存,返回指向新分配的零值的指针。
Go 语言中的break关键字可以用于终止for、switch和 select语句的执行,并跳出当前的循环或语句块。

Go 语言中的结构体字段可以使用标签(Tag)来进行元数据的注释和描述,可以通过反射
(Reflection)获取结构体字段的标签信息。
Go 语言中的错误类型实现了error接口,可以通过实现自定义的错误类型来提供更具体的错误信息。
Go 语言中的类型别名(Type Alias)可以用来为现有的类型创建一个新的名称,可以提高代码的可读性。
Go 语言中的断言(Assertion)可以使用.(T)的语法将一个接口类型的值转换为指定的类型T,并检查转换是否成功。

Go 语言中的切片(Slice)是对数组的抽象,可以动态地调整长度,使用切片可以更方便地进行元素的增删改操作。
Go 语言中的递归函数可以调用自身,可以用于解决需要重复执行相同任务的问题,但需要注意递归深度的限制。
Go 语言中的range关键字还可以用于遍历通道(Channel),可以在通道关闭前遍历通道的所有值。
Go 语言中的函数可以返回多个值,可以通过使用括号和逗号来指定多个返回值的类型。

Go语言中的select语句用于处理多个通道的并发操作,可以用于读取或写入通道,等待任意一个通道就绪。
Go 语言中的接口类型可以嵌套,一个接口可以作为另一个接口的嵌入字段,从而组合多个接口的方法。
Go 语言中的方法集决定了一个类型可以调用哪些方法,类型的方法集分为值接收者方法集和指针接收者方法集。
Go 语言中的标准库提供了丰富的并发编程支持,包括sync包中的互斥锁、读写锁、条件变量等,以及 atomic包中的原子操作。

Go    语言中的字符串可以使用双引号"或反引号`来定义,反引号字符串可以包含多行文本和特殊字符而无需转义
Go 语言中的map是一种无序的键值对集合,可以通过make()函数创建,使用[key] = value语法进行元素的增加、修改和删除
Go 语言中的包级别变量可以被整个包内的函数和方法共享,可以在包级别使用var关键字进行定义

Go 语言中的方法是一种特殊类型的函数,与某个类型关联,可以通过类型的实例来调用

Go 语言中的标准库提供了丰富的功能模块,包括网络编程、文件操作、加密解密、日期时间处理、正则表达式等。
Go 语言中的range关键字可以用于遍历数组、切片、映射、通道和字符串等数据结构,它会返回索引和对应的值。
Go语言中的错误类型实现了error接口,可以通过自定义错误类型来实现更丰富的错误信息。
Go语言中的goto语句可以用来无条件地跳转到代码中的指定标签,但是应该谨慎使用,避免引入混乱的控制流。
Go 语言中的range关键字在遍历切片或数组时会返回每个元素的副本,而在遍历映射时会返回键和值的副本。

Go 语言中的map 类型是无序的键值对集合,类似于Python中的字典。
Go语言中的chan类型是一种用于在协程之间传递数据的通道类型。
Go语言的并发模型使用协程(goroutine)实现,协程是一种轻量级的线程,可以在一个线程中并发执行多个任务。
Go语言的内存分配是自动的,使用垃圾回收机制回收不再使用的内存。
Go语言支持多种操作系统和硬件平台。
Go语言的标准库中包含丰富的功能模块,包括网络、文件、加密、图像处理等模块。
Go语言的函数可以返回多个值,这对于函数返回错误码和错误信息等场景非常有用。

Golang面试总结(五)

go的内存分配是怎么样的?

Go的内存分配借鉴了Google的TCMalloc分配算法,其核心思想是内存池+多级对象管理,内存池主要是预先分配内存,减少向系统申请的频率;多级对象有:mheap、mspan、arenas、mcental、mcache。它们以mspan作为基本分配单位。具体的分配逻辑如下:

当要分配大于32K的对象时,从mheap分配。

当要分配的对象小于等于32K大于16B时,从P上的mcache分配,如果mcache没有内存,则从mcental获取,如果mcental也没有,则向mheap申请,如果mheap也没有,则从操作系统申请内存。

当要分配的对象小于等于16B时,从mcache上的微型分配器上分配。

分页显示文件内容-more

more fileName

Go 语言中的通道 (Channel) 可以使用make()函数来创建,通道可以用于在协程之间进行数据传输和同步操作
Go 语言中的select语句可以用于处理多个通道的读写操作,可以选择第一个就绪的通道进行操作。
Go 语言中的defer语句可以用于延迟执行函数调用,无论函数是正常返回还是发生错误,defer语句都会执行
Go 语言中的range关键字可以用于遍历数组、切片、映射和通道等数据结构。
Go 语言中的接口(lnterface) 是一种约定,用于指定类型应该具有的方法集合,可以通过类型实现接口。
Go 语言中的结构体 (Struct) 是一种复合类型,可以包含多个字段,可以通过.运算符来访问结构体的字段。

Go 语言中的类型断言 (Type Assertion)可以将一个接口类型的值转换为其他具体类型的值,并进行类型检查。
Go 语言中的结构体可以使用匿名字段来实现嵌入其他结构体,从而可以直接访问嵌入结构体的字段和方法。
Go 语言中的并发编程可以使用通道 (channel) 来实现协程之间的通信和同步,通道可以在协程之间传递数据
Go 语言中的函数可以返回多个值,可以使用命名返回值来使函数的返回值具有描述性,并且可以直接使用返回值进行赋值。
Go 语言中的切片 (slice)是对数组的一个动态视图,可以通过切片来对底层数组进行操作,切片本身不存储数据。
Go 语言中的常量可以使用iota和const关键字来定义iota在常量声明中被隐式地赋予递增的整数值。

go实现归并排序,将两个有序链表合并为一个有序链表

package main

import (
	"fmt"
)

type LinkNode struct {
	Val  int
	Next *LinkNode
}

func mergeTwoLists(l1 *LinkNode, l2 *LinkNode) *LinkNode {
	// 创建一个新的头结点
	head := &LinkNode{}
	// 定义指针cur指向head,用于合并后的链表
	cur := head

	// 遍历l1和l2,比较结点的值,将较小的结点添加到合并后的链表中
	for l1 != nil && l2 != nil {
		if l1.Val <= l2.Val {
			cur.Next = l1
			l1 = l1.Next
		} else {
			cur.Next = l2
			l2 = l2.Next
		}
		cur = cur.Next
	}

	// 将剩余的结点添加到合并后的链表中
	if l1 != nil {
		cur.Next = l1
	} else {
		cur.Next = l2
	}
	return head.Next
}

func printList(l *LinkNode) {
	for l != nil {
		fmt.Printf("%d ", l.Val)
		l = l.Next
	}
	fmt.Println()
}
func main() {
	// 创建两个链表
	l1 := &LinkNode{Val: 1}
	l1.Next = &LinkNode{Val: 2}
	l1.Next.Next = &LinkNode{Val: 4}

	l2 := &LinkNode{Val: 1}
	l2.Next = &LinkNode{Val: 3}
	l2.Next.Next = &LinkNode{Val: 4}

	// 合并两个链表
	merged := mergeTwoLists(l1, l2)

	// 打印合并后的链表
	printList(merged)
}

以上代码首先定义了一个LinkNode结构体,表示链表的结点。然后实现了一个mergeTwoLists函数,用于将两个有序链表合并为一个有序链表。在函数中,我们创建了一个新的头结点head和一个用于遍历的变量cur。最后将剩余的结点添加到合并的链表中。最后,我们在main函数中创建两个链表l1和l2,并调用mergeTwoLists函数进行合并,并通过printList函数打印合并后的链表。 

go实现归并排序

归并排序(Merge Sort)是一种稳定的排序算法,它采用分治法的思想,将待排序的数组不断分割成两个子数组,直到每个子数组只有一个元素,然后将两个子数组合并成一个有序的数组,最终得到排序结果。

下面是用Go语言实现归并排序的示例代码:

package main

import (
	"fmt"
)

// 归并排序
func MergeSort(arr []int) []int {
	if len(arr) <= 1 {
		return arr
	}
	// 将数组分成两个子数组
	mid := len(arr) / 2
	left := MergeSort(arr[:mid])
	right := MergeSort(arr[mid:])

	// 合并两个子数组
	return merge(left, right)
}

// 合并两个有序数组
func merge(left, right []int) []int {
	result := make([]int, 0)

	// 两个子数组的起始索引
	i, j := 0, 0

	// 比较两个子数组的元素,将较小的元素加入到结果数组中
	for i < len(left) && j < len(right) {
		if left[i] <= right[j] {
			result = append(result, left[i])
			i++
		} else {
			result = append(result, right[j])
			j++
		}
	}
	// 将剩余的元素加入到结果数组中
	for i < len(left) {
		result = append(result, left[i])
		i++
	}

	for j < len(right) {
		result = append(result, right[j])
		j++
	}
	return result
}

func main() {
	arr := []int{5, 3, 8, 6, 2, 7, 1, 4}
	sortedArr := MergeSort(arr)
	fmt.Println(sortedArr) // 输出: [1 2 3 4 5 6 7 8]
}

 在上述代码中,MergeSort函数使用递归的方式将数组分成两个子数组,并分别对子数组进行排序。然后通过调用merge函数将两个有序的子数组合并成一个有序的数组。

merge函数中使用两个指针i和j分别指向两个子数组的起始位置,通过比较两个子数组的元素大小,将较小的元素加入到结果数组中,直到一个子数组的元素全部比较完毕。然后将剩余的元素加入到结果数组中。

最后,在main函数中调用MergeSort函数对数组进行排序,并输出排序结果。

并发编程: Go语言在语言级别上原生支持并发编程,通过goroutines和channels实现轻量级的并发操作。这使得编写高效且并发安全的代码变得简单。
垃圾回收: Go语言拥有自动垃圾回收 (GarbageCollection)功能,开发者不需要手动管理内存,使得编码更为简便。
静态类型语言: Go是一种静态类型语言,变量在编译时就需要指定其类型,并且类型检查是在编译期进行的。
编译型语言: Go语言是编译型语言,源代码需要先编译成机器码后才能执行。
简洁而高效:Go语言设计简洁,注重代码的可读性和易于维护。同时,Go的编译器和运行时都相对快速。
包管理: Go语言使用go命令来管理包和依赖,它将包放置在工作空间内,并鼓励代码的单一工作空间结构。

包管理: Go语言使用go命令来管理包和依赖,它将包放置在工作空间内,并鼓励代码的单一工作空间结构。
面向接口编程: Go语言鼓励使用接口(interfaces) 来实现面向接口编程,这增加了代码的灵活性和可扩展性。
丰富的标准库: Go标准库提供了广泛且强大的功能,包括网络编程、文件操作、加密、并发等,开发者可以充分利用这些标准库,减少重复造轮子的工作。
跨平台支持: Go语言支持跨平台编译,可以轻松地在不司的操作系统上编译和运行代码
开放源代码: Go语言是一种开源语言,其源代码在GitHub上公开并得到活跃的社区支持。
Go工具链: Go语言提供了强大的工具链,包括格式化代码、单元测试、性能分析等工具,使得开发者能够高效地开发、测试和优化代码。

Golang的面试总结(四)

Go的垃圾回收机制?

GMP模型是golang自己的一个调度模型,它抽象出了下面三个结构:

白色对象:未被使用的对象。

灰色对象:当前对象有引用对象,但是还没有对引用对象继续扫描过;

黑色对象:对上面提到的灰色对象的引用对象已经全部扫描过了,下次不用在扫描它了。

当垃圾回收开始时,Go会把根对象标记为灰色,其他对象标记为白色,然后从根对象遍历搜索,按照上面的定义去不断的对灰色对象进行扫描标记,当没有灰色对象时,表示所有对象已扫描过,然后就可以开始清除白色对象了。

Go 语言中的包 (Package)是一种代码的组织方式,可以将相关的代码放在一个包中,并通过包名进行访问
Go 语言中的并发模型使用协程 (Goroutine) 来实现并发执行,协程之间通过通道进行通信。
Go语言中的time包提供了用于处理时间和日期的函数和类型,可以进行时间的解析、格式化和计算等操作。
Go语言中的错误处理习惯是将错误作为函数的最后一个返回值,并约定使用error类型作为错误的表示。
Go 语言中的map是一种无序的键值对集合,可以通过键来快速查找对应的值,使用make()函数来创建map。
Go 语言中的defer语句在函数结束时才会执行,可以用于资源的释放和清理操作,类似于其他语言中的finally块。

Go 语言中的switch语句可以用于多个条件的判断,可以使用case语句来匹配不同的条件分支。
Go 语言中的错误处理通常使用if err != nil的方式来检查函数调用是否发生错误,并根据错误进行相应的处理。
Go 语言中的iota常量生成器可以在常量声明中创建自增的枚举值,每遇到一个常量声明,iota的值会自动递增。
Go 语言中的函数可以作为值进行传递和赋值,可以将函数作为参数传递给其他函数,也可以将函数作为返回值
Go 语言中的for循环可以使用range关键字来迭代数组切片、映射、通道等数据结构。
Go 语言中的类型断言 (Type Assertion)可以用于将接门类型的值转换为其他具体类型的值,并进行操作。

Go 语言中的switch语句可以用于多个条件的判断,可以使用case语句来匹配不同的条件分支
Go 语言中的错误处理通常使用if err != nil的方式来检查函数调用是否发生错误,并根据错误进行相应的处理。
Go 语言中的iota常量生成器可以在常量声明中创建自增的枚举值,每遇到一个常量声明,iota的值会自动递增。
Go 语言中的函数可以作为值进行传递和赋值,可以将函数作为参数传递给其他函数,也可以将函数作为返回值
Go 语言中的for循环可以使用range关键字来迭代数组切片、映射、通道等数据结构。
Go 语言中的类型断言 (Type Assertion)可以用于将接口类型的值转换为其他具体类型的值,并进行操作。

Go语言中的go关键字可以用于启动一个新的协程(Goroutine),实现并发执行多个任务。
Go 语言中的字符串可以使用+运算符进行拼接,也可以使用fmt.Sprintf()函数进行格式化。
Go 语言中的方法集决定了一个类型可以调用哪些方法方法集分为值接收者方法集和指针接收者方法集,它们决定了方法的接收者可以是类型的值还是指针。
Go 语言中的接口可以嵌套定义,一个接口可以包含其他接口作为它的方法集的一部分。
Go 语言中的包可以通过import关键字导入,可以使用.操作符来使导入的包中的函数可以直接调用,也可以使用别名来重命名导入的包。
Go 语言中的函数可以使用可变参数,通过在参数类型前加上...来表示可变参数,可以传入任意数量的参数

Go 语言中的类型断言 (Type Assertion)可以将一个接口类型的值转换为其他具体类型的值,并进行类型检查。
Go 语言中的结构体可以使用匿名字段来实现嵌入其他结构体,从而可以直接访问嵌入结构体的字段和方法。
Go 语言中的并发编程可以使用通道 (channel) 来实现协程之间的通信和同步,通道可以在协程之间传递数据
Go 语言中的函数可以返回多个值,可以使用命名返回值来使函数的返回值具有描述性,并且可以直接使用返回值进行赋值。
Go 语言中的切片 (slice)是对数组的一个动态视图,可以通过切片来对底层数组进行操作,切片本身不存储数据。
Go 语言中的常量可以使用iota和const关键字来定义iota在常量声明中被隐式地赋予递增的整数值。

Go语言中的包级别常量和变量可以在程序启动时自动初始化,如果没有显式指定初始值,则会使用其类型的零值进行初始化。
Go 语言中的new函数用于创建一个指向新分配的零值的指针,可以用于实例化结构体和其他复杂类型。
Go 语言中的switch语可以不带表达式,用于实现更复杂的条件逻辑,类似于其他语言中的if-else if-else结构
Go 语言中的recover函数用于从panic中恢复,只有在延迟函数中调用recover才有效。
Go 语言中的函数可以作为闭包 (closure)使用,闭包是一个函数值,它可以引用其函数体之外的变量
Go 语言中的类型断言 (Type Assertion)可以判断一个接口值是否实现了特定接口,以及转换为该接口类型

Golang面试总结(三)

goroutine的协程有什么特点,和线程相比?

goroutine非常的轻量,初始分配只有2KB,当栈空间不够用时,会自动扩容。同时,自身存储了执行stack信息,用于在调度时能恢复上下文信息。

而线程比较重,一般初始大小有几MB(不同系统分配不同),线程是由操作系统调度,是操作系统的调度基本单位。而golang实现了自己的调度机制,goroutine是它的调度基本单位。

linux进入指定目录-cd

cd [dirName]

如果文件不存在,go语言如何创建?

import (
    "OS"
    "path/filepath"
)

newpath := filepath.join(".","public")
err := os.MkdirAll(newpath,os.ModePerm)
//TODO:handler error

linux目录中搜索文件(find)

find [path][options][expression]

golang如何获取当前运行的程序路径?

package main
import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    ex, err := os.Executable()
    if err != nil {
        panic(err)
    }
    exPath := filepath.Dir(ex)
    fmt.Println(exPath)
}

linux搜索特定内容(grep)

grep的全称是Global Regular Expression Print

golang如何打印struct结构体的数据信息到控制台中?

type Project strcut {
    Id    int64    `json:"project_id"`
    Title    string    `json:"title"`
    Name    string    `json:"name"`
    Data    Data    `json:"data"`
    Commits    Commits    `json:"commits"`    
}

fmt.Printf("%+v\n",Project)

golang:%v,%+v,%#v区别

%v 按默认格式输出,
%+v 在%v的基础上额外输出字段名,
%#v 在%+v的基础上额外输出类型名。

linux判断文件类型(file)

file [optiions] 文件名
-v -z -L -f name

python链式函数

可以在一行里面调用多个函数

def add(a,b):
    return a+b

def subtract(a,b):
    return a-b

a,b = 4,5
print((subtract if a>b else add)(a,b)) #9

linux快速复制、设置属性和备份文件(install)

python差值函数

下面方法在对两个列表的每个元素应用给定函数后,返回两个列表的差值。

def differencence_by(a,b,fn):
    b = set(map(fn,b))
    return [item for item in a if fn(item) not in b]

from math import floor
difference_by([2.1,2.2],[2.3,3.4],floor) #[1.2]
difference_by([{'x':2},{'x':1}],[{'x':1}],lambda v :v['x']) # [{x:2}]

python 数组转置

巧用zip()对一个二维数组进行转置。

array = [['a','b'],['c','d'],['e','f']]

transposed = zip(*array)

print(list(transposed))
#[('a','c','e'),('b','d','f')]

array = [['a','b','c'],['d','e','f']]
transposed = zip(*array)
print(list(transposed))
# [('a','d'),('b','e'),('c','f')]

linux复制文件到多个目录

echo /home/user/1/ /home/user/2/ /home/user/3/ |xargs -n 1 cp /home/user/my_file.txt

golang短变量声明

每次使用变量时都要先进行函数声明,我们可以使用name := expression的语法形式来声明2和初始化局部变量,相比使用var声明的方式可以减少声明的步骤:

var a int = 10

a := 10

使用短变量声明时有两个注释事项:

短变量声明只能在函数内使用,不能用于初始化全局变量

短变量声明代表引入一个新的变量,不能在同一作用域重复声明变量

多变量声明中如果其中一个变量是新变量,那么可以使用短变量声明,否则不可以重复声明变量。

linux用一个命令创建目录树

mkdir new_folder

mkdir -p new_folder/{folder_1,folder_2,folder_3,folder_4,folder_5}

Go高并发高可用分布式

并发和并行

进程和线程

由来

早期工作方式

多道处理程序

分时系统

进程和线程概念

形象化理解

线程的状态

Python中的进程和线程

Python的线程开发

Thread类

线程启动

线程退出

线程的传参

多线程编程

TCP编程

socket编程

CS编程

TCP服务端编程

服务器端编程步骤

实战:实现WEB服务器--多线程阻塞IO版webserver_multithread.py

import threading
import time
import socket

html = """\
HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:11:20 GMT
Context-Type: text/html
Content-Length: 302
Connection: keep-alive
Server: wu123.xiaofeng.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>云原生www.xiaofeng.com-multithread</h1>
</body>
</html>\
"""

# Web服务器,HTTP,TCP,纯文本
# 1 监听座,门童
server = socket.socket() # TCP ipv4
laddr = ('0.0.0.0', 9999)
server.bind(laddr)
server.listen(1024)

def recv(conn, raddr):
    print("我等你发数据")
    while True:
        try:
            data = conn.recv(4096) # 默认阻塞方法,会导致主线程卡住,进入阻塞态
            if not data:
                break
            print(raddr, data)
            conn,send(html.encode()) # utf-8
        except Excaption as e :
            print(e)

def accep():
    while True:
        # conn 服务员socket
        newsock, raddr = server.accept() # 默认阻塞,迎宾指派服务员以后和您单独服务
        print(newsock, '~~~~~~~~~') # fd文件描述符
        # while True:
        #     data = newsock.recv(4096)
        threading.Thread(target=recv, name="recv", args=(newsock, raddr)).start()

if __name__ == '__main__':
    threading.Thread(target=accept, name="accept").start()

    while True:
        # 我是主线程,没人阻塞我了,我很高兴,我要监管他们
        time.sleep(5)
        print(threding.enumerate())


线程的状态切换:就绪、运行、终止、阻塞

socket对象-->bind((ip,port))-->listen-->accept-->close

                                                                      |--> recv or send --> close

目标:了解Goroutine以及背后的多线程、IO多路复用

并发、串行、交替、队列都是高并发的解决方案

进程、线程

发起的web请求,是IO请求,会进入阻塞态

线程池、IO多路复用

频繁创建销毁线程,代价不小--> 线程池

线程,内核态、用户态之间切换,保护现场、恢复现场的

操作系统有办法:非阻塞IO(不就绪抛异常)、IO多路复用

同步  一定要在这一次拿到最终成果。为了一定拿到,你可能阻塞、也可能非阻塞

异步

不一定要最终成果。你可以给我个存根。

实战:实现web服务器--线程池版

实现多线程加阻塞IO版本

一个客户端请求到达后端,开启一个线程为之服务

线程内运行函数代码,接收HTTP请求并解析,返回HTTP响应报文

问题

大量的线程为HTTP连接服务,用完就断,而创建和销毁线程的代价加高

解决方案就是利用线程池

如果拥有海量线程来处理并发客户端请求,线程调度时上下文切换将给系统造成巨大的性能消耗

我们用python高级异步线程池ThreadPoolExecutor来改造代码。

webserver_threadpool.py

import threading
import time
import socket
from concurrrent.futures import ThreadPoolExecutor,
html = """\
HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:11:20 GMT
Context-Type: text/html
Content-Length: 301
Connection: keep-alive
Server: wu123.xiaofeng.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>云原生www.xiaofeng.com-threadpool</h1>
</body>
</html>\
"""



def recv(conn, raddr):
    print("我等你发数据")
    while True:
        try:
            data = conn.recv(4096) # 默认阻塞方法,会导致主线程卡住,进入阻塞态
            if not data:
                break
            print(raddr, data)
            conn,send(html.encode()) # utf-8
        except Excaption as e :
            print(e)

def accep(sock):
    while True:
        # conn 服务员socket
        newsock, raddr = sock.accept() # 默认阻塞,迎宾指派服务员以后和您单独服务
        print(newsock, '~~~~~~~~~') # fd文件描述符
        # while True:
        #     data = newsock.recv(4096)
        #threading.Thread(target=recv, name="recv", args=(newsock, raddr)).start() # n个
        pool.submit(recv,newsock,raddr)

if __name__ == '__main__':
    pool = ThreadPoolExecutor()

    # Web服务器,HTTP,TCP,纯文本
    # 1 监听座,门童
    server = socket.socket() # TCP ipv4
    laddr = ('0.0.0.0', 9999)
    server.bind(laddr)
    server.listen(1024)

    # threading.Thread(target=accept, name="accept").start() # 1个
    pool.submit(accept,server)    

    while True:
        # 我是主线程,没人阻塞我了,我很高兴,我要监管他们
        time.sleep(5)
        print(threding.enumerate())
import string
import threading
import time

def count():
    c = 1
    while True:
        time.sleep(1)
        print("count:", c)
        c += 1

def char():
    s = string.ascii_lowercase
    for c in s:
        time.sleep(2)
        print("char = ", c)

t1 = threading.Thread(target=count, name='count')
t2 = threading.Thread(target=char, name='char')
# 真正启动线程靠start
t1.start()
t2.start()
print('~' * 30)

multi_threading.py

IO模型

IO两个阶段

IO工程分两阶段:1.数据准备阶段。从设备读取数据到内核空间的缓冲区

2.内核空间复制回用户空间进程缓冲区阶段

系统调用--read函数、recv函数

同步阻塞IO

进程等待(阻塞),直到读写完成。(全程等待)

mmap、sendfile、zero copy减少拷贝,减少用户态内核态的切换

同步非阻塞IO

进程调用recvfrom操作,如果IO设备没有准备好,立即返回ERROR,进程不阻塞。用户可以再次发起系统调用(可以轮询),如果内核已经准备好,就阻塞,然后复制数据到用户空间。虽然不阻塞,但是不断轮询,CPU处于忙等。

IO多路复用

也称为Event-driven IO。

所谓IO多路复用,就是同时监控多个IO,称为多路IO,哪怕只有一路准备好了,就不需要等了就可以开始处理这一路的数据。这种方式提高了同时处理IO的能力。

select几乎所有操作系统平台都支持,poll是对select的升级。epoll,linux系统内核2.5+开始支持,对select和poll的增强,在监视的基础上,增加回调机制。BSD、Mac平台有kqueue,Windows有iocp。

以select为例,将关注的IO操作告诉select函数并调用,进程阻塞,内核“监视”select关注的文件描述符fd,被关注的任何一个fd对应的IO准备好了数据,select返回。再使用read将数据复制到用户进程。

Epoll与select相比,解决了select监听fd的限制和O(n)遍历效率问题,提供回调机制等,效率更高。

webserver_multiplexing.py

import threading
import time
import selectors
from seletor import EVENTREAD, EVENTWRITE

html = """\
HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:11:20 GMT
Context-Type: text/html
Content-Length: 304
Connection: keep-alive
Server: wu123.xiaofeng.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>云原生www.xiaofeng.com-multiplexing</h1>
</body>
</html>\
"""



def recv(conn):
    print("我等你发数据")
    #while True:
    try:
        data = conn.recv(4096) # 默认阻塞方法,会导致主线程卡住,进入阻塞态
        if not data:
            return selector.unregister(conn)
        print(conn.getpeername, data)
        conn,send(html.encode()) # utf-8
    except Excaption as e :
        print(e)
    finally:
        seletor.unregister(conn)

def accept(server):
    # while True:
        # conn 服务员socket
        newsock, raddr = server.accept() # 默认阻塞,迎宾指派服务员以后和您单独服务
        print(newsock, '~~~~~~~~~') # fd文件描述符
        # while True:
        #     data = newsock.recv(4096)
        #threading.Thread(target=recv, name="recv", args=(newsock, raddr)).start() # n个
        key = selector.register(newsock, EVENT_READ, recv)

if __name__ == '__main__':
    seletor = seletors.DefaultSelectot()

    # Web服务器,HTTP,TCP,纯文本
    # 1 监听座,门童
    server = socket.socket() # TCP ipv4
    laddr = ('0.0.0.0', 9999)
    server.bind(laddr)
    server.listen(1024)

    #threading.Thread(target=accept, name="accept").start() # 1个
    key = seletor.register(server, EVENT_READ, accept)
    
    while True:
        events = selector.select() # 阻塞 [(keyn, mask),()],当多路就绪时解除阻塞,返回就绪的那几路
        for key, mask in events: # events只有就绪那几路
            print(key,key.fileobj, key.fd,key.data,type(key.data))
            print(mask)
            key.data(key.fileobj) # accept (server) recv(newsock)
            print('~' * 30)

    #while True:
        # 我是主线程,没人阻塞我了,我很高兴,我要监管他们
        #time.sleep(5)
        #print(threding.enumerate())
import threading
import time
import selectors
from seletor import EVENTREAD, EVENTWRITE

html = """\
HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:11:20 GMT
Context-Type: text/html
Content-Length: 304
Connection: keep-alive
Server: wu123.xiaofeng.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>云原生www.xiaofeng.com-multiplexing</h1>
</body>
</html>\
"""



def recv(conn):
    print("我等你发数据")
    #while True:
    try:
        data = conn.recv(4096) # 默认阻塞方法,会导致主线程卡住,进入阻塞态
        if not data:
            return selector.unregister(conn)
        print(conn.getpeername, data)
        conn,send(html.encode()) # utf-8
    except Excaption as e :
        print(e)
    finally:
        seletor.unregister(conn)

def accept(server):
    # while True:
        # conn 服务员socket
        newsock, raddr = server.accept() # 默认阻塞,迎宾指派服务员以后和您单独服务
        newsock.setblocking(False)
        print(newsock, '~~~~~~~~~') # fd文件描述符
        # while True:
        #     data = newsock.recv(4096)
        #threading.Thread(target=recv, name="recv", args=(newsock, raddr)).start() # n个
        key = selector.register(newsock, EVENT_READ, recv)

def slt():
    while True:
        events = selector.select() # 阻塞 [(keyn, mask),()],当多路就绪时解除阻塞,返回就绪的那几路
        for key, mask in events: # events只有就绪那几路
            print(key,key.fileobj, key.fd,key.data,type(key.data))
            print(mask)
            key.data(key.fileobj) # accept (server) recv(newsock)
            print('~' * 30)


if __name__ == '__main__':
    seletor = seletors.DefaultSelectot()

    # Web服务器,HTTP,TCP,纯文本
    # 1 监听座,门童
    server = socket.socket() # TCP ipv4
    laddr = ('0.0.0.0', 9999)
    server.bind(laddr)
    server.listen(1024)
    server.setblocking(False)

    #threading.Thread(target=accept, name="accept").start() # 1个
    key = seletor.register(server, EVENT_READ, accept)
    
    threading.Thread(name="selector", target=slt).start()
    while True:
        # 我是主线程,没人阻塞我了,我很高兴,我要监管他们
        time.sleep(5)
        print(threding.enumerate())
        print(list(setletor.get_map().keys()))

IO多路复用版

        非常显著减少了线程,以前一个请求一个线程,现在只需要为select阻塞分配一个线程

EPoll

协程本质和Goroutine

多个任务要并发执行,假如单核核心CPU,只能分时,交替运行。

线程切换 # 操作系统控制交替运行,由不得你

协程切换?# 

import string
import time

# def count():
#     c = 1
#     #while True:
#     for i in range(5):
#         time.sleep(1)
#         print(c)
#         c += 1
#     return None
def count():
    c = 1
    while True:
        time.sleep(1)
        print(c)
        yield c # yield 所有生成器函数一旦执行,执行到函数结束,或者yield的位置这时函数竟然没有执行完它暂停了
        # yield岂不是程序员控制何时暂停执行
        c += 1

def char():
    s = string.ascii_lowercase
    for c in s:
        time.sleep(2)
        print(c)
        # io非阻塞,错误=》io交给io多路复用 go协程netpoller
        yield c
        # data = io的值


t1 = count() # 生成器对象
t2 = char() # 牙膏

tasks = [t1,t2]
while True: # 大循环必须有, 调度问题, scheduler
    pop_indexes = []
    for i,task in enumerate(tasks):
        x = next(task, None)
        if x is None:
            print("task=", task, " bye")
            pop_indexes.append(i)
        for i in reversed(pop_indexes):
            tasks.pop(i)
        print(tasks, '#####')

#t = count() # python中只要函数中有yield关键字,这个函数不普通,成为生成器函数,生成器函数调用立即返回一个生成器对象
# 生成器对象也是迭代器对象,你可以简单的不准确理解为数组
# print(t)
# a = next(t) # 才执行了count函数到yield时暂停执行且返回了
# next(t) # 从上一次yiled后开始继续执行,运行到碰到yield或者return
# next(t)
# 1 next函数驱动 2 for 挤到牙膏没有了
# count() # 一般情况下,一个函数正常执行,必须到函数所有语句执行完,函数执行return,同步调用
print('~' * 30)

协程:

1、协程可以在同一个线程中交替执行

因为可以yield,因为可以开发人员控制yield函数暂停,

用户空间切换

保护、恢复

2、某些地方必须有循环

GMP模型

Go语言协程中非常重要的协程调度器scheduler和网络轮询器netpoller。

Go协程调度中,有三个重要角色:

M:Machine thread,系统线程。所有代码都要再系统线程上跑,协程最终也是代码,也不例外

G:Goroutine, Go协程。存储了协程的执行栈信息,状态和任务函数等。初始栈大小约2~4k,理论上开启百万个Gouroutine不是问题

P:Go1.1版本引入,Processor,虚拟处理器

可以通过环境变量GOMAXPROCS或runtime.GOMAXPROCS()设置,默认为CPU核心数

P的数量决定者最大可并行的G的数量

P有自己的队列(长度256),里面放着待执行的G

M和P需要绑定在一起,这样P队列中的G才能正在有地方执行

1、使用go func 创建一个Goroutine g1

2、当前P为p1,将p1归入当前P的本地队列LRQ(Local Run Queue),如果满了,就加入到GRQ(Global Run Queue)

3、p1和m1绑定,m1从p1的LRQ中请求G,如果没有,就从GRQ中请求G,如果还没有,就从别的P的LRQ中偷(work stealing)

4、假设m1拿到了g1

5、让g1的代码在m1线程上运行

(1)同步系统调用时,执行如下

如果遇到了同步阻塞调用,m1和pg1分离,m1带着g1阻塞者。

从休眠线程队列中获取一个空闲线程,和p1绑定,并从p1队列中获取g来执行;如果休眠队列无空闲线程,就创建一个线程提供给p1.

如果m1阻塞结束,需要和一个空闲的p绑定,优先和原来的p1绑定。如果没有空闲的P,g1会放到GRQ中,M加入到休眠线程队列中。

(2)异步网络IO调用时,如下

m1执行给g1,执行过程中发生了异步网络IO调用时,g1和p1分离,g1会被网络轮询器Netpoller接手。m1再从p1的LRQ中拿一个Goroutine g2执行。注意,m1和p1不解绑。

就大致相当于网络轮询器Netpoller内部就是使用了IO多路复用,类似我们第二天代码中的select的循环。GO对于不同操作系统Mac(kqueue)、Linux(epoll)、Windows(iocp)提供了支持。

Go TCP编程

package main

import (
	"fmt"
	"log"
	"net"
)

func main() {
	// 1 socket
	// 2 bind
	// 3 listen
	laddr, err := net.ResolveTCPAddr("tcp", ":9999")
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	server, err := net.ListenTCP("tcp", laddr)
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	defer server.Close()
	// 4 accept
	newsock, err := server.Accept()
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	defer newsock.Close()
	// recv send; read  write io
	buffer := make([]byte, 4096)
	n, err := newsock.Read(buffer)
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	fmt.Println(buffer[:n])
	newsock.Write([]byte("www.wu123.com"))
}
package main

import (
	"fmt"
	"log"
	"net"
)

var html = `HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:11:20 GMT
Context-Type: text/html
Content-Length: 293
Connection: keep-alive
Server: wu123.xiaofeng.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>云原生www.xiaofeng.com-go</h1>
</body>
</html>`

func main() {
	// 1 socket
	// 2 bind
	// 3 listen
	laddr, err := net.ResolveTCPAddr("tcp", ":9999")
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	server, err := net.ListenTCP("tcp", laddr)
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	defer server.Close()
	for {
		// 4 accept
	    newsock, err := server.Accept()
	    if err != nil {
		    log.Fatalln(err) // 错误打印一下,退出进程
	    }
	
	    go func() {
	    	defer newsock.Close()
		    // recv send; read  write io
		    buffer := make([]byte, 4096)
		    n, err := newsock.Read(buffer)
		    if err != nil {
			    log.Fatalln(err) // 错误打印一下,退出进程
		    }
		    fmt.Println(string(buffer[:n]))
		    newsock.Write([]byte(html))
	    }()
    }
	fmt.Println("###########")
}
package main

import (
	"fmt"
	"log"
	"net"
)

var html = `HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:11:20 GMT
Context-Type: text/html
Content-Length: 293
Connection: keep-alive
Server: wu123.xiaofeng.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>云原生www.xiaofeng.com-go</h1>
</body>
</html>`

func main() {
	// 1 socket
	// 2 bind
	// 3 listen
	laddr, err := net.ResolveTCPAddr("tcp", ":9999")
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	server, err := net.ListenTCP("tcp", laddr)
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
    go func() {
        	defer server.Close()
	    for {
			// 4 accept
	        newsock, err := server.Accept()
	        if err != nil {
		        log.Fatalln(err) // 错误打印一下,退出进程
	        }
	
	        go fun() {
		        defer newsock.Close()
		        // recv send; read  write io
		        buffer := make([]byte, 4096)
		        n, err := newsock.Read(buffer)
		        if err != nil {
			        log.Fatalln(err) // 错误打印一下,退出进程
		        }
		        fmt.Println(string(buffer[:n]))
		        newsock.Write([]byte(html))
	        }()
	    
	    }
    }()
    fmt.Println("###########")
    for {
    }
}
package main

import (
	"fmt"
	"log"
	"net"
)

var html = `HTTP/1.1 200 OK
Date: Wed, 22 Mar 2023 22:11:20 GMT
Context-Type: text/html
Content-Length: 293
Connection: keep-alive
Server: wu123.xiaofeng.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>云原生www.xiaofeng.com-go</h1>
</body>
</html>`

func main() {
	// 1 socket
	// 2 bind
	// 3 listen
	laddr, err := net.ResolveTCPAddr("tcp", ":9999")
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	server, err := net.ListenTCP("tcp", laddr)
	if err != nil {
		log.Fatalln(err) // 错误打印一下,退出进程
	}
	go func() {
		defer server.Close()
		for {
			// 4 accept
			newsock, err := server.Accept()
			if err != nil {
				log.Fatalln(err) // 错误打印一下,退出进程
			}

			go func() {
				defer newsock.Close()
				// recv send; read  write io
				buffer := make([]byte, 4096)
				n, err := newsock.Read(buffer)
				if err != nil {
					log.Fatalln(err) // 错误打印一下,退出进程
				}
				fmt.Println(string(buffer[:n]))
				newsock.Write([]byte(html))
			}()
		}
	}()
	fmt.Println("###########")
	for {
        time.Sleep(10 * time.Second)
		fmt.Println(runtime.NumGoroutine())
	}
}

1 多线程阻塞IO

2 线程池阻塞IO

3 IO多路复用

4 Goroutine

https://download.joedog.org/siege/

# tar xf siege-4.1.5.tar.gz
# cd siege-4.1.5
# ./configure
# make && install

# siege -c 200 -t 30 http://192.168.204.130:9999

一、Go发展

诞生

版本

设计理念

二、环境与开发

安装

Windows安装

Linux安装

环境变量

开发工具

VSCode插件安装

Go三件套

大纲

Ubuntu

初始Go程序

Go命令

version

env

mod

get

install

build

run

fmt

三、项目结构

设计理念

day001:一起认识 Go语言_@Gopher的博客-CSDN博客

day002:学懂Go语言基础语法(一)_@Gopher的博客-CSDN博客

day003:学懂Go语言基础语法(二)_@Gopher的博客-CSDN博客

day003:学懂Go语言基础语法(三)_@Gopher的博客-CSDN博客


1、打印九九乘法表。如果可以要求间隔均匀。

// 1*1=1 打印第一行 列j=1 行i=1 j<=i

// 1*2=2 2*2=4  打印第二行 列j=1 行i=2,列j=2 行i=2

// 1*3=3 2*3=6 3*3=9  打印第三行 列j=1 行i=3,列j=2 行i=3,列j=3 行i=3

// 1*4=4 2*4=8 3*4=12 4*4=16

// 1*5=5 2*5=10 3*5=15 4*5=20 5*5=25

// 1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36

// 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49

// 1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64

// 1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81

package main

import "fmt"

func main() {
	count := 10
	for i := 1; i < count; i++ { // 控制行
		for j := 1; j <= i; j++ { // 控制列
			res := j * i
			fmt.Printf("%d*%d=%d\t", j, i, res)
		}
		fmt.Println()
	}
}

main函数必须在main包中

同一个目录,包名必须一样

main是程序入口,操作系统调用时,必须知道从哪里开始

package main

import "fmt"

func multiplyTable() {
	for i := 1; i < 10; i++ {
		// 做了9行,i表示某一行。每一行里面还要做9列
		for j := 1; j < i; j++ {
			if j == 1 {
				fmt.Printf("%d*%d=%-2d", j, i, i*j)
			} else {
				fmt.Printf("%d*%d=%-3d", j, i, i*j)
			}
		}
		fmt.Println()
	}
}
package main

import "fmt"

func multiplyTable() {
	var width int // 零值
	for i := 1; i < 10; i++ {
		// 做了9行,i表示某一行。每一行里面还要做9列
		for j := 1; j < i; j++ {
			if j == 1 {
				width = 2
			} else {
				width = 3
			}
			// fmt.Printf("%d*%d=%-[4]*[3]d", j, i, i*j, width)  // %[4]d
			fmt.Printf("%d*%d=%-[3]*d", j, i, width, i*j) // %[4]d
		}
		fmt.Println()
	}
}

func main() {
	// main函数必须在main包中
	// 同一个目录,包名必须一样
	// main是程序入口,操作系统调用时,必须知道从哪里开始
	multiplyTable() // 调用,执行一次,可以反复调用
}

2、随机生成100以内的20个非0正整数,打印出来。对生成的数值,第单数个(不是索引)累加求和,第偶数个累乘求积。打印结果

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	// main函数必须在main包中
	// 同一个目录,包名必须一样
	// main是程序入口,操作系统调用时,必须知道从哪里开始
	limiter := 20
	var ( // 把业务上有关联的可以批量定义在一起
		sum     = 0
		product uint64 = 1
	)
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < limiter; i++ {
		v := r.Intn(99) + 1 // [0,99) 100以内不含0 [1,99]
		if i&1 == 0 {
			// 累加
			sum += v
		} else {
			product *= uint64(v)
		}
	}
	fmt.Println(sum, product)
}

3、打印100以内的斐波那契数列

func fibo(limiter int) {
	// 给出最早2项,必须给
	a, b := 1, 1
	fmt.Println(a, b)
	for {
		a, b = b, a+b
		if b > limiter {
			break
		} // 退出条件
		fmt.Println(b)
	}
}

类型的本质

	var a = 0x63                      // int
	fmt.Printf("%T %[1]d %[1]c\n", a) // ctrl + j
	var b byte = 99                   // uint8
	fmt.Printf("%T %[1]d %[1]c\n", b) // 单字节整数
	var c rune = 0x63                 //rune
	fmt.Printf("%T %[1]d %[1]c\n", c) // int32
	var d rune = '\x63'               //rune
	fmt.Printf("%T %[1]d %[1]c\n", d)

	var e = "c" // "\x63" // string长度为1
	fmt.Println(len(e))
	fmt.Printf("%T %[1]q\n", e)
	fmt.Printf("%T %[1]v\n", string(a)) // string(a) 把a当作unicode查表
	// 对深入理解 序列化 反序列化有很大帮助

数据结构

类型

数值处理

取整

其它数值处理

标准输入

Scan:空白字符分割,回车提交。换行符当做空白字符

package main

import "fmt"

func main() {
	var scan int
	var err error
	var s1, s2 string
	scan, err = fmt.Scan(&s1, &s2)
	if err != nil {
		panic(err) // 程序终止,不会向后执行了
	}
	fmt.Println(scan)
	fmt.Printf("%T %[1]s,%T %[2]s\n", s1, s2)
	fmt.Println("~~~~~~~~~~~~~~~~~~")

	var i1, i2 int
	fmt.Print("Please input year two ints:")
	scan, err = fmt.Scan(&i1, &i2)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%T %[1]d %T %[2]v\n", i1, i2) // stdin c
}

如果少一个数据,Scan就会阻塞;如果输入数据多了,等下回Scan读取。例如,一次性输入a b 1 2看看效果。

Scanf:读取输入,按照格式匹配解析。如果解析失败,立即报错,那么就会影响后面的Scanf。

package main

import "fmt"

func main() {
	var n int
	var err error
	var name string
	var age int
	fmt.Print("Plz input your name and age:")
	n, err = fmt.Scanf("%s %d", &name, &age)
	if err != nil {
		panic(err)
	}
	fmt.Println(n)
	fmt.Printf("%T %[1]s, %T %[2]d", name, age)

	var weight, height int
	n, err = fmt.Scan(&weight, &height)
	if err != nil { // err不为空,说明有err了,当然要崩溃,因为往后执行,可能数据有错,所以恐慌程序崩溃
		panic(err)
	}
	fmt.Printf("%T %[1]s, %T %[2]d", weight, height)
}

fmt.Scanf("%s,%d", &name, &age) 中%s会和后面的非空白字符分不清楚,用 abc,20 是匹配不上的,因为除空白字符外,都可以看做是字符串。所以,建议格式字符串中,一律使用空格等空白字符分割。

线性数据结构

数组 长度不可变 内容可变 可索引 值类型 顺序表

	var a0 [3]int // 必须明确给出个数,零值len=cap,go中数组长度不能改变
	fmt.Println(len(a0), cap(a0))
	// var a1 = [3]int // 右边不能只写类型,要写字面常量
	var a1 = [3]int{} // {}容器类型字面量界定符,长度len, cap
	fmt.Println(len(a1), cap(a1), a1)
	var a2 [3]int = [3]int{}
	fmt.Println(a2)

	// count := 3
	const count = 3
	var a3 = [count]int{} // 数组定义时,必须明确长度,因为变量可以变,它怕你反悔,所以只能使用常量,编译器在编译期明确了
	fmt.Println(a3)

	var a4 = [...]int{} // 推断元素个数
	fmt.Println(len(a4), cap(a4), a4)

	var a5 = [5]int{1, 20} // 初始化前2个
	fmt.Println(a5)
	var a6 = [5]int{1: 20, 4: 30} // index: value index不能越界
	fmt.Println(a6)

	// 2d
	// a7 := [2][3]int{}
	a7 := [2][3]int{1: {100, 2: 200, 1: 300}}
	fmt.Println(a7)
	a8 := [...][3]int{4: {100, 2: 200, 1: 300}}
	fmt.Println(a8)
package main

import (
	"fmt"
)

func showAddr(arr [5]int) [5]int {
	fmt.Printf("3 %v %p\n", arr, &arr) // 全新副本
	arr[0] = 10000
	fmt.Printf("4 %v %p\n", arr, &arr)
	return arr
}
func main() {
	// 尤其是用过其他语言的,但在Go中不是你想的那样
	var a0 = [5]int{1, 3, 5, 7, 9}
	fmt.Printf("1 %v %p\n", a0, &a0)

	var a1 = a0 // 其他语言中,这句话理解为地址赋予,a0和a1共用同一个地址,但是go不是这样。值的复制,造一个完全一样的副本
	fmt.Printf("2 %v %p\n", a1, &a1)
	fmt.Println(a0 == a1, &a0 == &a1)
	a1[2] = 5000
	fmt.Println(a0, a1)
	fmt.Println("~~~~~~~~~~~~~~~")

	a8 := showAddr(a0) // 函数传参, return是对返回值又做了一个副本
	fmt.Println("~~~~~~~~~~~~~~~")
	fmt.Println(a0)
	fmt.Printf("8 %v %p\n", a8, &a8)
}

切片

	// 尤其是用过其他语言的,但在Go中不是你想的那样
	var s0 = []int{1, 3, 5, 7, 9} // 字面量定义,长度为5,容量为5
	fmt.Printf("%T %[1]v, %d %d\n", s0, len(s0), cap(s0))

	var s1 []int // 声明,零值填充,长度为0,容量为0,
	fmt.Println(s1, len(s1), cap(s1), &s1)
	fmt.Printf("%p %[1]T\n", &s1)

	var s2 = []int{} // []int切片,长度和容量都是0
	fmt.Println(s2, len(s2), cap(s2))

	// 2 make推荐,用的很多,make可以给内建容器开辟内存空间
	var s3 = make([]string, 0) // 切片使用make,第二个参数0表示长度,长度为0的切片,容量为0
	fmt.Println(s3, len(s3), cap(s3))

	s4 := make([]string, 0, 5) // 切片使用make,第二个参数0表示长度,第三个参数5表示容量
	fmt.Println(s4, len(s4), cap(s4))
	// 尤其是用过其他语言的,但在Go中不是你想的那样
	var s0 = []int{1, 3, 5, 7, 9}      // 字面量定义,长度为5,容量为5
	fmt.Printf("%p %p\n", &s0, &s0[0]) // &s0 结构体header标量值,&s0[0]通过底层数组索引0取底层数组地址
	s0 = append(s0, 11)
	fmt.Println(s0, &s0[0], len(s0), cap(s0))
	// 尤其是用过其他语言的,但在Go中不是你想的那样
	var s0 = make([]int, 3, 5) // 字面量定义,长度为3,容量为5
	fmt.Printf("s0 %p, %p, %-2d %-2d, %v\n", &s0, &s0[0], len(s0), cap(s0), s0)
	s1 := append(s0, 1, 2) // s0[0 0 0 1 2]或[1 2]忽略了header, append追加,虽然增加了但没有扩容,append不更新header
	// fmt.Println(s0, len(s0), cap(s0))
	fmt.Printf("s0 %p, %p, %-2d %-2d, %v\n", &s0, &s0[0], len(s0), cap(s0), s0)
	fmt.Printf("s1 %p, %p, %-2d %-2d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
	fmt.Println("~~~~~~~~~~~~")

	s2 := append(s0, -1)
	fmt.Printf("s0 %p, %p, %-2d %-2d, %v\n", &s0, &s0[0], len(s0), cap(s0), s0)
	fmt.Printf("s1 %p, %p, %-2d %-2d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
	fmt.Printf("s2 %p, %p, %-2d %-2d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
	fmt.Println("~~~~~~~~~~~~")

	s3 := append(s2, 3, 4, 5) // 生成新的底层数组
	fmt.Printf("s0 %p, %p, %-2d %-2d, %v\n", &s0, &s0[0], len(s0), cap(s0), s0)
	fmt.Printf("s1 %p, %p, %-2d %-2d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
	fmt.Printf("s2 %p, %p, %-2d %-2d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
	fmt.Printf("s3 %p, %p, %-2d %-2d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3) // 7 10
	fmt.Println("~~~~~~~~~~~~")

	// s4 := append(s3, 6, 7, 8, 9)
	// fmt.Printf("s3 %p, %p, %-2d %-2d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3)
	// fmt.Printf("s4 %p, %p, %-2d %-2d, %v\n", &s4, &s4[0], len(s4), cap(s4), s4)
	s3 = append(s3, 6, 7, 8, 9)
	fmt.Printf("s3 %p, %p, %-2d %-2d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3)

扩容策略https://go.dev/src/runtime/slice.go

package main

import "fmt"

func showAddr(s []int) []int {
	fmt.Printf("3 %p %p %-2d %-2d %v\n", &s, &s[0], len(s), cap(s), s)
	// s[0] = 10000
	s = append(s, 111)

	fmt.Printf("4 %p %p %-2d %-2d %v\n", &s, &s[0], len(s), cap(s), s)
	return s
}
func main() {
	// 尤其是用过其他语言的,但在Go中不是你想的那样
	var s0 = []int{1, 3, 5} // len cap 3,s0称为[]int切片类型的变量,变量实际上一个结构体的实例{底层数组指针,len切片元素个数,cap切片容量}
	fmt.Printf("1 %p %p %-2d %-2d %v\n", &s0, &s0[0], len(s0), cap(s0), s0)
	var s1 = s0 // 复制的是什么? header结构体
	fmt.Printf("2 %p %p %-2d %-2d %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
	s1[0] = 100
	fmt.Println(s0, s1) // 操作s1就好像操作s0一样,优点类似复制了切片的地址,通过地址操作2个切片一起变
	fmt.Println("~~~~~~~~~~")
	s5 := showAddr(s0) // 复制了header
	fmt.Printf("1 %p %p %-2d %-2d %v\n", &s0, &s0[0], len(s0), cap(s0), s0)
	fmt.Printf("2 %p %p %-2d %-2d %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
	fmt.Printf("5 %p %p %-2d %-2d %v\n", &s5, &s5[0], len(s5), cap(s5), s5)

}

Go语言中全部是值传递,整型、数组这样的类型的值是完全复制,slice、map、channel、interface、function这样的引用类型也是值拷贝,不过复制的是标头值。

子切片

	s1 := []int{10, 30, 50, 70, 90} // len\cap 5 [0, 4]
	fmt.Printf("s1 %p %p, %d %d, %v\n", &s1, &s1[0], len(s1), cap(s1), s1)
	s2 := s1 // copy header
	fmt.Printf("s2 %p %p, %d %d, %v\n", &s2, &s2[0], len(s2), cap(s2), s2)
	// 子切片:有没有可能构造一个新底层数组?我用你的部分元素,不会导致扩容,共用一个底层数组
	s3 := s1[:] // 构造一个新header,不会构造新的底层数组
	fmt.Printf("s3 %p %p, %d %d, %v\n", &s3, &s3[0], len(s3), cap(s3), s3)

	s4 := s1[1:] // 掐头,新header,首地址变了,偏移一个元素,容量4,长度4,end缺省5
	fmt.Printf("s4 %p %p, %d %d, %v\n", &s4, &s4[0], len(s4), cap(s4), s4)

	s6 := s1[:4] // 去尾,新header,首地址不偏移,容量5,长度4
	fmt.Printf("s6 %p %p, %d %d, %v\n", &s6, &s6[0], len(s6), cap(s6), s6)
	s7 := s1[1:1] // 掐头,新header,偏移一个元素,容量4,长度0
	fmt.Printf("s7 %p |没法显示,%d %d|, %v\n", &s7 /*&s7[0]*/, len(s7), cap(s7), s7)
	fmt.Println(s1)
	s7 = append(s7, 300, 400) // len 2 cap 4
	fmt.Println(s1)
	fmt.Println(s7)
	s8 := s1[4:4] // 长度为0,cap为1,首地址偏移4个元素
	fmt.Printf("s8 %p |没法显示,%d %d|, %v\n", &s8 /*&s8[0]*/, len(s8), cap(s8), s8)

	//fmt.Println(s1[5]) // out of index range
	s9 := s1[5:5] // 对不对?子切片时,可以使用的最大值是容量
	// s1[5:5] // 偏移5个元素,长度0,容量0 []int{}
	// fmt.Println(s9)
	fmt.Printf("s9 %p |没法显示,%d %d|, %v\n", &s9 /*&s8[0]*/, len(s9), cap(s9), s9)
	s9 = append(s9, 11) // 扩容,分道扬镳,容量为1,长度为1
	fmt.Printf("s9 %p |没法显示,%d %d|, %v\n", &s9 /*&s8[0]*/, len(s9), cap(s9), s9)
	s9 = append(s9, 22) // 扩容 2
	fmt.Printf("s9 %p |没法显示,%d %d|, %v\n", &s9 /*&s8[0]*/, len(s9), cap(s9), s9)
	s9 = append(s9, 33) // 4
	fmt.Printf("s9 %p |没法显示,%d %d|, %v\n", &s9 /*&s8[0]*/, len(s9), cap(s9), s9)

常见线性数据结构

数组array

链表Linked List

栈Stack

队列Queue

链接表 list:索引定位效率还行,随意插入、删除

熟记下面表格中特殊字符

注意:这里的1指的是字符1,不是数字1

import "fmt"

func main() {
	fmt.Println("a\x09b\x0ac \x31\x41\x61")
}
a	b
c 1Aa

"a b" len? 3

"a        b" len? 3

"a\tb" len? 3

"a\\tb" len 4

"a\r\nb" len 4

`a

b

`

	s := "a	b"
	s1 := "a\tb"   //
	s2 := "a\x09b" // a\tb
	s3 := "a\\tb"  //
	s4 := `a\nb`
	fmt.Println(s, s1, s2, s3)
	fmt.Println(s4, len(s4), []byte(s4))
	// s := 'a' //rune 4bytes
	// var s byte = 'a'
	// s := '测' // rune 4bytes unicode 不是容器不能用len
	// fmt.Printf("%T\n", s)

	s1 := "abc" // 3
	s2 := "测试"  // 6bytes utf-8 其他语言len是看字符数. len只能用在内建类型的容器上
	// s3 := "测" // string 3bytes
	fmt.Println(len(s1), len(s2)) // len只看字节数

	// string有序字节序列,类似slice,header底层字符数组,但是用起来为了不增加学习go的难度,设计者把它使用方式和其他语言统一了
	t1 := []byte(s1)                                  // []byte(xxx) 强制类型转换;[]byte{} byte切片字面量定义
	fmt.Println(t1, len(t1), cap(t1), &t1[0], &t1[1]) // [97 98 99]
	t2 := []rune(s1)
	fmt.Println(t2, len(t2), &t2[0], &t2[1]) // [97 98 99] offset 4bytes

	t3 := []byte(s2)         // 能不能?
	fmt.Println(t3, len(t3)) // [230 181 139 232 175 149] 6
	// t4 := []rune(s2) // 可以吗?
	// fmt.Println(t4, len(t4)) // [rune rune] unicode; utf-8->unicode
	fmt.Println("~~~~~~~~~~~~")

	fmt.Println(string(27979))                      // string(unicode码) "测" %q
	fmt.Println(string([]rune{27979, 35797}))       // "测试" unicode序列-》utf-8 string
	fmt.Println(string([]byte{0x61, '\x62', 0x63})) // abc byte sequence -> utf-8 string sequence
	fmt.Println("~~~~~~~~~~~~")

	fmt.Println(s1[0], s1[1], s1[2]) // ? 用的索引,不是for range,字节序列,[]byte, 其中的一个byte uint8
	fmt.Println(s2[0], s2[1])        // ? 字节序列,[]byte len6, 230 181

	// fmt.Println(string([]byte{230, 181, 139, 232, 175, 149})) // 测试
	s1 := "abc"
	s2 := "测试"
	s3 := s1 + s2 // + 运算符重载,go不支持运算符重载
	for i := 0; i < len(s3); i++ {
		fmt.Println(i, s3[i]) // uint8
	}
	for i, v := range s3 { // for range 安全遍历字符方式 rune
		fmt.Println(i, v)         // 打印几下?5 v什么类型rune int32
		fmt.Printf("%c\n", s3[i]) // 能看到什么?只能取1个byte
	}
	s1 := "abc"
	s2 := "测试"
	s3 := s1 + s2 // + 运算符重载,go不支持运算符重载

	s4 := fmt.Sprintf("%s---%s", s1, s2) // 推荐
	fmt.Println(s3, s4)

	s5 := strings.Join([]string{s1, s2}, "***") // []string
	fmt.Println(s5)

	var builder strings.Builder // 构建器
	builder.Write([]byte(s1))
	builder.WriteByte('-')
	builder.WriteString(s2)
	s6 := builder.String()
	fmt.Println(s6)

strings.ToLower()

strings.ToUpper()

strings.HasPrefix()

strings.HasSuffix()

	s1 := "www.wa123.com" // utf-8
	for i, v := range s1 {
		fmt.Println(i, string(v))
	}
	fmt.Println(strings.Index(s1, ".")) // 3 -> 第一次找到的索引值
	fmt.Println(strings.Index(s1, "w")) // 0
	fmt.Println(strings.Index(s1, "t")) // -1 没找到
	// Index *
	fmt.Println(strings.IndexByte(s1, 0x61)) // ? 5
	fmt.Println(strings.IndexRune(s1, 0x61)) // ? 5
	fmt.Println(strings.IndexAny(s1, "aw"))  // chars 字符合集,只要匹配到字符集中一个立即返回
	fmt.Println(strings.LastIndex(s1, "wa")) // 从右向左找 ? 查找方向不影响返回的正索引
	fmt.Println(strings.Contains(s1, "wa"))  // bool
	fmt.Println(strings.Count(s1, "w"))      // 3
	fmt.Println(strings.Count(s1, "ww"))     // 1
	fmt.Println(strings.Count(s1, "www"))    // 1

移除

	s1 := "www.wa123.com" // utf-8
	for i, v := range s1 {
		fmt.Println(i, string(v))
	}
	// s2 := "a b \tc\r\nd"
	s2 := "\v\r\n\t   \ta b\tc\r\nd\n\t   "
	fmt.Println(strings.TrimSpace(s2)) // 去除两端空白字符
	fmt.Println(strings.TrimLeft("abcdefg", "bad"))
	fmt.Println(strings.TrimLeft("abcdefg", "badc"))
	fmt.Println(strings.TrimRight("abcdefg", "bagdc"))
	fmt.Println(strings.Trim("abcdefg", "bagdc"))
	fmt.Println(strings.Trim(s2, " ")) // 去除空格
	s1 := "www.wa123.com" // utf-8
	for i, v := range s1 {
		fmt.Println(i, string(v))
	}

	fmt.Printf("%T %[1]v", strings.Split(s1, ".")) // []string
	fmt.Printf("%T %[1]v", strings.Split(s1, "t")) // []string 返回一个元素[原理的字符串]
	fmt.Println(strings.Split(s1, ""))             // []string 按照字符切,

	// fmt.Println(strings.SplitN(s1, "com", -1)) // < 0, 能切则切,Split
	fmt.Println(strings.SplitN(s1, "com", 0)) // ==0 []string, [] 推荐make([]string,0, 100)
	fmt.Println(strings.SplitN(s1, "com", 1)) // n 指的是返回切片的元素个数,只能是[原字符串]
	fmt.Println(strings.SplitN(s1, "com", 2)) // n切片只能有2个元素
	s1 := "www.wa123.com" // utf-8
	for i, v := range s1 {
		fmt.Println(i, string(v))
	}

	// replace 不能被替换,生成新的string
	fmt.Println(strings.Replace(s1, "com", "net", -1)) // < 0 , ReplaceAll
	fmt.Println(strings.Replace(s1, "com", "net", 0))  // 可以认为是新字符串
	fmt.Println(strings.Replace(s1, "com", "net", 1))  // 替换一次
	s1 := "www.wa123.com" // utf-8
	for i, v := range s1 {
		fmt.Println(i, string(v))
	}

	fmt.Println(strings.Repeat("~^", 40))
	fmt.Println(strings.Map(func(r rune) rune {
		fmt.Println(r, "$$")
		return r
	}, s1)) // s1进行rune遍历,for range s1, 每次处理一个rune,返回一个字符,Map会把每次返回的字符拼接成一个新的字符串
	// map 映射
	// if r >= 'a' && r <= 'z' {
	// 	r = r - 0x20
	// }
	var i int8 = -1
	fmt.Println(i)
	var j uint8 = uint8(i)
	fmt.Println(j) // 255
	type myByte byte // byte又定义为myByte,虽然你就是它,go认为你是新类型
	// 扩展方法
	type byte = uint8 // 别名,=表示就是一回事,byte编译后就没有了,1.9版本开始支持别名
	var a byte = 'C' // 助记名字
	var b uint8 = 49
	fmt.Println(a, b, a+b)

	var c myByte = 50
	// fmt.Println(a, c, a + c) // 报错 强制类型转换
	s1 := "127"
	fmt.Println(string(127)) // "?"
	fmt.Println(string(s1))
	fmt.Println(strconv.Atoi(s1)) // ascii to int
	fmt.Println(strconv.Atoi("s string"))
	fmt.Println("~~~~~~~~~~~~")
	// fmt.Println(strconv.ParseInt(s1, 10, 64)) // int64 127
	fmt.Println(strconv.ParseInt(s1, 16, 64)) // 295

	s2 := "3.141516" // 某些时候是无限逼近,近似
	fmt.Println(strconv.ParseFloat(s2, 32))

	if t, ok := strconv.ParseBool("true"); ok != nil {
		fmt.Println("error", ok)
	} else {
		fmt.Println(t)
	}
	// 1 字面量
	var m1 = map[int]string{
		1:   "abc",
		2:   "xyz",
		100: "t",
	}
	fmt.Println(m1 == nil, len(m1))
	// 2 make
	// m2 := make(map[string]int) // 空间很小
	m3 := make(map[string]int, 150) // map 为什么高效?空间换时间
	fmt.Println(m3, len(m3))        // 0个元素
	h := md5.New()
	h.Write([]byte("abc"))
	fmt.Printf("%x\n", h.Sum(nil))
	h.Reset()
	h.Write([]byte("abb"))
	fmt.Printf("%x\n", h.Sum(nil))
	fmt.Println("~~~~~~~~~~~~~~~")
	// sha256 // 256bits -> %x string len? 16?
	h1 := sha256.New()
	h1.Write([]byte("abc"))
	s := fmt.Sprintf("%x", h1.Sum(nil))
	fmt.Println(len(s), s)
	// var m1 map[int]string // 零值不可用,map故意m1为nil,nil不是空map
	// m1[1] = "abc"
	// fmt.Println(m1 == nil)
	// var s1 []int // 零值可用吗?
	// fmt.Println(s1 == nil)
	// s1 = append(s1, 10)
	// fmt.Println(s1)
	m1 := make(map[string]int) // 有空间大小
	m1["100"] = 100            // 新增,len 1
	fmt.Println(m1)
	fmt.Println(m1["100"])
	fmt.Println(m1["1"]) // ?go可以,如果key不存在,返回零值
	// v, ok := m1["1"]
	// if !ok {
	// 	fmt.Println("key不存在", v)
	// } else {
	// 	fmt.Println("存在", v)
	// }
	if v, ok := m1["1"]; !ok {
		fmt.Println("key不存在", v)
	} else {
		fmt.Println("存在", v)
	}
	m1["1"] = 1000 // 创建kv对
	m1["1"] += 1   // 覆盖value
	// key能不能被覆盖?不能
	fmt.Println(m1)
	// map玩的就是key,key不变,统计

	delete(m1, "100")
	fmt.Println(m1)

	for k, v := range m1 {
		fmt.Println(k, v)
	}

问题:数组、切片、map谁遍历效率更高?

只要是遍历,和规模有关,O(n)

排序算法

        冒泡法,交换排序、快排(递归排序)

        选择排序,堆排序

        插入排序,

sort

        快排,分情况调用不同的排序算法

腾讯PHP/GO工程师面试经历

一面是技术面,用的腾讯会议,开局自我介绍之后就开始做题。题目不算难,都非常考验基础扎不扎实。面试官特别喜欢就一个问题深入去问,直到你卡壳。

第一题是非常经典的,从浏览器敲下地址到页面呈现出来,中间发生了什么事情,越详细越好。

主要是说说 TCP 三次握手,四次挥手,详细的状态转换,以及 time_wait 状态存在的意义,滑动窗口也要说一说。

以及 nginx 和 php-fpm 是怎么通信的,fast-cgi 对比 cgi 有啥改进。

第二题是 JS 的浮点数和 async、await 的题目。

浮点数在计算机是怎么表示的,这个要答全一点.async 和 await 都是随便答一答,只要中规中矩就过关,没有深入追问。

第三题是 Vue nextTick 的原理。

第四题是 go channel 相关的题目,读代码写出执行结果。

第五题是 go defer 顺序问题,读代码写出执行结果。

第六题是 go slice 的相关原理。

第七题是 php-fpm 进程数量过多,如何优化的问题

第八题是 php 内存泄露如何排查,处理的问题

第九题是 GPM 模型的原理,以及 GO 不同版本的 GC 算法原理

第十题是 mysql 分表分库问题,及正在运行的业务中,数据表修改字段如何避免锁表导致业务不可用的问题。

第十一题是 redis 相关的,分布式锁,延迟队列,跳表的一些问题

第十二题是数据结构相关的,有向图的连通性问题,要手写代码。

除了面试题,一面还就你简历上你写的技能做了一些问答,还问了些项目相关的东西。

一面过了之后,二面就跑去深圳面,是组长和组员一起面,问了些项目相关的问题再聊聊人生,我没把细节答好,就没下文了,想想还是可惜。

总结起来,一面的范围相当广,从 php、go、js 到 mysql、reids、nginx、mq。也有一定的深度,但总体不算难。二面主要面项目经验,如果没做过大项目,就非常吃亏。

腾讯面经

一面

 算法题二选一

 https://leetcode-cn.com/problems/permutations/

 https://leetcode-cn.com/problems/sorted-merge-lcci/

 MySQL 隔离级别

 MySQL 锁

 MySQL 存储结构(b+树)

 索引 回表 是什么

 消息队列,rabbitmq

 rabbitmq 如何保证可靠性(生产者可靠性、消费者可靠性、存储可靠性)

 rabbitmq 几种模式

 es 索引的过程

 线上是如何分表分库的,用什么做分表分库的策略,跨表查询

 MySQL 如何同步到 es

 线上 Redis 用的是什么模式

 缓存热 key 怎么办

二面

 介绍项目

 defer 、go 继承,手写快排

 登录流程,JWT、session、cookie

三面

 缓存一致性

 Redis key 统计

 Redis 单线程,io 多路复用

 算法题 https://www.nowcoder.com/practice/35119064d0224c35ab1ab612bffee8df

 Redis slowlog 原理

四面(面委)

 项目为主

 tcp quick_ack 、 nodelay ,socket 编程

 职业规划

 为什么换工作

五面(GM)

 项目

 go 协程机制

腾讯云、京东云面经

个人情况

毕业两年,北京某在线教育,技术氛围不错。主要语言栈golang、python,主要技术方

向是k8s、容器、云计算。有服务上云的实践经历,了解cicd基本流程,求知意向是容

器研发、基础架构研发、运维研发之类的(主要还是研发方向)。

已面试公司

快手、字节、小米、金山云、完美世界、京东云、百度、西安腾讯云。 快手二面卒,字

节一面卒(面了两次,约了第三次二面过了拒了三面),小米二面卒,金山云感觉聊的

挺好的一面卒,完美世界offer,京东云现场面offer,百度一面卒,西安腾讯云offer。

面试前期准备很重要!!!准备充分面试时感受会好很多。千万别裸面!!!

简单说一下面试的基本问题吧(主要还是偏向基础)

项目方向:

项目的话我不多说什么,就是自己的项目细节自己肯定清楚,如果项目中不是自己做的

部分,建议不要在简历上写太多,写清楚自己做了什么,容易被抠细节问,项目一般都

会抠细节,特别细的那种!!!

语言栈:

因为我主要语言栈是go,所以一般都比较少问python。

golang

1、gin框架路由怎么实现的,具体正则怎么匹配?限流中间件怎么实现?

2、go的slice与数组的区别,slice的实现原理,源码?

3、golang的协程调度,gpm模型。协程调度过程中的锁。

4、golang的channel实现,channel有缓存和无缓存,一般会直接撸码(三个goroutine顺序打印)。

5、golang的关键字defer、recover、pannic之类的实现原理。

6、sync包里面的锁、原子操作、waitgroup之类的。

7、make和new的区别,引用类型和非引用类型,值传递之类的。

python

1、python多线程、多进程。

2、python的装饰器怎么实现的?

操作系统:

1、进程、线程、协程间的区别以及他们间的切换之类的,有时候会问到语言级别的协

程。

2、io复用、用户态/内核态转换

3、awk命令

4、linux查看端口占用

5、top命令,free命令中的各个参数表示什么,buff/cache都表示什么?

k8s & 容器:

1、简单聊一下什么是云原生、什么是k8s、容器,容器与虚机相比优势。

2、k8s组件,pod创建的过程,operator是什么?

3、docker是怎么实现的,底层基石namespace和cgroup。

4、k8s的workload类型,使用场景,statefulset你们是怎么用的?

5、limit和request,探针,一般怎么排查pod问题,查看上次失败的pod日志。

6、sidecar是什么,怎么实现的?

7、pv,pvc,动态pv怎么实现

8、k8s的声明式api怎么实现的,informer源码。

9、cicd,发布模式。

10、svc的负载均衡、服务发现,ipvs与iptables。 以上基本是会被问的点(虽然有一些问题我也不是很熟),另外很多会被问k8s的网络之类的,因为我比较菜,这块被问的比较少。

计算机网络:

1、tcp三次握手四次挥手,为什么不能是两次握手,三次挥手?握手和挥手过程中的状

态。

2、time_wait作用,为什么是2msl,close_wait作用,time_wait过多怎么办?

3、http请求的过程,浏览器输入网址请求过程?dns解析的详细过程?

4、https与http的区别,https第一次服务端回传是否加密?

5、tcp与udp区别,tcp怎么保证可靠性。

6、http请求头、分隔符、长连接怎么实现

数据库:

1、mysql的事务,事务使用场景。

2、mysql的索引,什么情况下索引失效,聚簇索引与非聚簇索引,索引的存储b+树与b-树区别。 3、join的内外连接,最左匹配原则。

4、redis的数据结构,hmap怎么实现的,持久化怎么做,go操作redis的方式。 数据库

方向有被问到,我基本没答上来(一般都告诉他只会基础,开发直接使用gorm)。

数据结构与算法:

1、倒排索引和B+树

2、判断链表是否有环,时间复杂度要求0(1)

3、LeetCode上合并区间的题

4、leetcode的股票买卖的题

5、二叉树的最近公共祖先

6、有序数组合并

7、什么是平衡二叉树、最小堆

8、大文件的top10问题

9、golang实现栈、队列

其他:

1、git 的相关操作,合并commit,合并之类的。

2、场景设计(比较多)

小米面经1

一面

1. innodb MVCC实现

2. b+树是怎么组织数据的,数据的顺序一定是从左到右递增的么

3. 页分裂伪代码,b+树的倒数底层层可以页分裂么

4. 合并k个有序链表

5. redis的hashtable是怎么扩容的

6. select poll epoll,epoll具体是怎么实现的

7. GMP是怎么调度,channel是怎么收发消息的,channel的recq和g是怎么建立关系的

8. innodb二次写是什么

9. undo里面具体存的是什么

10. b+树节点具体存的是什么

11. mysql一页最大能存多少数据

12. myisam和innodb索引上的区别

13. innodb commit之前,redo 的prepare然后binlog commit,然后redo再commit有

什么缺点?5.6之后是怎么优化的?

14. redo和binlog的区别

15. 读锁和写锁区别

二面

1. 蛇形打印二叉树

2. myisam为什么不支持事务,如果要支持事务要怎么做

3. 函数只能返回1-7的随机数,请用这个函数返回1-5,要求平均

4. 聊项目

三面

1. go的协程调度和os的线程调度有什么区别

2. 只有写锁实现读写锁

3. go的调度是怎么实现的

4. go的网络IO为什么快?还有优化空间么

5. epoll为什么这么快,还有优化空间么?如果要你实现一个网络IO应该怎么实现

6. 设计一个每秒80万qps的过滤器

7. 过滤器用redis实现,宕机期间数据怎么恢复

8. 设计一个下单 扣减库存的分布式应用,请求超时了怎么办,一直重试超时了怎么办

9. 数组A12和数组B23是一个关系圈,A能通过2找到3,数组A12和数组B23和数组

C35也是一个关系圈,给一个二维数组求关系数

小米游戏面经

一、 介绍连接池项目

1. 介绍连接池常用的参数,最大连接数,最小存活数这些意义,为什么要有这些

2. 当链接超过最大连接数怎么处理,等待有空闲连接还是创建一个继续给出,比较两

者的优劣

3. 连接池清理链接的逻辑,如何优化的

4. 当连接池中有一些链接不可用了怎么办,如何保证这些连接的可用

5. 当出现下游某个实例挂掉了,连接池应该怎么处理

6. 对比 mysql redis http 连接池的实现

二、 介绍负载均衡算法

7. 介绍平滑负载均衡算法,实现

8. 当出现下游出现不可用,负载均衡算法怎么处理

三、 介绍聊天室项目

9. 介绍实现原理的,互相通信的逻辑

10. 聊天室服务端如何把消息下发给用户

11. 介绍websocket包的字段

12. 当有用户掉线怎么处理

四、 redis相关

13. redis的数据结构

14. 各个数据结构的操作( 这个忘了,不会… )

15. 各个数据结构的使用场景

16. 如何保证 Redis 的高可用

17. 当有一个key读取的频率非常高怎么办 ( 这个也是不会)

五、 算法相关

18. 介绍快速排序 优先队列的实现 ( 没有答好 )

字节面经

一面

自我介绍

算法题:

1. https://leetcode-cn.com/problems/find-peak-element/

2. https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

3. https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/

索引,倒排索引,切词,如何根据 doc id 有没有出现某个 token

服务高可用是怎么做的

MySQL 可重复读、读提交区别、原理

爬虫 URL 去重,设计存储结构(FST,前缀树+后缀树)

MySQL (a,b,c) 索引,几条 SQL 走索引的情况

思考题:概率 p 生成 0,1-p 生成 1,如何 1/2 概率生成 1

二面

算法题:

1. https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

2. https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/

3. https://leetcode-cn.com/problems/coin-change-2/

讲一下 es 索引的过程

切词怎么切,切词算法,降噪

让你带应届生,怎么带,有什么工程经验可以分享

Redis 缓存淘汰有哪些

三面

自我介绍

算法题:

1. https://leetcode-cn.com/problems/first-missing-positive/

文章下面的评论,按点赞数排序,SQL 怎么写

把所有评论放到内存里,怎么设计数据结构,存储并排序

select * 会有什么问题

缓存热 key 怎么解决

职业发展

领导如何评价你

项目难点,亮点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值