深信服后端开发岗校招面经,挂在了二面!

深信服今年是真能给啊!星球一位球友拿到了深信服后端开发 ssp,base 北京,薪资 28k*12 ,年终 0-6 个月,签字费给到了 30w

下面,给大家分享一位读者的深信服武汉 Java 岗位的面经,大家感受一下难度。

面试情况

武汉的深信服 Java 岗位,八股和项目考察整体难度一般,就是卡在了算法题上,最终这位读者倒在了二面。

个人情况

  • 本科二本,硕士 211,25 届
  • 有一段武汉某中厂的实习经历
  • 做了一个单体系统和一个轮子项目

面试题

八股问题不多,Java 问的很少,问了下项目和场景题,然后就是手撕算法。

我对一二面提到的面试问题添加了参加答案供大家复习参考。

1、自我介绍

一个好的自我介绍应该包含这几点要素:

  1. 用简单的话说清楚自己主要的技术栈于擅长的领域,例如 Java 后端开发、分布式系统开发;
  2. 把重点放在自己的优势上,重点突出自己的能力比如自己的定位的 bug 的能力特别厉害;
  3. 避免避实就虚,适当举例体现自己的能力,例如过往的比赛经历、实习经历;
  4. 自我介绍的时间不宜过长,一般是 1~2 分钟之间。

2、介绍自己的实习经历,做了什么,学到了什么

如果你有实习经历的话,自我介绍之后,第二个问题一般就是聊你的实习经历。面试之前,一定要提前准备好对应的话术,突出介绍自己实习期间的贡献。

很多同学实习期间可能接触不到什么实际的开发任务,大部分时间可能都是在熟悉和维护项目。对于这种情况,你可以适当润色这段实习经历,找一些简单的功能研究透,包装成自己参与做的,大部分同学都是这么做的。不用担心面试的时候会露馅,只要不挑选那种明显不会交给实习生做的任务,你自己也能讲明白就行了。不过,还是更建议你在实习期间尽量尝试主动去承担一些开发任务,这样整个实习经历对个人提升也会更大一些。

3、1G 大小的文件中出现频率最高的 100 个词

下面是星球一位球友提供的答案,分享在星球里的这篇帖子里:https://t.zsxq.com/HtRQ1

这个问题可以通过多路归并排序方法解决。

步骤 1:多路归并排序对大文件进行排序

多路归并排序对大文件排序的步骤如下:

  1. 将 1GB 的文件按顺序切分成多个小文件,每个文件大小不超过 2MB,总共 500 个小文件。这样可以确保每个小文件在后续处理时能够被完全加载到内存中,满足 10MB 的内存限制。
  2. 使用 10MB 的内存分别对每个小文件中的单词进行排序。这样可以确保每个小文件内部是有序的,这为后续的多路归并排序打下基础。
  3. 使用一个大小为 500 的最小堆,将所有 500 个已排序的小文件进行合并,生成一个完全有序的文件。

其中第 3 步,对 500 个小文件进行多路排序的思路如下:

  1. 初始化一个最小堆,大小就是有序小文件的个数 500。堆中的每个节点存放每个有序小文件对应的输入流。
  2. 按照每个有序文件中的下一行数据对所有文件输入流进行排序,单词小的输入文件流放在堆顶。
  3. 拿出堆顶的输入流,并且将下一行数据写入到最终排序的文件中,如果拿出来的输入流还有数据的话,那么就将这个输入流再次添加到栈中。否则说明该文件输入流中没有数据了,那么可以关闭这个流。
  4. 循环这个过程,直到所有文件输入流中没有数据为止。

步骤 2:统计出现频率最高的 100 个词

  1. 初始化一个 100 个节点的小顶堆,用于保存 100 个出现频率最高的单词。
  2. 遍历整个文件,一个单词一个单词地从文件中读取出来,并且进行计数。
  3. 等到遍历的单词和上一个单词不同的话,那么上一个单词及其频率如果大于堆顶的词的频率,那么放在堆中。否则不放

归并排序涉及大量的磁盘读写操作,可能会成为性能瓶颈。下面是一些优化建议:

  • 缓冲区大小:合理设置缓冲区大小,减少磁盘 I/O 次数。
  • 使用更高效的存储介质:如 SSD 以提高读写速度。
  • 并行归并:在硬件支持的情况下,进行多线程或多进程的并行归并,提升整体速度。

4、给你一个数字如何知道其是否在 10 亿不重复数字中

对于这种大数据量去重/判重的场景,我们可以考虑使用 位图(Bitmap)。位图可以在不占用太多内存的前提下,解决海量数据的存在性问题,进而实现去重/判重。

什么是 Bitmap? Bitmap 是一种用于存储二进制数据的数据结构。简单来说,Bitmap 就是使用二进制位来表示某个元素是否存在的数组。每一位只有两种状态,可以方便地用 0 和 1 来表示存在与不存在。

使用 Bitmap 的话,一个数字只需要占用 1 个 bit。

