首页 > IT职业 > IT职场 > 理想汽车的Java 后端面经来了
来源:小林coding
一线城市由于限车油牌,很多我身边的朋友,买的第一辆车都是电车,基本上人均特斯拉。国内也有很多优秀的新能源车企,理想、小鹏、比亚迪、蔚来等等,最近小米也入局这个赛道。
这些新能源车企中,发现理想的校招薪资特别高,不少拿到理想汽车开发岗 offer 的同学跟我反馈开到了 30-35k x 14(总包 40-50w),比互联网大厂都多很多,问我要不要接?

算法岗更是离谱,都接近 40k 了(总包 55w+)。


看了下理想的财报才知道,原来理想汽车 2023 的销售非常好,所以在开发岗位上扩招了很多,同时给校招生开的薪资非常的给力。
话说回来,理想开这么高薪资,面试难度如何呢?
之前分享了很多互联网公司后端面经,这次给大家分享一位同学面试理想汽车的Java 后端面经,这个面经还是比较经典,基本后端的知识都问了遍。
我也把问到的知识点,罗列了一下
对索引使用左或者左右模糊匹配,会索引失效
当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx% 这两种方式都会造成索引失效。
比如下面的 like 语句,查询 name 后缀为「林」的用户,执行计划中的 type=ALL 就代表了全表扫描,而没有走索引。
// name 字段为二级索引
select * from t_user where name like '%林';

对索引使用函数,会索引失效
有时候我们会用一些 MySQL 自带的函数来得到我们想要的结果,这时候要注意了,如果查询条件中对索引字段使用函数,就会导致索引失效。
比如下面这条语句查询条件中对 name 字段使用了 LENGTH 函数,执行计划中的 type=ALL,代表了全表扫描:
// name 为二级索引
select * from t_user where length(name)=6;

对索引进行表达式计算,会索引失效
在查询条件中对索引进行表达式计算,也是无法走索引的。
比如,下面这条查询语句,执行计划中 type = ALL,说明是通过全表扫描的方式查询数据的:
explain select * from t_user where id + 1 = 10;

对索引隐式类型转换,会索引失效
如果索引字段是字符串类型,但是在条件查询中,输入的参数是整型的话,你会在执行计划的结果发现这条语句会走全表扫描。
我在原本的 t_user 表增加了 phone 字段,是二级索引且类型是 varchar。

然后我在条件查询中,用整型作为输入参数,此时执行计划中 type = ALL,所以是通过全表扫描来查询数据的。
select * from t_user where phone = 1300000001;

这是因为 phone 字段为字符串,所以 MySQL 要会自动把字符串转为数字,所以这条语句相当于:
select * from t_user where CAST(phone AS signed int) = 1300000001;
可以看到,CAST 函数是作用在了 phone 字段,而 phone 字段是索引,也就是对索引使用了函数!而前面我们也说了,对索引使用函数是会导致索引失效的
联合索引非最左匹配,会索引失效
联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配。
比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:
需要注意的是,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。
但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效:



缓存雪崩解决方案:
缓存击穿解决方案:
缓存穿透解决方案:
布隆过滤器由「初始值都为 0 的位图数组」和「 N 个哈希函数」两部分组成。当我们在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
布隆过滤器会通过 3 个操作完成标记:
举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。

在数据库写入数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第 1、4、6 位置的值设置为 1。当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。
布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 1、4、6 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。
所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
共享内存:线程可以通过访问共享的内存区域来进行数据交换和共享。Linux提供了共享内存的机制,可以使用shmget()、shmat()等函数进行共享内存的创建和映射。
信号量:线程可以使用信号量来进行同步和互斥操作。Linux提供了信号量机制,可以使用sem_init()、sem_wait()、sem_post()等函数来操作信号量。
互斥锁:线程可以使用互斥锁来实现对共享资源的互斥访问。Linux提供了互斥锁机制,可以使用pthread_mutex_init()、pthread_mutex_lock()、pthread_mutex_unlock()等函数来操作互斥锁。
条件变量:线程可以使用条件变量来等待和通知特定的条件。Linux提供了条件变量机制,可以使用pthread_cond_init()、pthread_cond_wait()、pthread_cond_signal()等函数来操作条件变量。
管道:线程可以使用管道进行简单的数据传输。Linux提供了管道机制,可以使用pipe()函数来创建管道,并使用read()和write()函数进行数据的读写。

socket,得到文件描述符;bind,将 socket 绑定在指定的 IP 地址和端口;listen,进行监听;accept,等待客户端连接;connect,向服务端的地址和端口发起连接请求;accept 返回用于传输的 socket 的文件描述符;write 写入数据;服务端调用 read 读取数据;close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。
所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。
成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。
应用层DNS解析,传输层TCP连接,网络层IP,数据链路MAC,真实物理层,接收到之后再一层层扒皮。

更详细传输层->网络层->数据链路层->路由器的过程,看图解网络->基础篇->键入网址到网页显示期间发生了什么?。

