go基础问题

发布时间:2023年12月21日

go基础问题

Go相关:

1、协程与线程

线程拥有自己的独立的栈和共享的堆,也是由操作系统进行调度。

协程共享堆,不共享栈,协程的调度由用户控制。

协程优点:

1)代码编辑简单,可以用同步的方式去写异步代码。

2)单线程模式,没有线程安全的问题,不需要加锁操作。

3)性能好,协程是用户态线程,切换更加高效。

4)线程无法掌控生命周期等要素。

2、go适合做什么

高并发的工作(如爬虫),微服务通信(grpc框架)

3、数据一致性?

在Go语言中,保证数据的一致性通常需要使用互斥锁(Mutex)或读写锁(RWMutex)等机制来实现。这些机制可以防止多个goroutine同时对同一个数据进行读写操作而出现数据不一致的情况。

4、你为什么选择学习Go语言?*

①Go语言具有简洁、易读和高效的语法。

语法简洁,没有java那么多的关键字和语法规则(如静态成员变量、继承),并且能推断类型;

快速编译,由于其静态类型系统和简洁的语法;

具有高性能和并发特性,使用协程和并发非常简单;

第三方库导入简单,与java相比,java下载导入依赖都很麻烦。

②庞大的开发者社区。

Go是谷歌开发的语言,有谷歌背书,使得社区非常活跃。

数据库相关:

1、数据库设计原则

三大范式:属性不可分、属性全依赖任一关键属性、不传递依赖。

索引:常用主键索引,主键索引=唯一索引+非空。

2、数据库注入问题

使用ORM框架(参数化查询,不使用SQL语句),限制输入格式,密码加密。

扩展->效验器(反射实现)

3、多级分类的设计

一般来说,一个分类就要设计一张表,联表查询效率低。

在同一张表中,我们可以使用类似二进制的方式定义字段,00,10,11。

4、mysql优化

建立索引。把数据量最大的表放到最外层,建立子表的查询路径。

其他:

1、git几个操作

查看/创建/删除分支:

git branch;git branch ;git branch -d

切换分支:git checkout

创建+切换分支:git checkout -b

合并某分支到当前分支:git merge

一、

1、

const cl = 100

func main() {

println(&cl,cl)

}

报错: cannot take the address of cl

常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。

2、

func main() {

type MyInt1 int

type MyInt2 = int

var i int =9

var i1 MyInt1 = i

var i2 MyInt2 = i

fmt.Println(i1,i2)

}

考点:**Go 1.9 新特性 Type Alias **

基于一个类型创建一个新类型,称之为defintion;基于一个类型创建一个别名,称之为alias。 MyInt1为称之为defintion,虽然底层类型为int类型,但是不能直接赋值,需要强转; MyInt2称之为alias,可以直接赋值。

3、

var ErrDidNotWork = errors.New(“did not work”)

func DoTheThing(reallyDoIt bool) (err error) {

if reallyDoIt {

result, err := tryTheThing()

if err != nil || result != “it worked” {

err = ErrDidNotWork

}

}

return err

}

func tryTheThing() (string,error) {

return “”,ErrDidNotWork

}

func main() {

fmt.Println(DoTheThing(true))

fmt.Println(DoTheThing(false))

}

if 语句块内的 err 变量会遮罩函数作用域内的 err 变量,结果:

4、fallthrough关键字的作用是什么?(重点)

其他语言中,switch-case 结构中一般都需要在每个 case 分支结束处显式的调用 break 语句以防止 前一个 case 分支被贯穿后调用下一个 case 分支的逻辑,go 编译器从语法层面上消除了这种重复的工作,让开发者更轻松;但有时候我们的场景就是需要贯穿多个 case,但是编译器默认是不贯穿的,这个时候 fallthrough 就起作用了,让某个 case 分支再次贯穿到下一个 case 分支。

ps: golang 的 select是一个面向channel的IO操作。

5、go 中除了加 Mutex 锁以外还有哪些方式安全读写共享变量?**

1、go -Channel,可以理解为先进先出的队列。

2、redis并发锁。

3、CAS无锁编程。

7、值传递和指针传递有什么区别

值传递:会创建一个新的副本并将其传递给所调用函数或方法

指针传递:将创建相同内存地址的新副本

需要改变传入参数本身的时候用指针传递,否则值传递

值传递或者指针传递都有可能发生逃逸,关键是有没有外部引用

8、defer语句(只)注册了一个函数调用,这个调用会延迟到defer语句所在的函数执行完毕后执行

s:=newSlice()

defer s.Add(1).Add(2).Add(4)

s.Add(3)

// 输出1234

9、pprof

