Go面试题汇总
1. golang协程为什么比线程轻量?
1. go协程调用跟切换比线程效率高。
线程是内核对外提供的服务,应用程序可以通过系统调用让内核启动线程,由内核来负责线程调度和切换。线程在等待IO操作时线程变为unrunnable状态会触发上下文切换。现代操作系统一般都采用抢占式调度,上下文切换一般发生在时钟中断和系统调用返回前,调度器计算当前线程的时间片,如果需要切换就从运行队列中选出一个目标线程,保存当前线程的环境,并且恢复目标线程的运行环境,最典型的就是切换ESP指向目标线程内核堆栈,将EIP指向目标线程上次被调度出时的指令地址。
2. go协程占用内存少。
执行go协程只需要极少的栈内存(大概是4~5KB),默认情况下,而线程栈的大小为1MB。goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行。
2. 数组与切片比较
数组:
1. 数组是具有固定长度的,并且拥有0个或者多个相同元素的数据类型,数组在使用前必须确定数组长度,。
2. 数组的长度是数组类型的一部分,比如[3]int 和 [4]int是两种不同类型的数组,可以通过内置函数len(array)来获取数组的长度。
3. 数组需要指定大小,如果不指定会根据初始化来自动推算出大小,在初始化之后不可改变,无法修改其长度。
4. 数组是值传递。
5. 数组是内置(build-in)类型,是一组同一类型数据的集合,它是值类型。
6. 和php等语言一样,go数组从0开始的下标索引来访问数组元素。
切片:
1. 切片表示一个拥有相同类型的可变长度的序列。它对底层的数组(内部是通过数组保存数据的)进行了抽象,并提供相关的操作方法。
2. 切片是一种轻量级的数据结构,它有三个属性:指针,长度,容量。这些属性是 Golang 需要操作底层数组的元数据:
切片数据结构的定义:
type slice struct { array unsafe.Pointer len int cap int }
上面结构体里的3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。
3. 切片不需要指定大小。
4. 切片是地址传递。
5. 切片可以痛殴数组来初始化,也可以通过内置函数make()来初始化。初始化时候len = cap,在append等追加元素时候容量不够(cap不足)时候将len扩容2倍。
3. cap()和len()函数的区别
len()返回切片中的元素个数。
cap()返回切片的容量即切片最长可以容纳的元素数量。
package main import "fmt" func main() { var makeSlice = make([]int,3,5) dumpSlice(makeSlice) } func dumpSlice(makeSlice []int){ fmt.Printf("len长度=%d cap长度=%d slice=%v\n",len(makeSlice),cap(makeSlice),makeSlice) }
输出结果:
len长度=3 cap长度=5 slice=[0 0 0]
4. 创建切片slice几种方式
// 直接声明 var slice []int // make的方式 var makeSlice1 = make([]int,3,5) // 创建一个类型为 int,长度为 3,容量为 5 的切片 var makeSlice2 = make([]int,3) // 不指定容量,默认容量等于初始时的长度 // 字面量方式 var strSlice = []int{1,2,3,4,5} // 从数组中截取 var array = [...]int{1, 2, 3, 4, 5, 6} myslice := array[1:4]
5. map 是无序的吗?
是的。看下面代码
m:= map[string]string{"1":"aaa","2":"bbb","3":"ccc","4":"ddd","5":"eee"} for k, v := range m { log.Printf("k: %v, v: %v", k, v) }
打印出来结果都是随机无序的。
如何变成有序的输出?
可以先将key进行索引后,在使用sort排序key,然后在根据排序好的索引进行打印输出。
m:= map[string]string{"1":"aaa","2":"bbb","3":"ccc","4":"ddd","5":"eee"} var keys [] string for k := range m{ keys = append(keys,k) } fmt.Println(keys,len(keys)) sort.Strings(keys)//排序切片key for _,val := range keys{ //循环key取值 fmt.Println(m[val]) }
上面使用了sort.Strings排序,对于golang中的排序
go语言对于 int 、 float64 和 string 数组或是分片的排序, go 分别提供了 sort.Ints()
、 sort.Float64s()
和 sort.Strings()
函数, 默认都是从小到大排序。
6. 如何倒叙一个数组
使用sort提供的一些方法
import ( "fmt" "sort" ) var s []int func main() { s := []int{5, 2, 6, 3, 1, 4} sort.Sort(sort.Reverse(sort.IntSlice(s))) fmt.Println(s) }
打印结果:
[1 2 3 4 5 6]
7. make和new的区别
分别看下make和new的定义
make:
// The make built-in function allocates and initializes an object of type // slice, map, or chan (only). Like new, the first argument is a type, not a // value. Unlike new, make's return type is the same as the type of its // argument, not a pointer to it. The specification of the result depends on // the type: // Slice: The size specifies the length. The capacity of the slice is // equal to its length. A second integer argument may be provided to // specify a different capacity; it must be no smaller than the // length, so make([]int, 0, 10) allocates a slice of length 0 and // capacity 10. // Map: An empty map is allocated with enough space to hold the // specified number of elements. The size may be omitted, in which case // a small starting size is allocated. // Channel: The channel's buffer is initialized with the specified // buffer capacity. If zero, or the size is omitted, the channel is // unbuffered. func make(t Type, size ...IntegerType) Type
new:
// The new built-in function allocates memory. The first argument is a type, // not a value, and the value returned is a pointer to a newly // allocated zero value of that type. func new(Type) *Type
1. 从入参看,new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针,make接受参数是两个。
2. make只能创建内建类型(slice、map、chan),因为对于引用类型的变量,不光要声明它,还要为它分配内容空间,同时返回这三个引用类型本身,并且初始化。
new则是可以对所有类型进行内存分配,返回的是指向类型的指针。
看下面代码
var i *int *i=10 fmt.Println(*i)
输出结果报错。
如果使用new方式修改,如下
var i *int i=new(int) *i=10 fmt.Println(*i)
打印就是对的。
3. new返回指针, make 返回引用。
4. new初始化零值, make初始化非零值。
8. uint,int,int8, int16,int32, int64的区别
查看下面代码所打印的结果
var i1 int = 1 var i2 int8 = 2 var i3 int16 = 3 var i4 int32 = 4 var i5 int64 = 5 var i6 uint = 6 fmt.Println("int类型的大小=",unsafe.Sizeof(i1)) fmt.Println("int8类型大小=",unsafe.Sizeof(i2)) fmt.Println("int16类型大小=",unsafe.Sizeof(i3)) fmt.Println("int32类型大小=",unsafe.Sizeof(i4)) fmt.Println("int64类型大小=",unsafe.Sizeof(i5)) fmt.Println("uint类型大小=",unsafe.Sizeof(i6))
int包括有符号整型或无符号整型。
uint和uint8等都属于无符号int类型。
go语言中的int的大小是和操作系统位数相关的,如果是32位操作系统就是4个字节,如果是64位就是8个字节。
范围大小列表
- 有符号整型范围
Int8 | [-128 : 127] |
Int16 | [-32768 : 32767] |
Int32 | [-2147483648 : 2147483647] |
Int64 | [-9223372036854775808 : 9223372036854775807] |
- 无符号整型范围
UInt8 | [0 : 255] |
UInt16 | [0 : 65535] |
UInt32 | [0 : 4294967295] |
UInt64 | [0 : 18446744073709551615] |
9. sprintf、fprintf和printf有什么区别
10. nil 切片和空切片区别