金山的面试,都是一些经典的问题

教育   2024-12-20 08:31   湖南  

欢迎点击下方👇关注我,记得星标哟~

文末会有重磅福利赠送

今天分享的是一位训练营的朋友金山的面试,业务主要为WPS销售平台的搭建,公司特点为数据横向多(一张表 几百个字段) 竖向数据没几条,面试问题涉及到的知识点挺多:项目、Go基础、Go并发、数据库、场景题以及算法题,感兴趣的朋友往下看看:

unsetunset问项目unsetunset

  1. 人员管理平台怎么使用的redis?redis缓存时间是多久?
  2. redis缓存主要寸的那些信息
  3. 采用了何种redis集群方式
  4. redis热点和大点数据了解过没
  5. 主从复制怎么评估的稳定高了50%以上?还是说是延迟卡顿减少50%以上?
  6. gtoken登录鉴权,解决一致性 你解决的哪个一致性?
  7. GC调优经验,你怎么进行调优的

unsetunsetGolang基础unsetunset

1. 一个整数int占多少位

在Go语言中,int 类型的大小是根据运行环境的架构而定的:

  • 在32位系统(如32位的Windows、Linux或macOS)上,int 类型占用32位(4个字节),因此它可以表示从 -2,147,483,648 到 2,147,483,647 的整数。
  • 在64位系统(如64位的Windows、Linux或macOS)上,int 类型占用64位(8个字节),因此它可以表示从 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 的整数。
  • 除了 int 之外,Go语言还提供了明确宽度的整数类型,比如** int8、int16、int32 和 int64,它们分别对应于8位、16位、32位和64位的有符号整数**。这些类型的大小是固定的,与运行时的系统架构无关。

此外,还有对应的无符号整数类型 uint8 (也称为 byte)、uint16、uint32 和 uint64,以及平台相关的 uint,它与 int 类型具有相同的大小。

2. 这个Int8的范围是多少?unint8范围是多少?

int8 类型可以存储从 -128 到 127 的有符号整数。这是因为 int8 类型使用了8位来存储值,其中最高位是符号位(0表示正数,1表示负数),剩下的7位用来表示数值。因此,它可以表示的最大正数是 2^7 - 1 = 127,最小的负数是 -2^7 = -128。

uint8 类型可以存储从 0 到 255 的无符号整数。uint8 也使用8位来存储值,但是它只用于表示非负数,即没有符号位。因此,它可以表示的最大值是 2^8 - 1 = 255,而最小值是 0。

3. 为什么Int8和Int32会不同

int8 和 int32 之间的差异主要体现在它们的位宽(即用来存储数值的二进制位数)和能够表示的数值范围上。这些差异是由计算机硬件和内存管理的基本原理决定的

4. GC频率是多久?多长时间计次一次

垃圾回收(GC)频率不是固定的,而是根据程序的内存分配情况动态调整的。Go使用了一个称为“目标堆大小”的概念来决定何时触发垃圾回收。这个目标堆大小是基于已分配的存活对象的总大小和一个可配置的乘数(GOGC环境变量),默认情况下,GOGC的值为100

具体来说,当已分配的存活对象的总大小增长到上一次GC后存活对象大小的GOGC%时,就会触发下一次GC。例如,如果上次GC后有10MB的存活对象,并且GOGC设置为100,那么当堆上的存活对象增长到20MB(即10MB + 10MB * 100%)时,就会触发新的GC周期。

Go的GC频率取决于应用程序的内存分配模式和GOGC的设定。对于内存分配频繁的应用程序,GC可能会更频繁地发生;而对于内存分配较少的应用程序,GC的频率则会较低。可以通过调整GOGC的值来手动控制GC的频率:

  • 降低GOGC:这会让GC更频繁地运行,减少每次GC之间的内存使用量,但也会增加CPU的使用率。
  • 提高GOGC:这会让GC不那么频繁地运行,允许更多的内存被使用,从而可能减少CPU的使用率,但也可能导致更高的内存占用。