pprof可以记录程序的运行信息,用于检查内存泄漏。内存泄漏会导致系统崩溃。我们还可以使用浏览器来查看系统的实时内存信息(包括CPU、内存、goroutine等的信息)。

参考: https://www.liwenzhou.com/posts/Go/performance_optimisation

net/http/pprof:使用服务器的场景

runtime/pprof:使用在非服务器应用程序的场景

http场景:

你的 HTTP 服务都会多出/debug/pprof,访问它会得到类似下面的内容:

在这里插入图片描述

在这里插入图片描述

使用命令go tool pprof url就可以获取指定profile文件,下载到本地,使用命令行进行分析:

如:

下载 cpu profile

go tool pprof http://127.0.0.1:6060/debug/pprof/profile

go tool pprof是个好用的工具:

go tool pprof [binary] [source]

binary 是应用的二进制文件,用来解析各种符号;

source 表示 profile 数据的来源,可以是本地的文件,也可以是 http 地址。

例子:

go build

./runtime_pprof.exe -cpu

//cpu.pprof是生成的本地文件

go tool pprof cpu.pprof

(pprof) 交互式界面常用命令:

topN:列出cpu占用高的函数

traces:列出函数调用栈

list 函数名:命令查看具体的函数分析

web

quit

此外,还可以通过go-torch生成火焰图

11、

panic需要等defer结束后才会向上传递。出现panic的时候,会先按照defer的后入先出的顺序执行,最后才会执行panic。

12、

Go语言中的引用类

型只有五个:

切片 映射 函数 方法 通道

nil只能赋值给上面五种引用类型的变量以及指针变量。

在这里插入图片描述

13、

go语言的指针不支持指针运算,指针运算包括:可以通过“&”取指针的地址、可以通过“*”取指针指向的数据

14、基本排序,哪些是稳定的

冒泡排序、直接插入排序、归并排序是稳定的排序算法。

快速排序、希尔排序、堆排序不是稳定的排序算法。

15、go struct能不能比较

可以能,也可以不能。

因为go存在不能使用==判断类型:map、slice,如果struct包含这些类型的字段,则不能比较。

这两种类型也不能作为map的key。

16、defer可以操作返回值吗?

defer类似栈操作,后进先出。

因为go的return是一个非原子性操作,比如语句 return i,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的。

17、select可以做什么

goroutine超时设置。

判断channel是否已满或空。如实现一个线程池。

18、结构体struct

一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。(该限制同样适用于数组。)但是S类型的结构体可以包含*S指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。

二、

1、golang 中 make 和 new 的区别?(基本必问)***

二者都是内存的分配(堆上),但是make只用于slice、map以及channel的初始化(非零值);而new用于类型的内存分配,并且内存置为零。new不常用.

  • **对于引用类型(slice、map、channel)的变量,我们不光要声明它,还要为它分配内容空间,否则报错:

var s1 *S = new(S)

相当于

new(File) 和 &File{}

func main() {

var i *int

// i=new(int)

//不声明内存类型会报错

  • i=10

fmt.Println(*i)

}

2、数组和切片的区别 (基本必问)***

数组切片
零值元素类型的零值nil
长度固定长度可变长度
类型值类型引用类型

nil 也是唯一可以和切片类型作比较的值

3、for range 的时候它的地址会发生变化么?

通过使用 for range 遍历切片,每次遍历操作实际上是对遍历元素的拷贝。而使用 for 遍历切片,每次遍历是通过索引访问切片元素,性能会远高于通过 for range 遍历。

因此想要优化使用 for range 遍历切片的性能,可以使用空白标识符 _ 省略每次遍历返回的切片元素,改为使用切片索引取访问切片的元素。

4、go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?

①多个defer的执行顺序为“后进先出”;

②defer、return、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。

无名返回值和最初的返回值相同

有名返回值和函数返回值为地址可能和最初的返回值不相同

5、 uint 类型溢出#

var a uint8 = 1

var b uint8 = 255

a-b=2

6、介绍 rune 类型?和byte的区别?(基本必问)***

在 Go 语言中支持两个字符类型,一个是 byte (实际上是 uint8 的别名),代表字符串 的单个字节的值,用来储存ASCII码,表示一个ASCII码字符;另一个是 rune(实际上是int32),代表单个 Unicode字符,常用来处理unicode或utf-8字符(一切字符),就是rune的使用范围更大。

s:=“3”;s[0] //uint8

