golang面试问题原创
# Golang中有哪些方式安全读写共享变量?
mutex锁 Golang中Goroutine 可以通过 Channel 进行安全读写共享变量
有缓冲channel和无缓充channel的区别
# Golang 中的并发模型
请谈一谈 go 语言的并发机制以及它所使用的CSP并发模型
Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。
通过channel通知实现并发控制 通过sync包中的WaitGroup实现并发控制 当主 goroutine 运行到 <-ch 接受 channel 的值的时候,如果该 channel 中没有数据,就会一直阻塞等待,直到有值。这样就可以简单实现并发控制
在Go 1.7 以后引进的强大的Context上下文,实现并发控制
Go的CSP并发模型,是通过goroutine和channel来实现的。 goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。 channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。
# Go线程实现模型MPG
M指的是Machine,一个M直接关联了一个内核线程。由操作系统管理。 P指的是”processor”,代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。 G指的是Goroutine,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。 P的数量由环境变量中的GOMAXPROCS决定,通常来说它是和核心数对应,例如在4Core的服务器上回启动4个线程。G会有很多个,每个P会将Goroutine从一个就绪的队列中做Pop操作,为了减小锁的竞争,通常情况下每个P会负责一个队列。
# 什么是channel,它是线程安全的吗?
# goroutine中的调度原理
# 线程/协程/进程的区别
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。 和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。
Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。
# golang双链表的实现,怎么定义,初始化,添加,删除节点
# make和new差别
new ()是内建函数,函数原型为: func new(Type) *Type 1 make 也是内建函数,它的函数原型 比 new 多了一个(长度)参数,返回值也不同: //第一个参数是一个类型,第二个参数是长度 //返回值是一个指向该类型的一个引用 func make(Type, size IntegerType) Type make和new都是golang用来分配内存的內建函数,且在堆上分配内存,并为该变量执行了初始化操作make 用于为引用类型分配内存空间,其在分配内存的同时,也初始化了一些相关属性(这里初始化这些相关属性时不再是简单的用默认零值来初始化了!!!)。new只是分配默认零值内存并返回指向该零值内存的一个指针也就是执行了一个默认初始化,全部用默认零值来进行初始化)。 make返回的是一个引用类型;而new返回的是一个指向零值内存空间的指针。 make只能用来分配及初始化引用类型:slice,map,channel的数据; new可以分配任意类型的数据。
# nil切片和空切片的区别
nil切片和空切片指向的地址不一样。nil空切片引用数组指针地址为0(无指向任何实际地址) 空切片的引用数组指针地址是有的,且固定为一个值 能否通过for循环遍历删除元素
# 对已经关闭的的 chan 进行读写,会怎么样?为什么?
读已经关闭的 chan 能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。 如果 chan 关闭前,buffer 内有元素还未读 , 会正确读到 chan 内的值,且返回的第二个 bool 值(是否读成功)为 true。 如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个 bool 值一直为 false。 写已经关闭的 chan 会 panic
# 用过 fallthrough 关键字吗?这个关键字的作用是什么?
其他语言中,switch-case 结构中一般都需要在每个 case 分支结束处显式的调用 break 语句以防止 前一个 case 分支被贯穿后调用下一个 case 分支的逻辑,go 编译器从语法层面上消除了这种重复的工作,让开发者更轻松;但有时候我们的场景就是需要贯穿多个 case,但是编译器默认是不贯穿的,这个时候 fallthrough 就起作用了,让某个 case 分支再次贯穿到下一个 case 分支。
# restful熟悉吗?都有哪些请求方法,分别代表什么意思?
# Golang导入包时,使用’_’/’.'导入有什么区别
# Go支持类型继承吗,Go 是面向对象的语言吗
没有传统的类,也没有继承。
取而代之的是结构和组合的方式
Go 有类型和方法,并且允许面向对象的编程风格,但没有类型层次。
Go 中的 "接口 "概念提供了一种不同的方法,我们认为这种方法易于使用,而且在某些方面更加通用。还有一些方法可以将类型嵌入到其他类型中,以提供类似的东西,但不等同于子类。
Go 中的方法比 C++ 或 Java 中的方法更通用:它们可以为任何类型的数据定义,甚至是内置类型,如普通的、"未装箱的 "整数。它们并不局限于结构(类)。
Go 由于缺乏类型层次,Go 中的 "对象 "比 C++ 或 Java 等语言更轻巧。
# 如何判断链表有环
不允许修改链表结构。 时间复杂度O(n),空间复杂度O(1)。
https://blog.csdn.net/u010983881/article/details/78896293 快慢指针,一个跳1,一个跳2
方法一、穷举遍历 方法一:首先从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就从头节点重新遍历新节点之前的所有节点,用新节点ID和此节点之前所有节点ID依次作比较。如果发现新节点之前的所有节点当中存在相同节点ID,则说明该节点被遍历过两次,链表有环;如果之前的所有节点当中不存在相同的节点,就继续遍历下一个新节点,继续重复刚才的操作。 算法的时间复杂度是0+1+2+3+…+(D+S-1) = (D+S-1)(D+S)/2 , 可以简单地理解成 O(NN)。而此算法没有创建额外存储空间,空间复杂度可以简单地理解成为O(1) 方法二、哈希表缓存 首先创建一个以节点ID为键的HashSet集合,用来存储曾经遍历过的节点。然后同样是从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就用新节点和HashSet集合当中存储的节点作比较,如果发现HashSet当中存在相同节点ID,则说明链表有环,如果HashSet当中不存在相同的节点ID,就把这个新节点ID存入HashSet,之后进入下一节点,继续重复刚才的操作。
方法三、快慢指针 首先创建两个指针1和2(在java里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。 方法四、Set集合大小变化 把节点放入set里,每次访问下个节点时,如果set长度不变,则跳出,说明有环。否则set长度+1,继续遍历。 该方法时间复杂度是O(N),空间复杂度上因为需要额外等数量的存储空间,所以空间复杂度是O(n)。 如何判断两个单链表是否相交,以及相交点 方法一、直接法 直接判断第一个链表的每个结点是否在第二个链表中,时间复杂度为O(len1*len2),耗时很大