当线程池的任务队列满了之后,线程池会执行指定的拒绝策略来应对,常用的四种拒绝策略包括:CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy,此外,还可以通过实现RejectedExecutionHandler接口来自定义拒绝策略。
四种预置的拒绝策略:
GC 是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃。
Java 虚拟机提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。Java 程序员不用担心内存管理, 因为垃圾收集器会自动进行管理。
G1(Garbage First) 垃圾收集器,是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方推荐选择使用 G1 来替代 CMS 。
G1最大的特点是引入分区的思路,弱化了分代的概念。合理利用垃圾收集各个周期的资源,解决了其他收集器、甚至 CMS 的众多缺陷。
G1 相比 CMS的改进主要是这几个方面:
G1 收集器的主要应用在多 CPU 大内存的服务中,在满足高吞吐量的同时,尽可能的满足垃圾回收时的暂停时间。在以下场景中,G1 更适合:
volatile关键字保证了两个性质:
对于JVM的内存屏障实现中,也采取了内存屏障。JVM的内存屏障有四种,我们来看一下这四种屏障和他们的作用:
LoadLoad屏障:对于这样的语句
第一大段读数据指令;
LoadLoad;
第二大段读数据指令;
LoadLoad指令作用:在第二大段读数据指令被访问前,保证第一大段读数据指令执行完毕
StoreStore屏障:对于这样的语句
第一大段写数据指令;
StoreStore;
第二大段写数据指令;
StoreStore指令作用:在第二大段写数据指令被访问前,保证第一大段写数据指令执行完毕
LoadStore屏障:对于这样的语句
第一大段读数据指令;
LoadStore;
第二大段写数据指令;
LoadStore指令作用:在第二大段写数据指令被访问前,保证第一大段读数据指令执行完毕。
StoreLoad屏障:对于这样的语句
第一大段写数据指令;
StoreLoad;
第二大段读数据指令;
StoreLoad指令作用:在第二大段读数据指令被访问前,保证第一大段写数据指令执行完毕。
针对volatile变量,JVM采用的内存屏障是:
通过这种方式,就可以保证被volatile修饰的变量具有线程间的可见性和禁止指令重排序的功能了。
主要区别有以下 5 个:
Kafka特点如下:
topic下的一个分区只能被同一个consumer group下的一个consumer线程来消费,但反之并不成立,即一个consumer线程可以消费多个分区的数据,比如Kafka提供的ConsoleConsumer,默认就只是一个线程来消费所有分区的数据。

所以,分区数决定了同组消费者个数的上限。
如果你的分区数是N,那么最好线程数也保持为N,这样通常能够达到最大的吞吐量。超过N的配置只是浪费系统资源,因为多出的线程不会被分配到任何分区。
Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。
可以把Spring AOP看作是对Spring的补充,它使得Spring不需要EJB就能提供声明式事务管理;或者 使用Spring AOP框架的全部功能来实现自定义的方面。
AOP概念:
IsModified接口,来简化缓存。AOP 与 OOP 不是相互对立的关系,可以把 AOP 看作是弥补 OOP 的不足,以此之长、补彼之短,两者结合使用效果最佳。
OOP 是针对业务实体及其属性 和行为 进行抽象封装 ,这个不难理解,例如:用户模块、订单模块 等。AOP 是针对业务切面进行提取,它所面对的是处理过程中的某个 步骤 或 阶段 ,以达到逻辑处理过程中各部分之间低耦合性的 隔离效果 ,例如:日志记录、权限验证 等。举个例子,如果单纯使用 OOP ,需要在日志模块、订单模块中进行权限验证、日志记录怎么办?难道要在每个方法前都加入权限验证、日志记录的代码吗?那么如果需要在每个方法前和方法后都记录日志怎么办?
这时如果使用 AOP,就可以借助代理完成这些重复的操作,就可以不在每个方法前加入权限验证、日志记录的代码,降低各部分之间的耦合。
Spring AOP的底层实现原理主要依赖于动态代理。在Spring AOP中,通过动态代理技术,可以在运行时动态地创建一个代理对象,将切面逻辑织入到目标对象的方法调用中。
Spring AOP主要有两种类型的代理:基于接口的代理和基于类的代理。
对于基于接口的代理,Spring AOP使用JDK动态代理来实现。JDK动态代理要求目标对象实现一个或多个接口,然后通过Proxy类的静态方法创建一个代理对象。代理对象实现了目标对象的接口,并且在方法调用前后添加了切面逻辑。
对于基于类的代理,Spring AOP使用CGLIB(Code Generation Library)来实现。CGLIB是一个强大的代码生成库,它通过继承的方式创建一个目标对象的子类,并在子类中重写目标对象的方法,从而实现切面逻辑的织入。
在运行时,当客户端调用目标对象的方法时,实际上是调用了代理对象的方法。代理对象会在方法调用前后执行切面逻辑,并最终将方法调用委托给目标对象。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70024922/viewspace-3004257/ ,如需转载,请注明出处,否则将追究法律责任。