从Go 1.8开始,Go引入了并发垃圾收集器,这大大减少了GC暂停时间(STW)。在Go 1.9中,进一步优化了GC,使得小对象的分配更加高效,并降低了GC的开销。

5. STW是为了做什么

Stop-The-World (STW) 的主要作用是确保在关键的垃圾回收步骤中,堆中的对象不会被应用程序线程修改,从而保证GC操作的准确性和一致性。Go的GC会在以下两个关键时刻触发STW

  1. 初始标记(Initial Mark)

  • 在这个非常短暂的STW阶段,所有应用程序线程都会暂停,以确保根集合(如栈、全局变量等)中的所有引用都被正确地标记为存活。这是必要的,因为根集合中的对象是垃圾回收的起点,如果这些对象在标记过程中发生变化,可能会导致一些存活对象被错误地标记为垃圾。
  • 最终清理(Final Cleanup)

    • 在垃圾回收的最后阶段,GC会再次进入STW状态,以完成最后的清理工作,包括释放已标记为垃圾的对象所占用的内存,并重置GC的状态以准备下一轮的回收。这确保了在清理过程中没有新的引用指向这些即将被回收的对象,避免了悬挂指针的问题。

    通过这两个短暂的STW阶段,Go的GC能够确保标记和清理过程的准确性,同时尽量减少对应用程序性能的影响。

    unsetunsetGolang高并发unsetunset

    1. 两个协程goroutine,同时对一个map写会发生什么?

    在Go语言中,map 不是线程安全的(或者说不是并发安全的)。如果你有两个或多个goroutine同时对同一个map 进行写操作(插入、更新或删除),而没有使用适当的同步机制来保护这些操作,可能会导致以下几种问题:

    1.竞争条件

    • 竞争条件是指两个或多个goroutine尝试同时修改同一个资源,这可能导致数据不一致。例如,一个goroutine可能正在读取map 中的某个值,而另一个goroutine在同一时间修改了这个值,导致第一个goroutine读取到的是部分更新或未定义的数据。

    2.恐慌(Panic)

    • Go的运行时会检测到对map 的并发写入,并触发一个panic。这是因为Go为了防止数据竞争,在检测到多个goroutine同时写入同一个map 时会抛出一个错误。

    3.数据丢失或覆盖

    • 如果两个goroutine几乎同时对map 进行写操作,可能会导致其中一个写操作被另一个覆盖,或者某些写操作完全丢失。这会导致数据不一致或丢失。

    3. 程序崩溃出现panic,出现panic使用recovery()还能运行吗

    可以使用recover() 函数来捕获panic,从而防止程序崩溃并允许你进行错误处理或恢复正常的程序流程。

    recover() 的工作原理

    • recover() 只能在defer 延迟调用的函数中使用,并且只能捕获当前goroutine中的panic
    • panic 发生时,Go会开始回溯调用栈,依次执行所有被defer 延迟的函数。如果在这些延迟函数中调用了recover(),它会捕获panic 并返回panic 的值,同时停止panic 的传播。
    • 如果recover() 捕获到了panic,程序可以继续执行后续的代码,而不会终止。

    recover() 的局限性

    • 仅限当前goroutinerecover() 只能捕获当前goroutine中的panic,无法捕获其他goroutine中的panic。如果你有多个goroutine,并且其中一个goroutine发生了panic,你需要在每个goroutine中分别设置defer 和recover() 来捕获可能的panic

    • 不能捕获系统级别的panic :某些由Go运行时触发的panic(如内存不足、栈溢出等)是无法通过recover() 捕获的。这些panic 通常会导致程序直接终止。

    4. defer与return的理解?看看代码了解下输出

    • 代码
    func AA() int {
        a := 1
        defer func() {
            a = 2
        }()
        return a
    }

    分析执行顺序:

    1. 初始化变量 a:
    • a := 1,此时a 的值为1
  • 设置defer 语句:
    • defer func() { a = 2 }(),这个匿名函数会被推迟到AA 函数返回之前执行。
  • 执行return a
    • return a 会将a 的当前值(即1)作为返回值,并将其存储在一个临时的返回值寄存器或栈上。注意,此时a 的值还没有被修改。
  • 执行defer 语句:
    • defer 语句中的匿名函数func() { a = 2 }() 会被执行,将a的值修改为2。但是,这并不会影响已经存储在返回值寄存器或栈上的返回值1
  • 函数返回:
    • 最终,AA 函数返回的是1,而不是2

    5. 数组和切片的底层原理,看代码讲讲输出

    • 第一段
    a := []int{0}
    b := a
    b[0] = 1
    fmt.Println(a, b)

    输出

    [1] [1]
    • 第二段
    a := []int{0}
    b := a
    a = append(a, 1)
    a = append(a, 2)
    b[0] = 1
    fmt.Println(a, b) 

    输出

    [0 1 2] [1]

    对于上面的答案,认为写错了或者有不理解的可以发评论区讨论一下。

    unsetunset数据库unsetunset

    1. mysql的主键索引和非主键索引有什么区别

    1.唯一性
    • 主键索引必须唯一且不能为空;非主键索引可以唯一或允许重复值。
    2.存储结构
    • 主键索引是聚簇索引,数据行按主键顺序物理存储;非主键索引是非聚簇索引,独立存储并包含指向数据行的指针。
    3.查询性能
    • 主键索引查询非常高效,直接定位数据行;非主键索引查询需要先查找索引再回表查找数据行,效率稍低。
    4.插入/更新性能
    • 主键索引插入和更新成本较高,可能涉及页分裂和数据行移动;非主键索引插入和更新相对简单,但更新索引列会触发索引维护。
    5.存储空间
    • 主键索引与数据行紧密相关,不额外占用太多空间;非主键索引占用额外存储空间,尤其是索引列较宽或多时。
    6.适用场景
    • 主键索引用于唯一标识每一行记录,确保数据完整性;非主键索引用于加速常见的查询条件,如WHEREJOIN 和ORDER BY

    2. mysql为什么使用B+树

    下面这些点都可以回答:

    1.高效磁盘 I/O

    • B+ 树的多层结构:B+ 树是一种平衡树,每个节点可以包含多个键值对,并且树的高度通常较低。这意味着即使在处理大量数据时,查询操作也只需要少数几次磁盘 I/O 操作即可找到目标数据。
    • 顺序访问友好:B+ 树的叶子节点是链表形式连接的,这使得范围查询(如BETWEEN>< 等)非常高效,因为可以按顺序遍历相邻的叶子节点,而不需要频繁地跳转到不同的磁盘块。

    2.支持范围查询

    • 连续的叶子节点:B+ 树的叶子节点存储实际的数据指针或数据行,并且这些叶子节点是按顺序链接的。因此,B+ 树非常适合处理范围查询,因为它可以在叶子节点之间进行高效的顺序扫描,而不需要回溯或跳跃。
    • 避免回表:对于覆盖索引(即查询的所有列都在索引中),B+ 树可以直接从叶子节点获取所需数据,而不需要回表查找实际的数据行,进一步提高了查询效率。

    3.插入和删除的高效性

    • 自平衡特性:B+ 树是一种自平衡树,插入和删除操作会自动调整树的结构,确保树始终保持平衡。这使得每次插入或删除操作的时间复杂度为 O(log n),并且不会导致树的高度显著增加。
    • 批量插入优化:B+ 树的节点可以容纳多个键值对,因此在批量插入数据时,可以一次性将多个键值对插入到同一个节点中,减少了磁盘写入的次数。

    4.内存和磁盘的高效利用

    • 高扇出(High Fan-out):B+ 树的每个节点可以包含多个键值对,这使得树的高度较低,减少了磁盘 I/O 操作的次数。同时,较高的扇出也意味着每个节点可以存储更多的数据,从而更好地利用内存和磁盘空间。
    • 缓存友好:由于 B+ 树的节点较大,一次磁盘读取可以加载多个键值对,这有助于提高缓存命中率,减少不必要的磁盘访问。

    5.并发性能

    • 局部更新:B+ 树的插入和删除操作只会影响局部节点,而不会影响整个树的结构。这使得多个并发操作可以安全地进行,而不会导致全局锁争用,从而提高了并发性能。
    • 减少锁竞争:由于 B+ 树的节点较大,多个键值对可以存储在一个节点中,减少了需要加锁的次数,进一步降低了锁竞争的可能性。

    3. 一个表 A\B两个字段,有联合索引,以下两行语句会不会用到这个联合索引

    select * from table where A=1,2,3 
    select * from table where B=1,2,3 

    在MySQL中,是否使用联合索引取决于查询的条件和索引的结构。假设你有一个表table,并且在字段A 和B 上创建了一个联合索引(复合索引),那么对于以下两条查询语句:

    -- 查询1
    SELECT * FROM table WHERE A IN (123);

    -- 查询2
    SELECT * FROM table WHERE B IN (123);

    联合索引的工作原理

    联合索引(复合索引)是按照索引列的顺序来组织数据的。例如,如果你创建了一个联合索引(A, B),那么索引会首先根据A 列进行排序,然后在同一A 值的情况下,再根据B 列进行排序。

    查询1:WHERE A IN (1, 2, 3)

    • 是否会使用联合索引
    • 原因:联合索引(A, B) 的第一个列是A,因此当查询条件中包含A 列时,MySQL 可以直接利用联合索引中的A 列来进行快速查找。即使查询只涉及A 列,联合索引仍然可以有效地加速查询,因为索引的前缀部分(即A 列)被用于过滤。

    查询2:WHERE B IN (1, 2, 3)

    • 是否会使用联合索引不一定
    • 原因:联合索引(A, B) 是按照A 列优先排序的,而B 列是次级排序列。如果查询条件只涉及B 列,MySQL 不一定能有效地利用这个联合索引。具体是否会使用联合索引取决于多个因素:
      • 索引的选择性:如果B 列的选择性较高(即B 列的值分布较为均匀),MySQL 可能会选择使用联合索引,尽管它不是最优选择。
      • 查询优化器的决策:MySQL 的查询优化器会根据统计信息(如表的大小、索引的选择性等)来决定是否使用联合索引。如果优化器认为全表扫描或其他索引更高效,它可能会选择不使用联合索引。
      • 覆盖索引:如果查询的其他列(如A 列)也在联合索引中,并且查询只需要这些列,那么联合索引可以作为覆盖索引使用,从而提高查询性能。

    4. mysql四个隔离层级

    读未提交、读已提交、可重复读和串行化

    unsetunsetGOLANG场景题unsetunset

    有一个数据库,要弃用了,但是表的数据需要迁移,且这个表随时都有人读写。如何在不停机不影响这张表读写的情况下,将他迁移到新库?

    对于这个问题,大家有什么思路,欢迎在评论区留言。如果想要知道如何回答的朋友可以私信我。

    unsetunset算法题unsetunset

    二叉树遍历

    很常见的算法题,可以尝试着写一下,说不定你就忘了呢。

    unsetunset早日上岸!unsetunset

    我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

    没准能让你能刷到自己意向公司的最新面试题呢。

    感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。

    点击下方文章,看看他们是怎么找到好工作的!

    Go就业陪跑训练营,辅导到就业为止!

    Java就业陪跑训练营,辅导到就业为止!

    我们又出成绩啦!大厂Offer集锦!遥遥领先!

    王中阳
    公司技术总监,创办就业陪跑服务,辅导学员拿到600多个offer。专注程序员的就业辅导、简历优化、模拟面试等。
     最新文章