lifei6671 / interview-go Goto Github PK
View Code? Open in Web Editor NEWgolang面试题集合
License: Apache License 2.0
golang面试题集合
License: Apache License 2.0
您好,看代码定义的:
const (
Left = iota
Top
Right
Bottom
)
case s == 'L':
z = (z + 1) % 4
case s == 'R':
z = (z - 1 + 4) % 4
这个左右是不是调转了
过往经验来看
企业面试gopher的时候
比较高频的问题
大体还是集中在
goroutine,channel,gRPC,gin方面
希望可以多增加这方面的内容
golang case 语句出现在三个地方
var a = "hello"
switch a {
case "hello":
fmt.Println(1)
case "world":
fmt.Println(2)
default:
fmt.Println(0)
}
一分支多值
var a = "mum"
switch a {
case "mum", "daddy":
fmt.Println("family")
}
分支表达式
var r int = 11
switch {
case r > 10 && r < 20:
fmt.Println(r)
}
此时switch 没有变量和表达式,case 各分支默认和 true
比较
通过 fallthrough
继续执行其他case
var s = "hello"
switch {
case s == "hello":
fmt.Println("hello")
fallthrough
case s != "world":
fmt.Println("world")
}
这种情况其实属于上种情况,只是和接口类型断言关联,比较常见,特拿出来单说
switch v := i.(type) {
case int:
...
case string:
...
default:
...
}
关键字type只能用在switch语句里,如果用在switch外面会报错
select {
case v1 := <-c1:
fmt.Printf("received %v from c1\n", v1)
case v2 := <-c2:
fmt.Printf("received %v from c2\n", v1)
case c3 <- 23:
fmt.Printf("sent %v to c3\n", 23)
default:
fmt.Printf("no one was ready to communicate\n")
}
除 default 外,如果只有一个 case 语句评估通过,那么就执行这个case里的语句;
除 default 外,如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个;
如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句;
如果没有 default,那么 代码块会被阻塞,指导有一个 case 通过评估;否则一直阻塞
我注意到https://github.com/lifei6671/interview-go/blob/master/question/q010.md中缺少了Rd
func的实现,我完成了,并且经过了简单的测试。
func (m *Map) Rd(key string, timeout time.Duration) interface{} {
e, ok := m.c[key]
if !ok {
// create channel
m.c[key] = &entry{
isExist: false,
ch: make(chan struct{}),
}
}
// 阻塞
e, _ = m.c[key]
if e.ch != nil {
select {
case <-e.ch:
case <-time.After(timeout):
}
}
return m.c[key].value
}
看下 runtime,最后一个创建的 G 会赋值给 _runnext,第一个调度的就是它了。
https://github.com/lifei6671/interview-go/blob/master/algorithm/docs/match-sunday-string.md
精简版代码供参考:
func strStrSunday(haystack, needle string) int {
if haystack == "" {
return -1
}
if needle == "" {
return 0
}
haystackLen := len(haystack)
needleLen := len(needle)
i := 0
for {
// 先判断剩余字符串的长度是否足够
if i+needleLen > haystackLen {
return -1
}
// 匹配上之后,直接返回index
if haystack[i:i+needleLen] == needle {
return i
}
// 判断needle所在位置的下一个占位是否包含在haystack中
// needle所在位置的下一个占位可能超出haystack的长度
if i+needleLen == haystackLen {
return -1
}
//lastIdx := strings.LastIndex(needle, string(haystack[i+needleLen]))
lastIdx := -1
for j := needleLen - 1; j >= 0; j-- {
if haystack[i+needleLen] == needle[j] {
lastIdx = j
break
}
}
// 根据lastIdx结果进行不同的移位
if lastIdx >= 0 {
i += needleLen - lastIdx
} else {
i += needleLen + 1
}
}
}
示例代码:
letter,number := make(chan bool),make(chan bool)
wait := sync.WaitGroup{}
go func() {
i := 1
for {
select {
case <-number:
fmt.Print(i)
i++
fmt.Print(i)
i++
letter <- true
break
default:
break
}
}
}()
wait.Add(1)
go func(wait *sync.WaitGroup) {
str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
i := 0
for{
select {
case <-letter:
if i >= strings.Count(str,"")-1 {
wait.Done()
return
}
fmt.Print(str[i:i+1])
i++
if i >= strings.Count(str,"") {
i = 0
}
fmt.Print(str[i:i+1])
i++
number <- true
break
default:
break
}
}
}(&wait)
number<-true
wait.Wait()
这个代码正确得到了结果,但是代码它本身存在严重的瑕疵:
chan
没有数据时,陷入多余的循环,浪费硬件资源原题: https://github.com/lifei6671/interview-go/blob/master/question/q009.md
答案中done <- true
后面应该加上 return
,否则子协程不会立刻退出, 往已经关闭的 done 写数据
如果此后做一些处理的话就会触发 panic
当函数被循环调用时, 必现 panic: send on closed channel
func main() {
random := make(chan int)
done := make(chan bool)
go func() {
for {
num, ok := <-random
if ok {
fmt.Println(num)
} else {
done <- true
}
}
}()
go func() {
defer close(random)
for i := 0; i < 5; i++ {
random <- rand.Intn(5)
}
}()
<-done
close(done)
// 触发 panic
time.Sleep(1 * time.Second)
}
s := "hello world abc is a test chars in program develop comm"
index := strStrSunday(s, "common")
fmt.Println("common index is", index)
正常应该返回-1, 实际会返回54 什么的
附上根据原链接java 版本实现的一个方法:
`
func strStrSunday(haystack string, needle string) int {
if len(haystack) < len(needle) {
return -1
}
if haystack == needle {
return 0
}
originIndex := 0
aimIndex := 0
nextIndex := 0
for aimIndex < len(needle) {
if originIndex > len(haystack)-1 {
return -1
}
if haystack[originIndex] == needle[aimIndex] {
originIndex++
aimIndex++
} else {
nextIndex = originIndex - aimIndex + len(needle)
if nextIndex < len(haystack) {
offset := strings.LastIndex(needle, string(haystack[nextIndex]))
if offset == -1 {
originIndex = nextIndex + 1
} else {
originIndex = nextIndex - offset
}
aimIndex = 0
} else {
return -1
}
}
}
return originIndex - aimIndex
}
`
package main
import (
"fmt"
"time"
"sync"
)
type sp interface {
Out(key string, val interface{}) //存入key /val,如果该key读取的goroutine挂起,则唤醒。此方法不会阻塞,时刻都可以立即执行并返回
Rd(key string, timeout time.Duration) interface{} //读取一个key,如果key不存在阻塞,等待key存在或者超时
}
type Item2 struct {
value interface{}
ch chan struct{}
exist bool
}
type ConcurrentMap struct {
mapval map[string]*Item2
rwmutex *sync.RWMutex
}
func NewConcurrentMap() *ConcurrentMap {
return &ConcurrentMap{
mapval: make(map[string]*Item2, 0),
rwmutex: new(sync.RWMutex),
}
}
func (c *ConcurrentMap) Out(key string, val interface{}) {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
item, ok := c.mapval[key]
if !ok {
c.mapval[key] = &Item2{
value: val,
exist: true,
}
return
}
item.value = val
if !item.exist {
if item.ch != nil {
close(item.ch)
item.ch = nil
}
}
return
}
func (c *ConcurrentMap) Rd(key string, timeout time.Duration) interface{} {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
item, ok := c.mapval[key]
if !ok {
item = &Item2{
exist: false,
ch: make(chan struct{}, 1),
}
c.mapval[key] = item
}
if item.exist {
return item.value
}
select {
case <-time.After(timeout):
return nil
item, _ = c.mapval[key]
return item.value
}
func main() {
mapval := NewConcurrentMap()
mapval.Out("yanglei", "xx")
mapval.Out("yanglei", "yy")
fmt.Printf("%v\n", mapval.Rd("yanglei2", 5 * time.Second))
}
冒泡排序是通过数去找位置,这里的冒泡排序写成选择排序了,附代码
func bubbleSort(arr []int) []int {
if len(arr) == 0 {
return arr
}
n := len(arr)
for i := 0; i < n-1; i++ {
for j := 0; j < n-1-i; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
return arr
}
你有测试过你写的代码吗, 这个明显有问题啊
在交替打印数字和字母的解法里面,负责打印字母的goroutine可以正常退出,但是负责打印数字的goroutine是一个死循环,因此示例代码存在泄露goroutine的风险
go func() {
i := 1
for {
select {
case <-number:
fmt.Print(i)
i++
fmt.Print(i)
i++
letter <- true
}
}
}()
负责打印字母的goroutine在退出时可以执行close(number)
告知打印数字的goroutine退出
package main
import (
"fmt"
"sync"
)
func main() {
letter, number := make(chan bool), make(chan bool)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer func() {
fmt.Println("\nNumber goroutine exit.")
wg.Done()
}()
i := 1
for range number {
fmt.Print(i)
i++
fmt.Print(i)
letter <- true
}
}()
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer func() {
close(number)
fmt.Println("\nLetter goroutine exit.")
wg.Done()
}()
i := 'A'
for range letter {
if i >= 'Z' {
return
}
fmt.Print(string(i))
i++
fmt.Print(string(i))
i++
number <- true
}
}(&wg)
number <- true
wg.Wait()
}
这个题目,不发生内存拷贝?? 讲道理, 字符串数组转换成[]rune切片, 就已经发生了内存拷贝了好吧
func (p *Project) run(msgchan chan interface{}) {
for {
defer p.deferError()
go p.exec(msgchan)
time.Sleep(time.Second * 2)
}
}
这一段错误不仅是 defer p.deferError()
没有出现在协程内部,而是无节制地在创建协程。虽然通过 time.Sleep(time.Second * 2)
限制了频率,但如果以 Main 内 time.Sleep(time.Second * 100000000000000)
如此久的等待,迟早也会因为内存占用过大而报错。
有以下的两种情况,需要大家思考一下:
1、在规定的时间内,如果没找到,这种情况怎么处理,当前的处理方式是等到超时退出。
2、关于size的设定。需要根据具体情况跑benchmark,来确定最佳的性能。(同时也需要注意size太小的话,goroutine数量会太多)
给出的实例代码中 内循环没有判断是否超过字符串长度,如果给出[]string{"flower", "flow", "flow"} 这样的输入直接就panic了。
建议修改为
for i := range firstStr {
for j := 1; j < l; j++ {
if len(arr[j]) == i || arr[j][i] != firstStr[i] {
return firstStr[:i]
}
}
}
应该是先去其他p中拿,再去global中拿,写错了
type Student struct {
Age int
}
func main() {
kv := map[string]*Student{"a":{Age:21}}
kv["a"].Age = 22
fmt.Println(kv)
}
这样修改为啥不报错了呢
答案是:在多核CPU中,因为CPU缓存会导致多个核心中变量值不同步。
我的观点是:当前核心的变量值发生改变时,其他核心缓存中的值会置为不可用,当读取或修改时会重新从主存里读取。所以应该不会有不同步的问题。并且我也无法浮现这个问题 求解
func isUniqueString(s string) bool {
if len(s) > 256 {
return false
}
unique := make(map[int32]bool)
for _, v := range s {
if v > 256 {
return false
}
if _, ok := unique[v]; ok {
return false
} else {
unique[v] = true
}
}
return true
}
package main
import (
"fmt"
"time"
"sync"
)
type sp interface {
Out(key string, val interface{}) //存入key /val,如果该key读取的goroutine挂起,则唤醒。此方法不会阻塞,时刻都可以立即执行并返回
Rd(key string, timeout time.Duration) interface{} //读取一个key,如果key不存在阻塞,等待key存在或者超时
}
type Item2 struct {
value interface{}
ch chan struct{}
exist bool
}
type ConcurrentMap struct {
mapval map[string]*Item2
rwmutex *sync.RWMutex
}
func NewConcurrentMap() *ConcurrentMap {
return &ConcurrentMap{
mapval: make(map[string]*Item2, 0),
rwmutex: new(sync.RWMutex),
}
}
func (c *ConcurrentMap) Out(key string, val interface{}) {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
item, ok := c.mapval[key]
if !ok {
c.mapval[key] = &Item2{
value: val,
exist: true,
}
return
}
item.value = val
if !item.exist {
if item.ch != nil {
close(item.ch)
item.ch = nil
}
}
return
}
func (c *ConcurrentMap) Rd(key string, timeout time.Duration) interface{} {
c.rwmutex.Lock()
defer c.rwmutex.Unlock()
item, ok := c.mapval[key]
if !ok {
item = &Item2{
exist: false,
ch: make(chan struct{}, 1),
}
c.mapval[key] = item
}
if item.exist {
return item.value
}
select {
case <-time.After(timeout):
return nil
case <-item.ch:
}
item, _ = c.mapval[key]
return item.value
}
第六题机器人坐标计算
如果命令字符串包含嵌套的命令,那么题中的代码就会有问题。
举个例子,输入的命令为2(F3(B)),正确的答案是(0,-4)。然而给出的代码输出是(0,0)
正确的代码可以参考如下:
const (
NORTH = iota
EAST
SOUTH
WEST
)
type Offset struct {
x int
y int
}
var dirlist []Offset
var dirname []string
func init() {
dirname = []string{"NORTH", "EAST", "SOUTH", "WEST"}
dirlist = []Offset{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}
}
func getOffset(offset Offset, sign int, x int, y int) (int, int) {
return x + offset.x*sign, y + offset.y*sign
}
func rmove(dir int, x int, y int, start int, s string) (int, int, int, int) {
times := 0
i := start
//fmt.Printf("start:%d\n", start)
for ; i < len(s); i++ {
ch := s[i]
//fmt.Printf("ch:%d,", ch)
if ch >= '0' && ch <= '9' {
times = times*10 + int(ch-'0')
} else if ch == '(' {
next := 0
for j := 0; j < times; j++ {
x, y, dir, next = rmove(dir, x, y, i+1, s)
}
i = next
} else if ch == ')' {
break
} else if ch == 'R' {
dir = (dir + 1) % 4
} else if ch == 'L' {
dir = (dir + 3) % 4
} else if ch == 'F' {
x, y = getOffset(dirlist[dir], 1, x, y)
} else if ch == 'B' {
x, y = getOffset(dirlist[dir], -1, x, y)
}
}
//fmt.Printf("s:%s,x:%d,y:%d,dir:%d,i:%d\n", s, x, y, dir, i)
return x, y, dir, i
}
// @返回参数1: 作标
// @返回参数2:方向
func move(s string) ([]int, int) {
dir := NORTH
x, y, dir, _ := rmove(dir, 0, 0, 0, s)
//fmt.Printf("dir:%s\n", dirname[dir])
return []int{x, y}, dir
}
func main() {
abc := make(chan int, 1000)
for i := 0; i < 10; i++ {
abc <- i
}
go func() {
for {
a := <-abc
fmt.Println("a: ", a)
}
}()
close(abc)
fmt.Println("close")
time.Sleep(time.Second * 100)
}
这个最大的问题是没有做channel读取的检查吧 ,loop循环的时候如果不检查是否channel可读,会一直输出类型的初始化值(0) 上面的代码会打印大量无用的0值, 只要检查后退出即可
func main() {
abc := make(chan int, 1000)
for i := 0; i < 10; i++ {
abc <- i
}
go func() {
for {
a, ok := <-abc
if ok{
fmt.Println("a: ", a)
}else{
return
}
}
}()
close(abc)
fmt.Println("close")
time.Sleep(time.Second * 100)
}
第11题,是能正常执行完毕的,推测现在golang 的 GC 动作是不需要所有正在运行 goroutine 都停止后进行。猜测。
`
func threeSumClosest(nums []int, target int) int {
if len(nums) == 3 {
return nums[0] + nums[1] + nums[2]
}
sort.Ints(nums)
sum := nums[0] + nums[1] + nums[2]
for i := 0; i < len(nums); i++ {
l := i+1
r := len(nums) - 1
for l < r {
current := nums[i] + nums[l] + nums[r]
if math.Abs(float64(sum - target)) > math.Abs(float64(target - current)) {
sum = current
}
if current < target {
l++
} else if current == target {
return target
} else {
r--
}
}
}
return sum
}
`
func isUniqueString2(s string) bool {
if strings.Count(s,"") > 3000{
return false
}
for i, v := range s {
if v > 127 {
return false
}
if i != strings.LastIndex(s,string(v)) {
return false
}
}
return true
}
str := []rune(s)
这里的强制类型转换会生成新的空间
number, letter := make(chan struct{}), make(chan struct{}) letter <- struct{}{} 这种使用方式比 number, letter := make(chan bool), make(chan bool) letter <- true 更好些吧
数组可以比较,切片只可以与nil比较
看到前面已经有一个关于这个问题的讨论,我还是选择了开了一个新的issue谈谈我的想法。
除了使用加锁的方法,还可以使用channel来实现goroutine调度,这是一个典型的 pingpong
的问题,加上一个channel接受完成通知就好。
package main
import (
"fmt"
)
func main() {
ping := make(chan struct{}, 1)
pong := make(chan struct{}, 1)
done := make(chan struct{})
ping <- struct{}{}
go func() {
for i := 0; i < 10; i++ {
<-ping
fmt.Print(i)
pong <- struct{}{}
}
}()
go func() {
for i := 0; i < 10; i++ {
<-pong
fmt.Printf("%c", 'A'+i)
ping <- struct{}{}
}
done <- struct{}{}
}()
<-done
close(ping)
close(pong)
}
代码中 并没有用sync.RWMutex锁的特性 这样反而会引起歧义,还不如直接用sync.Mutex更清晰
https://github.com/lifei6671/interview-go/blob/master/question/q014.md
下面代码写法有什么问题?
package main
import (
"fmt"
)
type Student struct {
Age int
}
func main() {
kv := map[string]Student{"menglu": {Age: 21}}
kv["menglu"].Age = 22
s := []Student{{Age: 21}}
s[0].Age = 22
fmt.Println(kv, s)
}
是因为map中保存的是值类型 kv["menglu"] 操作是拿到map中的拷贝
如果换成 kv := map[string]*Student{"menglu": {Age: 21}} 使用指针就不会有问题
package main
import (
"fmt"
"runtime"
"sync"
)
const N = 28
func main() {
runtime.GOMAXPROCS(1)
wg := &sync.WaitGroup{}
wg.Add(N)
for i := 0; i < N; i++ {
go func(m int) {
defer wg.Done()
fmt.Printf(`%d%d`,m+1,m+2)
runtime.Gosched()
}(i)
go func(m int) {
defer wg.Done()
if m == 26 {
runtime.Goexit()
}
fmt.Printf(`%c%c`, 'A'+m, 'A'+m+1)
}(i)
i = i + 1
}
wg.Wait()
}
2个通知用的chan类型都为bool,但程序中只是单纯的发送信号,true/false的值的意义没有被使用。
题目:
6、请说明下面代码书写是否正确。
var value int32
func SetValue(delta int32) {
for {
v := value
if atomic.CompareAndSwapInt32(&value, v, (v+delta)) {
break
}
}
}
解析:
atomic.CompareAndSwapInt32 函数不需要循环调用。
疑问:
atomic.CompareAndSwapInt32
仅提供了原子交换的操作,我理解SetValue
这个函数的用意应该就是让value
加上delta
大小的数值,所以在atomic.CompareAndSwapInt32
操作失败,返回False
的时候应该要循环重新读取新值然后进行CAS操作,直到atomic.CompareAndSwapInt32
返回为True
。
解析中的不需要循环调用,是否描述不正确。
func reverseString(s string) (string, bool) {
str := []rune(s)
l := len(str)
if l > 5000 {
return "", false
}
for i := 0 ; i < l/2; i++ {
str[i] = str[l-i-1] - str[i]
str[l-i-1] = str[l-i-1] - str[i]
str[i] = str[i] + str[l-i-1]
}
return string(str), true
}
在没有函数调用的情况下, for true{}依然会让出执行权。
题目一
题目二
位于q007和q014的这两个题目问题是一样的,但是答案不一样,q007的答案更为准确,将题目二的代码改成这样就可以正确运行:
type Student struct {
Age int
}
func main() {
kv := map[string]*Student{"menglu": {Age: 21}}
kv["menglu"].Age = 22
s := []Student{{Age: 21}}
s[0].Age = 22
fmt.Println(kv, s)
}
我在stackoverflow上找到了和q007类似的解答:https://stackoverflow.com/a/32751792/4737579
原文:
第二个方法使用的是golang内置方法strings.Index和strings.LastIndex,用来判断指定字符串在另外一个字符串的索引未知,分别是第一次发现位置和最后发现位置
错别字:索引位置
第二题中也没有限定字符串是从键盘输入的,为什么有127的判断呢?
targetstr := "" for i := 0; i < 256; i++ { targetstr += string(i) }
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.