1、判断浮点数是否为整数
func IsInt(bits uint32, bias int) {
expoent := int(bits>>23) - bias - 23
coefficient := (bits & ((1 << 23) - 1)) | (1 << 23)
intTest := (coefficient & (1 << uint32((-expoent)-1)))
fmt.Printf("\nExpoent:%d Coefficient: %d IntTest: %d\n",
expoent,
coefficient,
intTest)
if expoent < -23 {
fmt.Println("Not INTEGER\n")
return
}
if expoent < 0 && intTest != 0 {
fmt.Println("Not INTEGER\n")
return
}
fmt.Println("INTEGER\n")
}
2、为什么两个不同类型的数相乘和相除结果是整数或者浮点数
在Go语言规范中,对于常量表达式也制定了专门的规则。除了移位操作,如果操作数两边是不同类型的无类型常量,则结果类型的优先级为:整数(int)<符文数(rune)<浮点数(float)<复数(Imag)。根据此规则,上面两个常数之间相乘的结果将是一个浮点
数,因为浮点数的优先级比整数高。下面的例子结果为浮点数。const third = 1/3.0 原因:因为浮点数优先级最高,所以结果就返回浮点数。 const zero = 1 / 3 原因:因为两个数都是整数,所以结果就返回整数。
常量分为命名常量与未命名常量。未命名常量只会在编译期间存在,因此不会存储在内存中。而命名常量存在于内存静态只读区,不能被修改。同时,Go语言禁止对常量取地址的操作。常量作为Go语言独特的功能之一,离不开编译时的解析。具体来说,隐式类型转换的规则为:有类型常量优先于无类型常量,当两个无类型常量进行运算时,结果类型的优先级为:整数(int)<符文数(rune)<浮点数(float)<复数(Imag)。
3、字符串占字节问题
普通字母都只占据1字节,但是特殊的字符(例如大部分中文)会占据3字节
var b = "GO语言" fmt.Println("字节:", len(b)) //8 两个字符是1*2=2 两个汉字是3*2=6 总共为8 for i := 0; i < len(b); i++ { fmt.Printf("%x\n", b[i]) }
fmt.Printf有一个特殊的格式化符#U可用于打印符文数十六进制 的Unicode编码方式及字符形状。如上例打印出: var b = "GO语言" for index, runeValue := range b { fmt.Printf("%#U starts at byte position %d\n", runeValue, index) } U+0047 'G' starts at byte position 0 U+004F 'O' starts at byte position 1 U+8BED '语' starts at byte position 2 U+8A00 '言' starts at byte position 5
在标准库strings包中包含字符查找、分割、大小写转换、trim修剪等数十个函数
//判断字符串s是否包含substr字符串 func Contains(s, substr string) bool //判断字符串s是否包含chars字符串中的任一字符 func ContainsAny(s, chars string) bool //判断字符串s是否包含符文数r func ContainsRune(s string, r rune) bool //将字符串s以空白字符分割,返回一个切片 func Fields(s string) []string //将字符串s以满足f(r)==true的字符分割,返回一个切片 func FieldsFunc(s string, f func(rune) bool) []string //将字符串s以sep为分隔符进行分割,分割后字符串末尾去掉sep func Split(s, sep string) []string
在标准库strconv包中,还包含很多字符串与其他类型进行转换的函数,如下所示:
//字符串转换为十进制整数 func Atoi(s string) (int, error) //字符串转换为某一进制的整数,例如八进制、十六进制 func ParseInt(s string, base int, bitSize int) (i int64, err error) //整数转换为字符串 func Itoa(i int) string //某一进制的整数转换为字符串,例如八进制整数转换为字符串 func FormatInt(i int64, base int) string
4、如何避免哈希碰撞?
一般有两种主要的策略:拉链法及开放寻址法。
拉链法:将同一个桶中的元素通过链表的形式进行链接,这是一种最简单、最常用的策略。随着桶中元素的增加,可以不断链接新的元素,同时不用预先为元素分配内存。
缺点:需要存储额外的指针用于链接元素,这增加了整个哈希表的大小。
同时由于链表存储的地址不连续,所以无法高效利用CPU高速缓存。
开放寻址法:(OpenAddressing)所有元素都存储在桶的数组中。当必须插入新条目时,将按某种探测策略操作,直到找到未使用的数组插槽为止。当搜索元素时,将按相同顺序扫描存储桶,直到查找到目标记录或找到未使用的插槽为止。
Go语言中的哈希表采用的是开放寻址法中的线性探测(LinearProbing)策略,线性探测策略是顺序(每次探测间隔为1)的。由于良好的CPU高速缓存利用率和高性能,该算法是现代计算机体系中使用最广泛的结构。
5、Go语言中的可比较性
◎布尔值是可比较的。
◎ 整数值可比较的。
◎ 浮点值是可比较的。
◎ 复数值是可比较的。
◎ 字符串值是可比较的。
◎ 指针值是可比较的。如果两个指针值指向相同的变量,或者两个指针的值均为nil,则它们相等。
◎ 通道值是可比较的。如果两个通道值是由相同的make函数调用创建的,或者两个值都为nil,则它们相等。
◎ 接口值是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者两个接口值都为nil,则它们相等。
◎ 如果结构的所有字段都是可比较的,则它们的值是可比较的。
◎ 如果数组元素类型的值可比较,则数组值可比较。如果两个数组对应的元素相等,则它们相等。
◎ 切片、函数、map是不可比较的。
6、Go语言为什么不支持并发的读写
和其他语言不同的是,map并不支持并发的读写,map并发读写是初级开发者经常会犯的错误。下面的操作由于协程并发读写会报错为fatal error:concurrent map read and map write。
aa := make(map[int]int) go func() { for{ aa[0]=5 //写 } }() go func() { for { _=aa[1] //读 } }()
Go语言只支持并发读取map,因此下面的函数不会报错。
aa := make(map[int]int) go func() { for { _=aa[0] //读 } }() go func() { for { _ = aa[1] //读 } }()
Go语言为什么不支持并发的读写,是一个频繁被提起的问题。我们可以在Go官方文档的Frequently Asked Questions [3] 中找到问题的答案。官方文档的解释是:“map不需要从多个Goroutine安全访问,在实际情况下,map可能是某些已经同步的较大数据结构或计算的一部
分。因此,要求所有map操作都互斥将减慢大多数程序的速度,而只会增加少数程序的安全性。”即Go语言只支持并发读写的原因是保证大多数场景下的查找效率。
7、go中panic和recover嵌套容易踩坑
func a1() { defer b() panic("a panic") } func b() { defer fb() panic("b panic") } func fb() { panic("fb painc") } func main() { a1() } 最终程序输出如下,先打印最早出现的panic,再打印其他的panic。后面会看到,每一次panic调用都新建了一个_panic结构体,并用一个链表进行了存储 panic: a panic panic: b panic panic: fb painc
嵌套panic不会陷入死循环,每个defer函数都只会被调用一次。当嵌套的panic遇到了recover时,情况变得更加复杂。将上面的程序稍微改进一下,让main函数捕获嵌套的panic。
func a1() { defer b() panic("a panic") } func b() { defer fb() panic("b panic") } func fb() { panic("fb painc") } func catch(funcname string) { if r := recover(); r != nil { fmt.Println(funcname, "recover:", r) } } func main() { defer catch("main") a1() }
最终程序的输出结果为main recover:fb panic,这意味着recover函数最终捕获的是最近发生的panic,即便有多个panic函数,在最上层的函数也只需要一个recover函数就能让函数按照正常的流程执行。如果panic发生在函数b或函数fb中,则情况会有所不同。例如,将函数fb改写如下,内部的recover只能捕获由当前函数或其子函数触发的panic,而不能触发上层的panic
func fb() { defer catch("fb") panic("fb painc") } 输出 fb recover: fb painc panic: a panic panic: b panic
8、go中通过reflect反射结构体输出insert语言
package main import ( "fmt" "reflect" ) type Student struct { Age int Name string } func main() { fmt.Println("输出:", createQuery(Student{ Age: 12, Name: "张三", })) //输出 insert into Student values(12, '张三') } func createQuery(q interface{}) string { //判断类型为结构体 if reflect.ValueOf(q).Kind() == reflect.Struct { //获取结构体名称 t := reflect.TypeOf(q).Name() //查询语句 query := fmt.Sprintf("insert into %s values(", t) v := reflect.ValueOf(q) //遍历结构体字段 for i := 0; i < v.NumField(); i++ { //判断结构体类型 switch v.Field(i).Kind() { case reflect.Int: if i == 0 { query = fmt.Sprintf("%s%d", query, v.Field(i).Int()) } else { query = fmt.Sprintf("%s, %d", query, v.Field(i).Int()) } case reflect.String: if i == 0 { query = fmt.Sprintf("%s%s", query, v.Field(i).String()) } else { query = fmt.Sprintf("%s, '%s'", query, v.Field(i).String()) } //剩下字段填充 } } query = fmt.Sprintf("%s)", query) fmt.Println(query) return query } return "" }
通过Kind类型可以方便地验证反射的类型是否相同。
num := 12.45 equl := reflect.TypeOf(num).Kind() == reflect.Float64 fmt.Println("kind is float64:", equl)
reflect.Value中的Interface方法以空接口的形式返回reflect.Value中的值。如果要进一步获取空接口的真实值,可以通过接口的断言语法对接口进行转换。下例实现了从值到反射,再从反射到值的过程。
var num float64 = 1.2345 pointer := reflect.ValueOf(&num) value := reflect.ValueOf(num) convertPointer := pointer.Interface().(*float64) convertValue := value.Interface().(float64) fmt.Println(convertValue, convertPointer) 输出:1.2345 0xc0000aa058
如果要转换的类型与实际类型不相符,则会在运行时报错。下例的反射中存储的实际是int指针,如果要转换为int类型,则会报错。
a := 56 x := reflect.ValueOf(&a).Int() fmt.Println(x) panic: reflect: call of reflect.Value.Int on ptr Value
如果反射中存储的是指针或接口,那么如何访问指针指向的数据呢?reflect.Value提供了Elem方法返回指针或接口指向的数据。
a := 56 x := reflect.ValueOf(&a).Elem().Int() fmt.Println(x) 输出:56
9、go中进程、线程、协程
操作系统调度到CPU中执行的最小单位是线程。在传统的单核(Core)CPU上运行的多线程应用程序必须交织线程,交替抢占CPU的时间片,如图所示。但是,现代计算机系统普遍拥有多核处理器。在多核CPU上,线程可以分布在多个CPU核心上,从而实现真正的并行处理。
当发生线程上下文切换时,需要从操作系统用户态转移到内核态,记录上一个线程的重要寄存器值(例如栈寄存器SP)、进程状态等信息,这些信息存储在操作系统线程控制块
(Thread Control Block)中。当切换到下一个要执行的线程时,需要加载重要的CPU寄存器值,并从内核态转移到操作系统用户态。如果线程在上下文切换时属于不同的进程,那么需要更新额外的状态信息及内存地址空间,同时将新的页表(Page Tables)导入内存。调度方式:
协程是用户态的。协程的管理依赖Go语言运行时的调度器。同时,Go语言中的协程是从属于某一个线程的,协程与线程的对应关系为M:N,即多对多,如下图所示。Go语言调度器可以将多个协程调度到一个线程中,一个协程也可能切换到多个线程中执行。
上下文切换的速度 :
协程的速度要快于线程,其原因在于协程切换不用经过操作系统用户态与内核态的切换,并且Go语言中的协程切换只需要保留极少的状态和寄存器变量值(SP/BP/PC),而线程切换会保留额外的寄存器变量值(例如浮点寄存器)。上下文切换的速度受到诸多因素的影
响,这里列出一些值得参考的量化指标:线程切换的速度大约为1~2微秒,Go语言中协程切换的速度比它快数倍,为0.2微秒左右 [3] 。调度策略:
线程的调度在大部分时间是抢占式的,操作系统调度器为了均衡每个线程的执行周期,会定时发出中断信号强制执行线程上下文切换。而Go语言中的协程在一般情况下是协作式调度的,当一个协程处理完自己的任务后,可以主动将执行权限让渡给其他协程。这意味着
协程可以更好地在规定时间内完成自己的工作,而不会轻易被抢占。当一个协程运行了过长时间时,Go语言调度器才会强制抢占其执行栈的大小:
线程的栈大小一般是在创建时指定的,为了避免出现栈溢出(Stack Overflow),默认的栈会相对较大(例如2MB),这意味着每创建1000个线程就需要消耗2GB的虚拟内存,大大限制了线程创建的数量(64位的虚拟内存地址空间已经让这种限制变得不太严重)。而Go
语言中的协程栈默认为2KB,在实践中,经常会看到成千上万的协程存在。同时,线程的栈在运行时不能更改,但是Go语言中的协程栈在Go运行时的帮助下会动态检测栈的大小,并动态地进行扩容。因此,在实践中,可以将协程看作轻量的资源。
10、go中并发和并行
并发与并行:
在Go语言的程序设计中,有两个非常重要但容易被误解的概念,分别是并发concurrency)与并行(parallelism)。通俗来讲,并发指同时处理多个任务的能力,这些任务是独立的执行单元。并发并不意味着同一时刻所有任务都在执行,而是在一个时间段内,所有的任务都能执行完毕。因此,开发者对任意时刻具体执行的是哪一个任务并不关心。如图14-5所示,在单核处理器中,任意一个时刻只能执行一个具体的线程,而在一个时间段内,线程可能通过上下文切换交替执行。多核处理器是真正的并行执行,因为在任意时刻,可以同时有多个线程在执行。在实际的多核处理场景中,并发与并行常常是同时存在的,即多核在并行地处理多个线程,而单核中的多个线程又在上下文切换中交替执行。由于Go语言中的协程依托于线程,所以即便处理器运行的是同一个线程,在线程内Go语言调度器也会切换多个协程执行,这时协程是并发的。如果多个协程被分配给了不同的线程,而这些线程同时被不同的CPU核心处理,那么这些协程就是并行处理的。因此在多核处理场景下,Go语言的协程是并发与并行同时存在的。
但是,协程的并发是一种更加常见的现象,因为处理器的核心是有限的,而一个程序中的协程数量可以成千上万,这就需要依赖Go语言调度器合理公平地调度。
总结:
进程是操作系统资源分配的基本单位,而线程是操作系统调度的基本单位。在一般情况下,线程是进程的组成部分,是不能脱离进程存在的。进程中的多个线程并发执行并共享进程的内存等资源。进程之间相对独立,不同进程具有不同的内存地址空间、操作系统资源描
述符等。协程是轻量级的线程,它依赖线程。由于协程具有更快的上下文切换速度、更灵活的调度策略、可伸缩的栈空间管理,借助简单的语法及运行时调度器的帮助,能够轻易编写出成千上万的协程,因此Go语言成为开发大规模、高并发项目的极佳选择。
11、日期转int保存数据库
//将前端传递过来的日期格式转换成int location, _ := time.LoadLocation("Local") birthDay, _ := time.ParseInLocation("2006-01-02", updateUserForm.Birthday, location)//将int转日期
"birthday": time.Unix(int64(rsp.BirthDay), 0).Format("2006-01-02"),
12、时区的问题
func main() { location, _ := time.LoadLocation("Asia/shanghai") inputTime := "2029-09-04 12:02:33" layout := "2006-01-02 15:04:05" t, _ := time.Parse(layout, inputTime) //自动是UTC时间 datetime := time.Unix(t.Unix(), 0).In(location).Format(layout) log.Printf("输入时间:%s,输出时间: %s", inputTime, datetime) //输入时间:2029-09-04 12:02:33,输出时间: 2029-09-04 20:02:33 t, _ = time.ParseInLocation(layout, inputTime, location)//本地时区 datetime = time.Unix(t.Unix(), 0).In(location).Format(layout) log.Printf("输入时间:%s,输出时间: %s", inputTime, datetime) //输入时间:2029-09-04 12:02:33,输出时间: 2029-09-04 12:02:33 }
13、go swagger的使用
PS D:\code\goproject\src\blog-service> go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2023/01/23 - 22:34:52 | 404 | 0s | ::1 | GET "/"
[GIN] 2023/01/23 - 22:34:52 | 404 | 0s | ::1 | GET "/favicon.ico"
[GIN] 2023/01/23 - 22:34:58 | 200 | 0s | ::1 | GET "/ping"
exit status 0xc000013a
PS D:\code\goproject\src\blog-service> go get -u gopkg.in/natefinch/lumberjack.v2
PS D:\code\goproject\src\blog-service> swag init
2023/01/24 13:42:40 Generate swagger docs....
2023/01/24 13:42:40 Generate general API Info, search dir:./
2023/01/24 13:42:44 ParseComment error in file internal\routers\api\v1\tag.go :cannot find type definition: model.TagSwagger
PS D:\code\goproject\src\blog-service> swag init
2023/01/24 13:43:08 Generate swagger docs....
2023/01/24 13:43:08 Generate general API Info, search dir:./
2023/01/24 13:43:08 Generating model.Tag
2023/01/24 13:43:08 Generating model.Model
2023/01/24 13:43:08 Generating errcode.Error
2023/01/24 13:43:08 create docs.go at docs\docs.go
2023/01/24 13:43:08 create swagger.json at docs\swagger.json
2023/01/24 13:43:08 create swagger.yaml at docs\swagger.yaml
PS D:\code\goproject\src\blog-service> go get -u github.com/swaggo/swag/cmd/[email protected]
go: downloading github.com/swaggo/swag v1.6.5
go: downloading github.com/go-openapi/spec v0.19.4
go: downloading github.com/go-openapi/jsonreference v0.19.3
go: downloading golang.org/x/tools v0.5.0
go: downloading github.com/urfave/cli v1.22.12
go: downloading github.com/go-openapi/jsonreference v0.20.2
go: downloading github.com/go-openapi/spec v0.20.8
go: downloading github.com/go-openapi/swag v0.19.5
go: downloading github.com/PuerkitoBio/purell v1.2.0
go: downloading github.com/go-openapi/jsonpointer v0.19.6
go: downloading github.com/go-openapi/swag v0.22.3
go: downloading golang.org/x/text v0.6.0
go: downloading golang.org/x/net v0.5.0
go: downloading golang.org/x/sys v0.4.0
go get: installing executables with 'go get' in module mode is deprecated.
To adjust and download dependencies of the current module, use 'go get -d'.
To install using requirements of the current module, use 'go install'.
To install ignoring the current module, use 'go install' with a version,
like 'go install example.com/cmd@latest'.
For more information, see https://golang.org/doc/go-get-install-deprecation
or run 'go help get' or 'go help install'.
go get: added github.com/KyleBanks/depth v1.2.1
go get: added github.com/PuerkitoBio/purell v1.2.0
go get: added github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
go get: added github.com/cpuguy83/go-md2man/v2 v2.0.2
go get: added github.com/ghodss/yaml v1.0.0
go get: added github.com/go-openapi/jsonpointer v0.19.6
go get: added github.com/go-openapi/jsonreference v0.20.2
go get: added github.com/go-openapi/spec v0.20.8
go get: added github.com/go-openapi/swag v0.22.3
go get: added github.com/josharian/intern v1.0.0
go get: added github.com/mailru/easyjson v0.7.7
go get: added github.com/russross/blackfriday/v2 v2.1.0
go get: added github.com/shurcooL/sanitized_anchor_name v1.0.0
go get: added github.com/swaggo/swag v1.6.5
go get: added github.com/urfave/cli v1.22.12
go get: upgraded golang.org/x/net v0.4.0 => v0.5.0
go get: upgraded golang.org/x/sys v0.3.0 => v0.4.0
go get: upgraded golang.org/x/text v0.5.0 => v0.6.0
go get: upgraded golang.org/x/tools v0.1.12 => v0.5.0
PS D:\code\goproject\src\blog-service>
PS D:\code\goproject\src\blog-service> go get -u github.com/swaggo/[email protected]
go: downloading github.com/swaggo/gin-swagger v1.2.0
go: downloading github.com/goccy/go-json v0.10.0
go: downloading github.com/mattn/go-isatty v0.0.17
go: downloading github.com/pelletier/go-toml v1.9.5
go: downloading github.com/ugorji/go/codec v1.2.8
go: downloading github.com/ugorji/go v1.2.8
go: downloading golang.org/x/crypto v0.5.0
go: downloading github.com/go-playground/locales v0.14.1
go: downloading github.com/ugorji/go v1.1.5-pre
go get: upgraded github.com/go-playground/locales v0.14.0 => v0.14.1
go get: upgraded github.com/goccy/go-json v0.9.11 => v0.10.0
go get: upgraded github.com/mattn/go-isatty v0.0.16 => v0.0.17
go get: upgraded github.com/swaggo/swag v1.6.5 => v1.8.9
go get: upgraded github.com/ugorji/go/codec v1.2.7 => v1.2.8
go get: upgraded golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e => v0.5.0
PS D:\code\goproject\src\blog-service> go get -u github.com/swaggo/files
go: downloading github.com/swaggo/files v1.0.0
go get: added github.com/swaggo/files v1.0.0
PS D:\code\goproject\src\blog-service> go get -u github.com/alecthomas/template
go get: added github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
PS D:\code\goproject\src\blog-service>