近期作为后台开发面试官整理的一些简单面试题(仅供参考仅供参考仅供参考
Golang中函数调用是传值还是传引用
Golang中make
和new
的区别?
以下程序输出为?
实际输出为:
因为for range
创建了迭代对象每个元素的副本,而不是直接返回每个元素的引用,如果使用该值变量的地址作为指向每个元素的指针,就会导致错误,在迭代时,返回的变量是同一个迭代过程中根据切片依次赋值的变量,所以最终map
中存储的地址都是同一个变量的地址,而其值即为最后一次迭代中赋的值
以下程序输出为?
实际输出为:
每轮循环启动一个协程,而协程启动与循环变量递增不是在同一个协程,协程启动的速度远小于循环执行的速度,所以即使是第一个协程刚起启动时,循环变量可能已经递增完毕。由于所有的协程共享循环变量i,而且这个i会在最后一个使用它的协程结束后被销毁,所以最后输出结果时i
是循环变量的末值即2
,输出的都是nums[2]
package main
import (
"fmt"
"sync"
)
func main() {
var nums = []int{1, 2, 3}
var wg sync.WaitGroup
for i := range nums {
wg.Add(1)
go func(i int) {
fmt.Print(nums[i])
wg.Done()
}(i)
}
wg.Wait()
}
以下程序的输出是?
实际上这样写会使 Go 语言的编译器报错:
因为这样的运算溢出了
var b uint64 = 10
var c int64 = -3
atomic.AddUint64(&b, uint64(c))
fmt.Println(b)
go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?
package main
import (
"fmt"
)
func main() {
fmt.Println("a return:", a())
fmt.Println("b return:", b())
}
func a() int {
var i int
defer func() {
i++
fmt.Println("defer:", i)
}()
defer func() {
i++
fmt.Println("defer:", i)
}()
return i
}
func b() (i int) {
defer func() {
i++
fmt.Println("defer:", i)
}()
defer func() {
i++
fmt.Println("defer:", i)
}()
return
}
a defer: ?
a defer: ?
a return: ?
b defer: ?
b defer: ?
b return: ?
c defer: ?
c defer: ?
c return: ?
a defer: 1
a defer: 2
a return: 0
b defer: 1
b defer: 2
b return: 2
c defer: 1
c defer: 2
c return: 2
返回值不同的原因:
Golang 中的interface
是什么?
反射是什么?Golang中的反射是基于什么实现的?
Golang Reflection 三大法则:
总而言之,Golang中的反射是基于interface
实现的,因为任意类型都实现了interface{}
类型,所以可以把任意类型的变量转换成interface{}
类型,所以反射能从interface{}
的数据结构中反射出对象,也就是利用reflect.ValueOf和reflect.TypeOf从interface反射出对象的具体信息
以下程序会输出什么?
会Panic
:
对应 Reflection 三大法则的第三条,要修改一个反射对象,它的值必须是能修改的;简单点说,就是通过反射,必须反射出它的本身,而不是它的一份拷贝
那怎么反射出它的本身,和我们函数传参的时候很像:地址;将上文的v := reflect.ValueOf(x)
改为v := reflect.ValueOf(&x).Elem()
即可,如果想要操作原变量,反射变量 Value
必须要 hold 住原变量的地址才行:
已关闭的channel再次读取会出现什么现象?
closed channel是可以被消费者继续读取的,在读完了有意义的数据之后,将读到一堆空值
如何判断一个channel
已关闭?
函数中ch是一个只读的channel,因为<-ch左侧没有接收者,所以如果channel没有关闭,case <-ch
就会被阻塞,又因为select中写了default语句,所以select会直接执行default的空分支,最终return false;如果channel关闭了,case <-ch
就不会被阻塞,直接执行该分支return true
map 循环是有序的还是无序的?
ArrayList和LinkedList的区别
HashMap 和 HashTable 区别
List接口、Set接口和Map接口的区别
Java线程间的通信方式
判断对象是垃圾 ?
如果 A 引用 B,B 又引用 A,这 2 个对象是否能被 GC 回收?
常用的 GC 算法
equals()和 ==的区别
public boolean equals(Object obj) {
//this - s1
//obj - s2
return (this == obj);
}
我们对上面的两段内容做个总结吧:
redis事务满足关系性数据库事务的特性吗
zset的实现原理(或者说数据结构)
redis中pipeline的作用
LRU 和 LFU 是?
分布式锁的基本实现以及坑(展开到进阶实现)
利用 Redis set key
的 NX 参数来保证在这个 key 不存在的情况下写入成功,并且再加上 EX 参数设置过期时间
看门狗
,在业务执行时自动延长锁的过期时间(举例子:假如加锁的时间是30秒,过10秒检查一次,一旦加锁的业务没有执行完,就会进行一次续期,把锁的过期时间再次重置成30秒)RedLock
:红锁算法认为,只要n/2+1个节点加锁成功,那么就认为获取了锁, 解锁时将所有实例解锁。 流程为:
Redis的内存碎片是什么?Redis的内存碎片清理机制是什么?
redis
自己实现的内存分配器:在redis
中新建key-value
值时,redis
需要向操作系统申请内存,一般的进程在不需要使用申请的内存后,会直接释放掉、归还内存;但redis
不一样,redis
在使用完内存后并不会直接归还内存,而是放在redis
自己实现的内存分配器中管理,这样就不需要每次都向操作系统申请内存了,实现了高性能(但这样其它应用可就不高兴了,自私的Redis)value
的更新:redis
的每个key-value
对初始化的内存大小是最适合的,当这个value
改变的并且原来内存大小不适用的时候,就需要重新分配内存了,重新分配之后,就会有一部分内存redis
无法正常回收,一直占用着TEXT
127.0.0.1:6379[6]> config set activedefrag yes
OK
TEXT
# Enabled active defragmentation
# 碎片整理总开关
# activedefrag yes
# Minimum amount of fragmentation waste to start active defrag
# 内存碎片达到多少的时候开启整理
active-defrag-ignore-bytes 100mb
# Minimum percentage of fragmentation to start active defrag
# 碎片率达到百分之多少开启整理
active-defrag-threshold-lower 10
# Maximum percentage of fragmentation at which we use maximum effort
# 碎片率小余多少百分比开启整理
active-defrag-threshold-upper 100
TEXT
127.0.0.1:6379> memory purge
OK
如果设置了的有效时间,那么它会将 cookie保存在客户端的硬盘上,下次再访问该网站的时候,浏览器先检查有没有 cookie,如果有的话,就读取该 cookie,然后发送给服务器。如果你在机器上面保存了某个论坛 cookie,有效期是一年,如果有人入侵你的机器,将你的 cookie拷走,然后放在他的浏览器的目录下面,那么他登录该网站的时候就是用你的的身份登录的。所以 cookie是可以伪造的。当然,伪造的时候需要主意,直接copy cookie文件到 cookie目录,浏览器是不认的,他有一个index.dat文件,存储了 cookie文件的建立时间,以及是否有修改,所以你必须先要有该网站的 cookie文件,并且要从保证时间上骗过浏览器 两个都可以用来存私密的东西,同样也都有有效期的说法,区别在于session是放在服务器上的,过期与否取决于服务期的设定,cookie是存在客户端的,过去与否可以在cookie生成的时候设置进去。 (1)cookie数据存放在客户的浏览器上,session数据放在服务器上 (2)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,如果主要考虑到安全应当使用session (3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE (4)单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的docker多层构建镜像的意义
最大的使用场景是将编译环境和运行环境分离
讲讲docker网络模式(bridge, host)
如何在一个自定义ip上运行docker容器?
dockerFile中最常见的指令是什么?
ADD和COPY的区别:
Docker的常用命今?
如何临时退出一个正在交互的容器的终端,而不终止它?
什么是docker-compose?
如何退出容器时候自动删除?
Tag
: 位运算 / 回溯
给你一个整数数组 nums
,数组中的元素互不相同 ,返回该数组所有可能的子集(幂集)
解集不能包含重复的子集,你可以按任意顺序返回解集
15 min
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums
中的所有元素 互不相同func subsets(nums []int) [][]int {
res := `make`([][]int, 0)
length := len(nums)
for position := 0; position < 1<<length; position++ {
subset := `make`([]int, 0)
for j:=0; j < length; j++ {
if position&(1<<j) > 0 {
subset = append(subset, nums[j])
}
}
res = append(res, subset)
}
return res
}
Tag
: 分治 / 堆
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素
20 min
func findKthLargest(nums []int, k int) int {
return quickSelect(nums, 0, len(nums)-1, k)
}
func quickSelect(nums []int, left, right, k int) int {
if index := randomPartition(nums, left, right); index == k-1 {
return nums[index]
} else if index > k-1 {
return quickSelect(nums, left, index-1, k)
} else {
return quickSelect(nums, index+1, right, k)
}
}
func randomPartition(nums []int, left, right int) int {
randomIndex := (left+right)>>1
nums[randomIndex], nums[right] = nums[right], nums[randomIndex]
pivot := nums[right]
for i := left; i < right; i++ {
if nums[i] > pivot {
nums[i], nums[left] = nums[left], nums[i]
left++
}
}
nums[left], nums[right] = nums[right], nums[left]
return left
}
给定一个单链表,只给出头指针h: 1、如何判断是否存在环? 2、如何知道环的长度?
1、对于问题1,使用追赶的方法,设定两个指针slow、fast,从头指针开始,每次分别前进1步、2步。如存在环,则两者相遇;如不存在环,fast遇到NULL退出。 2、对于问题2,记录下问题1的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。
满足:push、pop、getLast、getmax
快慢指针
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
根据题目描述,由于加上了时间复杂度必须是 O(n) ,并且空间复杂度为 O(1) 的条件,因此不能用排序方法,也不能使用 map 数据结构。
程序员小吴想了一下午没想出来,答案是使用 位操作Bit Operation 来解此题。
将所有元素做异或运算,即a[1] ⊕ a[2] ⊕ a[3] ⊕ …⊕ a[n],所得的结果就是那个只出现一次的数字,时间复杂度为O(n)。
异或运算A ⊕ B的真值表如下:
AB⊕FFFFTTTFTTTF
输入: [1,2,2,1,3,4] 输出: [3,4]
根据前面找一个不同数的思路算法,在这里把所有元素都异或,那么得到的结果就是那两个只出现一次的元素异或的结果。
然后,因为这两个只出现一次的元素一定是不相同的,所以这两个元素的二进制形式肯定至少有某一位是不同的,即一个为 0 ,另一个为 1 ,现在需要找到这一位。
根据异或的性质 任何一个数字异或它自己都等于 0
,得到这个数字二进制形式中任意一个为 1 的位都是我们要找的那一位。
再然后,以这一位是 1 还是 0 为标准,将数组的 n 个元素分成两部分。
这样就解出题目。忽略寻找不同位的过程,总共遍历数组两次,时间复杂度为O(n)。
有一种玻璃杯从一栋100层的大楼扔下,该种玻璃杯超过某一层楼会摔碎。 现在给你两个杯子,问确定最低摔碎的楼层需要摔多少次?
这道题的假设是:最低摔碎的楼层可能是每一层楼,且概率相同。我们需要找一种方法,使得定位到[1-100]之间的任意一个数都是快速的。
最简单的方法是用一个杯子从第一层开始,不断一层层的往上试。但是这样的时间复杂度是O(n)。直觉也告诉我们想放大步子扔。
因为我们有两个杯子,可以考虑成一个杯子Cup1
不断扔直到破碎,它用来确定最低摔碎的楼层在什么范围,
另一个杯子Cup2
再此基础上一层层的扔。用来准确确定最低摔碎的楼层是多少。如果凭空想象,我们可能会想到二分法,每次隔5个楼层扔,10个楼层扔…
可是我们马上也应该会想到这么分的不妥之处在于:
确定最低摔碎的楼层所需次数是不均匀分布的。
我们再来看:每次扔的楼层间隔会带来什么影响?
确定最低摔碎的楼层:
总次数 = Cup1扔的次数 + Cup2扔的次数
楼层间隔越大,Cup2需要扔的次数越多。
相同楼层间隔下:最低摔碎的楼层越高,Cup1需要扔的次数越多,Cup2需要扔的次数可认为相同。
我们的目的其实是需要尽可能保证:不管最低摔碎的楼层是第一层还是第99层,扔的总次数都尽可能一致且减少。
如果小伙伴有看我上篇文章中LSMT分层步隆过滤器的实现,有没有受到启发?
这里我们可以使Cup1需要扔的楼层间隔递减,这样可改善高楼层所需Cup1/Cup2扔的次数。
假设第一次扔的楼层间隔为X,此后依次递减1层,直到楼层间隔为2.则: x+(x-1)+(x-2)+…+2 >=100
求解出答案为14。
数据是从一个数据流中读出来的,数据的数目随着时间的变化而增加。如果用一个数据容器来保存从流中读出来的数据,当有新的数据流中读出来时,这些数据就插入到数据容器中。
如果从数据流中读出奇数个数值,那么中位数就是所有值排序之后位于中间的数值。如果数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
数组是最简单的容器。如果数组没有排序,可以用 Partition 函数找出数组中的中位数。在没有排序的数组中插入一个数字和找出中位数的时间复杂度是 O(1)和 O(n)。
我们还可以往数组里插入新数据时让数组保持排序,这是由于可能要移动 O(n)个数,因此需要 O(n)时间才能完成插入操作。在已经排好序的数组中找出中位数是一个简单的操作,只需要 O(1)时间即可完成。
排序的链表时另外一个选择。我们需要 O(n)时间才能在链表中找到合适的位置插入新的数据。如果定义两个指针指向链表的中间结点(如果链表的结点数目是奇数,那么这两个指针指向同一个结点),那么可以在 O(1)时间得出中位数。此时时间效率与及基于排序的数组的时间效率一样。
思路:
如果能够保证数据容器左边的数据都小于右边的数据,这样即使左、右两边内部的数据没有排序,也可以根据左边最大的数及右边最小的数得到中位数。如何快速从一个容器中找出最大数?用最大堆实现这个数据容器,因为位于堆顶的就是最大的数据。同样,也可以快速从最小堆中找出最小数。 因此可以用如下思路来解决这个问题:用一个最大堆实现左边的数据容器,用最小堆实现右边的数据容器。往堆中插入一个数据的时间效率是O(logn).由于只需O(1)时间就可以得到位于堆顶的数据,因此得到中位数的时间效率是O(1).
接下来考虑用最大堆和最小堆实现的一些细节。首先要保证数据平均分配到两个堆中,因此两个堆中数据的数目之差不能超过1(为了实现平均分配,可以在数据的总数目是偶数时把新数据插入到最小堆中,否则插入到最大堆中)。还要保证最大堆中里的所有数据都要小于最小堆中的数据。当数据的总数目是偶数时,按照前面分配的规则会把新的数据插入到最小堆中。如果此时新的数据比最大堆中的一些数据要小,怎么办呢?
可以先把新的数据插入到最大堆中,接着把最大堆中的最大的数字拿出来插入到最小堆中。由于最终插入到最小堆的数字是原最大堆中最大的数字,这样就保证了最小堆中的所有数字都大于最大堆中的数字。
当需要把一个数据插入到最大堆中,但这个数据小于最小堆里的一些数据时,这个情形和前面类似。
原生实现一个协程池package
,使用者应能使用该协程池package
并发完成自己输入的一系列自定义任务(要求解耦,即协程池本身和自定义任务完全无关)
输入任务列表(以下仅为伪代码,具体任务的数据结构请自行设计)
[(“fibonacci”, 6), (“padovan”, 11), (“js_error_rate”, {“pv”: 30627846, “js_error_num”: 78456})]
使用者应能基于自定义的handler
完成并发任务(上述任务分别是斐波那契数列的第n个数
、巴都万数列的第n个数
、计算js_error率
),得到以下输出:
8, 12, 0.002562