我们假设数字为 QQ 号。 QQ 号是 4 字节无符号整数,共 32bit, 也就是说,QQ 号的取值范围是:[0, 2^32 - 1]。2^32 - 1 的值是 4294967295, 是一个 10 位的整数,大约是 43 亿。

这样的话,大约需要 512MB 内存就可以表示所有的 QQ 号了,计算过程:4294967295 / 8 / 1024 / 1024 ≈ 512MB。

假设我们要把 QQ 号 1384593330 放入 Bitmap,我们只需要将 1384593330 位置的数组元素设置为 1 即可。当我们要判断对应的 QQ 号是否已经存在于 Bitmap 中时,只需要查看对应位置的数组元素是否为 1 即可。

下面是使用 Bitmap 命令完成 QQ 号判重的简单演示:

bash 复制代码
> SETBIT mykey 1384593330 1
0
> GETBIT mykey 1384593330
1
> SETBIT mykey 1384593331 1
0
> GETBIT mykey 1384593331

如果我们想要进一步节省空间,并且容许较小的误差的话,还可以使用 布隆过滤器(Bloom Filter) 进一步优化。布隆过滤器就是基于 Bitmap 实现的,只是多加了哈希函数映射这一步。

更多高频场景题,可以参考《后端面试高频系统设计&场景题》(20+高频系统设计&场景面试题)。

5、线程间的同步的方式

线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。

下面是几种常见的线程同步的方式:

  1. 互斥锁(Mutex) :采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
  2. 读写锁(Read-Write Lock) :允许多个线程同时读取共享资源,但只有一个线程可以对共享资源进行写操作。
  3. 信号量(Semaphore) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
  4. 屏障(Barrier) :屏障是一种同步原语,用于等待多个线程到达某个点再一起继续执行。当一个线程到达屏障时,它会停止执行并等待其他线程到达屏障,直到所有线程都到达屏障后,它们才会一起继续执行。比如 Java 中的 CyclicBarrier 是这种机制。
  5. 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。

6、进程间的通信方式

下面这部分总结参考了:https://www.jianshu.com/p/c1015f5ffa74 这篇文章,推荐阅读,总结的非常不错。

  1. 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
  2. 有名管道(Named Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循 先进先出(First In First Out) 。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
  3. 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
  4. 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。
  5. 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
  6. 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
  7. 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

操作系统面试题总结:

7、Java 反射介绍,优缺点,用过吗

如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

反射可以让我们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。

不过,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。但是!这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。

比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。

java 复制代码
public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}

另外,像 Java 中的一大利器 注解 的实现也用到了反射。

为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。

Java 基础面试题总结:

8、为什么用 Redis 而不用本地缓存呢?

特性 本地缓存 Redis
数据一致性 多服务器部署时存在数据不一致问题 数据一致
内存限制 受限于单台服务器内存 独立部署,内存空间更大
数据丢失风险 服务器宕机数据丢失 可持久化,数据不易丢失
管理维护 分散,管理不便 集中管理,提供丰富的管理工具
功能丰富性 功能有限,通常只提供简单的键值对存储 功能丰富,支持多种数据结构和功能

9、项目用 Redis 缓存了什么数据?

Redis 除了可以用来缓存高频访问的数据之外,还以用来实现:

  • 分布式锁 :通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:如何基于 Redis 实现分布式锁?
  • 限流 :一般是通过 Redis + Lua 脚本的方式来实现限流。如果不想自己写 Lua 脚本的话,也可以直接利用 Redisson 中的 RRateLimiter 来实现分布式限流,其底层实现就是基于 Lua 代码+令牌桶算法。
  • 消息队列:Redis 自带的 List 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
  • 延时队列:Redisson 内置了延时队列(基于 Sorted Set 实现的)。
  • 分布式 Session :利用 String 或者 Hash 数据类型保存 Session 数据,所有的服务器都可以访问。
  • 复杂业务场景:通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜。

面试中,根据你项目的实际情况去回答即可!

Redis 面试题总结:

10、手撕算法

  • 一面手撕字符串转数字
  • 二面翻转链表
相关推荐
技术路上的苦行僧2 小时前
分布式专题(8)之MongoDB存储原理&多文档事务详解
数据库·分布式·mongodb
龙哥·三年风水3 小时前
workman服务端开发模式-应用开发-后端api推送修改二
分布式·gateway·php
小小工匠3 小时前
分布式协同 - 分布式事务_2PC & 3PC解决方案
分布式·分布式事务·2pc·3pc
闯闯的日常分享5 小时前
分布式锁的原理分析
分布式
sc写算法6 小时前
Hash 映射
数据结构·算法·哈希算法
太阳伞下的阿呆6 小时前
kafka常用命令(持续更新)
分布式·kafka
Java程序之猿7 小时前
微服务分布式(二、注册中心Consul)
分布式·微服务·consul
龙哥·三年风水7 小时前
workman服务端开发模式-应用开发-后端api推送修改一
分布式·gateway·php
power-辰南8 小时前
Zookeeper 底层原理解析
分布式·zookeeper·云原生
power-辰南8 小时前
Zookeeper常见面试题解析
分布式·zookeeper·云原生