a:=‘(’ //rune

rune字符类型int32,专用于存储unicode编码

byte字节型uint8

golang中默认存储字符串是采用utf8格式,如果直接len(中文字符), 那么会当成byte来进行处理,会乱码。使用[]rune()将utf8转换成4个字节的int32存储,可以直接计算汉字个数。

此外,range遍历字符串会按照rune为单位自动去处理。

8、调用函数传入结构体时,应该传值还是指针?

值传递:会创建一个新的副本并将其传递给所调用函数或方法

指针传递:将创建相同内存地址的新副本

需要改变传入参数本身的时候用指针传递,否则值传递

值传递或者指针传递都有可能发生逃逸,关键是有没有外部引用

9、JSON 标准库对 nil slice 和 empty slice 的处理是一致的吗?

nil slice :var s1 []int

empty slice : s1:=[]int{} || s1 := make([]int,0)

JSON 标准库对 nil slice 和 empty slice 的处理是不一致.

nil slice用于需要返回slice的函数,当函数出现异常的时候,保证函数依然会有nil的返回值。

当我们查询或者处理一个empty slice的时候,它会告诉我们返回的是一个slice,但是slice内没有任何值。

10、slice的len,cap,共享,扩容(基本必问)***

slice:=make([]int,len,cap)

slice := make([]type, len)

容量是指底层数组的大小,长度指可以使用的大小

len:切片的长度。

cap:切片的容量。默认扩容是2倍,当达到1024的长度后,按1.25倍。

扩容:每次扩容slice底层都将先分配新的容量的内存空间,再将老的数组拷贝到新的内存空间。这个操作不是并发安全的,可能导致append的数据丢失。

共享:slice的底层是对数组的引用,因此如果两个切片引用了同一个数组片段,就会形成共享底层数组。当slice发生内存的重新分配(如扩容)时,会对共享进行隔断。

11、孤儿进程,僵尸进程#

僵尸进程:

即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。

孤儿进程:

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID,损害运行系统。

12、go程序初始化顺序

main包->import->全局const->全局var->init()->main()

13、闭包的生命周期和作用范围

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包

闭包的作用:

延长局部变量的生命周期

让函数外部能操作内部的局部变量

闭包的生命周期:

产生:在嵌套内部函数定义执行完成时就产生了闭包

死亡:在嵌套的内部函数成为垃圾对象时

例:

i := incr()

println(i()) //会递增

println(incr()()) //不会递增

14、测试文件必须以什么结尾?功能测试函数必须以什么开头?压力测试函数必须以什么开头?

_test.go

Test

Benchmark

15、panic

panic 能够改变程序的控制流,函数调用panic 时会立刻停止执行函数的其他代码,并在执行结束后在当前 Goroutine 中递归执行调用方的延迟函数调用 defer

recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥任何作用

Q:若干个Goroutine,其中一个panic,会发生什么?

1.运行时恐慌,当panic被抛出异常后,如果我们没有在程序中添加任何保护措施的话,程序就会打印出panic的详细情况之后,终止运行。

2.有panic的子协程里的defer能执行,主协程和其他子协程里的defer不执行或者只能执行一半,这打破了【defer函数一定执行】的规则。

Q:defer可以捕获go协程的子协程的panic吗?

不能

1、recover 只能获取同协程内的panic

2、defer 获取recover 只能在直接func 内获取,不能再跨一个func

例:

  1. 不能获取panic

func main() {

defer recover()

go test()

}

  1. 可以获取同协程内的panic

func main() {

defer recover()

test()

}

Q:recover能处理所有的异常吗?

recover并非万能的,它只对用户态下的panic关键字有效

实际上在Go语言中,是存在着一些无法恢复的“恐慌”事件的,如fatalthrow方法、fatalpanic方法等,这些都是直接通过调用 exit() 方法进行中断的,属于无法恢复的“恐慌”事件,比如:对于go1.6以上版本,如果出现 并发map读写 程序会直接崩溃

15、restful API

统一资源标识符(Uniform Resource Identifier, URI)

统一资源定位符(Uniform Resource Locator, URL)

REST是一种基于HTTP的网络应用程序的设计架构风格和开发方式。

API 是两个计算机通过互联网交换信息的接口。

RESTful架构是对MVC架构改进后所形成的一种架构,通过使用事先定义好的接口与不同的服务联系起来。在RESTful架构中,浏览器使用 GET 、POST等请求方式分别对指定的URL资源进行增删改查操作。因此,RESTful是通过URI实现对资源的管理及访问,具有扩展性强、结构清晰的特点。

RESTful常用在前后端分离应用中,后端服务器为前端服务器提供接口。浏览器向前端服务器请求视图,通过视图中包含的AJAX函数发起接口请求获取模型,即使获取后端数据失败也能呈现视图。

(与RESTful相对应的风格有JSP,RPC API)

文章来源:https://blog.csdn.net/corruptwww/article/details/135112993
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。