图解学习网站:https://xiaolincoding.com
大家好,我是小林。
雷总的小米 su7 ultra 一发布,直接卖爆了,2 小时就完成了全年的销售目标,50 多万的价格就有 1500+马力,这马力可是千万级跑车的配置了,1.98 秒破百,谁看了不心动,要不是狠自己钱包太小,我都想下手了。。
于是我就好奇,小米汽车的薪资待遇如何?
正好,我在「OfferShow」那里看到了「小米汽车-校招薪资爆料」汇总数据,分享给大家看看。
| 城市 | 岗位 | 校招薪资爆料 |
| 北京 | 自动驾驶 | 39k*15 |
| 北京 | 软开 | 24k*15 |
| 北京 | 测试 | 23k*15 |
| 北京 | 软开 | 22k*15 |
| 上海 | 电池系统测试 | 18k*15 |
| 北京 | 服务端开发工程师 | 22*15+0.8*12 |
| 南京 | 机械岗 | 30w |
| 上海 | 动力集成测试 | 23k*15 |
| 上海 | 算法 | 23k*15 |
| 北京 | 产品经理 | 16k*15 |
| 南京 | 热管理 | 18.5k*16 |
关注我的同学们大部分都是程序员,我们就专注看软开的校招薪资,小米年终奖是 3 个月,所以是 15 薪资计算:
- 22k x 15 = 33w,北京24k x 15 = 36w,北京
软开的年包在 30-35w,是属于大厂范畴的薪资了,虽然和互联网一线大厂会有一点点差距,但是整体还是不错的。
那小米汽车软开岗的面试难得如何呢?
一起瞧瞧小米汽车Java软开岗面经,这次是二面的面经了,问了Spring、MySQL、设计模式、网络协议、分布式事务这些内容,最后还是一样有一个算法题。
小米汽车二面
SpringBoot的启动流程介绍一下
SpringBoot是一个服务Spring框架的框架,能够简化配置文件,快速构建web应用,内置tomcat,无需打包部署,直接运行。 当我们启动一个SpringBoot应用的时候,都会用到如下的启动类:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
只要加上@SpringBootApplication,然后执行run()方法,就可以启动一个应用程序,启动的流程如下:
- 首先从main找到run()方法,在执行run()方法之前new一个SpringApplication对象进入run()方法,创建应用监听器SpringApplicationRunListeners开始监听然后加载SpringBoot配置环境(ConfigurableEnvironment),然后把配置环境(Environment)加入监听对象中然后加载应用上下文(ConfigurableApplicationContext),当做run方法的返回对象最后创建Spring容器,refreshContext(context),实现starter自动化配置和bean的实例化等工作。
Spring IOC、 AOP解决了什么问题?没有IOC之前怎么做?
Spring IOC 解决的问题,主要是解耦对象之间的依赖关系。在传统的编程中,对象之间的依赖关系通常是在代码中硬编码实现的,这使得代码的可维护性和可扩展性变差。IOC 通过将对象的创建和依赖关系的管理交给 Spring 容器,实现了对象之间的解耦。比如,一个业务逻辑类可能依赖于多个数据访问类,使用 IOC 后,业务逻辑类不需要知道具体的数据访问类是如何创建和获取的,只需要声明它的依赖关系,由 Spring 容器来负责注入。
也可以提高提高代码的可维护性和可测试性。当依赖关系被集中管理在 Spring 容器中时,如果需要更换某个依赖对象,只需要在配置文件或注解中进行修改,而不需要在大量的业务代码中进行查找和修改。在进行单元测试时,也可以很方便地通过 IOC 容器注入模拟的依赖对象,提高了测试的灵活性和可操作性。
在没有 IOC 框架之前,开发人员通常需要在代码中手动创建对象及其依赖对象。例如,在一个 Java 项目中,如果一个 UserService 类依赖于 UserDao 类,那么在 UserService 类中需要通过 new 关键字来创建 UserDao 的实例,如下所示:
public class UserService {
private UserDao userDao = new UserDao();
public void doSomething() {
// 使用userDao进行业务操作
userDao.saveUser();
}
}
这样的代码存在着严重的耦合问题,如果需要更换 UserDao 的实现类或者对其进行一些初始化配置,就需要在 UserService 类中进行修改,随着项目规模的增大,维护成本会越来越高。
Spring AOP 解决的问题,主要是可以分离业务逻辑和横切关注点,提高代码的复用性。在企业级应用开发中,往往存在一些横切关注点,如日志记录、事务管理、权限控制等,这些功能会分散在各个业务逻辑代码中,导致代码的可读性和可维护性变差。AOP 允许将这些横切关注点从业务逻辑中分离出来,以切面的形式进行统一管理和实现。通过 AOP,可以将通用的横切逻辑封装在切面中,然后在多个业务逻辑中进行复用。例如,对于日志记录功能,可以创建一个日志切面,在多个不同的业务方法上都可以应用这个切面来记录日志,而不需要在每个业务方法中都编写重复的日志记录代码。
在没有 AOP 之前,要实现日志记录、事务管理等横切功能,通常需要在每个需要这些功能的方法中手动编写相关代码。以日志记录为例,可能需要在每个业务方法的开始和结束位置都添加日志记录代码,如下所示:
public class UserService {
public void doSomething() {
// 记录方法开始日志
System.out.println("进入doSomething方法");
// 业务逻辑代码
// 记录方法结束日志
System.out.println("离开doSomething方法");
}
}
这样不仅会导致代码大量重复,而且当需要修改日志记录的格式或策略时,需要在多个地方进行修改,容易出现遗漏和不一致的情况。
自己项目中有用到AOP吗?
有,主要用在了日志记录功能,避免在每个业务方法里重复编写日志记录代码。
看你也会golang,golang和java的区别?
Golang是由Google设计的静态类型、编译型语言,注重简洁和高效并发。而Java是一种面向对象的语言,拥有成熟的生态系统和跨平台能力,依赖JVM运行。
在语法方面,Golang更简洁,没有类和继承,使用接口和结构体,而Java是严格的面向对象,需要更多的样板代码。并发模型方面,Golang有goroutine和channel,轻量级且易于使用;Java使用线程和线程池,相对重量级,但通过并发包提供丰富的工具。
性能方面,Golang作为编译型语言,通常启动更快,内存占用更低;Java虽然JIT优化后性能强劲,但启动和内存开销较大。
内存管理方面,两者都有垃圾回收,但Golang的GC更注重低延迟,Java的GC调优更复杂但灵活。 错误处理机制也不同,Golang采用显式错误返回,而Java使用异常机制。这可能影响代码的可读性和错误处理流程。
生态系统方面,Java有大量成熟的框架和库,适合企业级应用;Golang的生态在云原生和微服务领域发展迅速,但相对年轻。
应用场景方面,Golang适合高并发、分布式系统,云基础设施,而Java在企业应用、Android开发、大数据处理等方面占优。
MySQL建表的时候有哪些优化手段?
合理选择数据类型:根据实际存储的数据范围选择合适的数值类型,避免使用过大的数据类型造成空间浪费。例如,如果存储的整数范围在 0 - 255 之间,使用 TINYINT 即可,而不是 INT。对于固定长度的字符串,使用 CHAR 类型;对于可变长度的字符串,使用 VARCHAR 类型。同时,根据实际存储的字符串长度合理设置字段长度。
控制字段数量:避免创建过多不必要的字段,过多的字段会增加表的复杂度和存储开销,同时也会影响查询性能。可以将一些不常用的字段单独存储在其他表中,通过关联查询获取数据。
反范式化设计:可以适当引入一些数据冗余,将相关联的数据存储在同一个表中,减少表之间的关联查询。需要在范式化和反范式化之间找到一个平衡点。
合理创建索引:在经常用于 WHERE 子句、JOIN 子句和 ORDER BY 子句的字段上创建索引,以提高查询效率。避免在重复值较多的字段上创建索引,因为这样的索引效果不佳。例如,在一个性别字段上创建索引可能没有太大意义。当然,过多的索引会增加存储开销和写操作的性能开销,因此要根据实际查询需求合理创建索引,避免创建过多不必要的索引。
MySQL索引怎么建?
主键索引是一种特殊的唯一索引,它不允许有空值。通常在创建表时指定主键,MySQL 会自动创建主键索引。创建方式:
-- 在创建表时创建主键索引
CREATE TABLE table_name (
column1 datatype PRIMARY KEY,
column2 datatype,
...
);
-- 使用 ALTER TABLE 语句创建主键索引
ALTER TABLE table_name ADD PRIMARY KEY (column1, column2, ...);
创建普通索引的方式:
-- 在创建表时创建普通索引
CREATE TABLE table_name (
column1 datatype,
column2 datatype,
...
INDEX index_name (column1, column2, ...)
);
-- 在已存在的表上创建普通索引
CREATE INDEX index_name ON table_name (column1, column2, ...);
-- 使用 ALTER TABLE 语句创建普通索引
ALTER TABLE table_name ADD INDEX index_name (column1, column2, ...);
创建唯一索引的方式,唯一索引要求索引列的值必须唯一,但允许有空值。
-- 在创建表时创建唯一索引
CREATE TABLE table_name (
column1 datatype,
column2 datatype,
...
UNIQUE INDEX index_name (column1, column2, ...)
);
-- 在已存在的表上创建唯一索引
CREATE UNIQUE INDEX index_name ON table_name (column1, column2, ...);
-- 使用 ALTER TABLE 语句创建唯一索引
ALTER TABLE table_name ADD UNIQUE INDEX index_name (column1, column2, ...);
MySQL中的锁机制你知道哪些?
在 MySQL 里,根据加锁的范围,可以分为全局锁、表级锁和行锁三类。
| 锁类型 | 加锁范围 | 加锁语句 | 具体说明 |
|---|---|---|---|
| 全局锁 | 整个数据库 | flush tables with read lock |
执行该语句后数据库处于只读状态,其他线程的增删改或表结构修改操作都会阻塞 |
| 表级锁 | 表 | lock tables |
对表加表锁,会限制别的线程的读写,也会限制本线程接下来的读写操作 |
| 表级锁(元数据锁) | 表 | 自动加锁 | 对表进行 CRUD 操作时加 MDL 读锁;对表做结构变更操作时加 MDL 写锁 |
| 表级锁(意向锁) | 表 | 执行插入、更新、删除操作时自动加锁 | 执行插入、更新、删除操作时,先对表加上「意向独占锁」,然后对该记录加独占锁 |
| 行级锁(记录锁) | 表中的一条记录 | InnoDB 引擎自动加锁 | 有 S 锁(共享锁)和 X 锁(排他锁)之分,满足读写互斥,写写互斥 |
| 行级锁(间隙锁) | 表中的记录间隙 | InnoDB 引擎自动加锁,只存在于可重复读隔离级别 | 用于解决可重复读隔离级别下幻读的现象 |
| 行级锁(Next-Key Lock) | 表中的一个范围及记录本身 | InnoDB 引擎自动加锁 | 是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身 |
全局锁:通过flush tables with read lock 语句会将整个数据库就处于只读状态了,这时其他线程执行以下操作,增删改或者表结构修改都会阻塞。全局锁主要应用于做
全库逻辑备份,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。
表级锁:MySQL 里面表级别的锁有这几种:
表锁:通过lock tables 语句可以对表加表锁,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。元数据锁:当我们对数据库表进行操作时,会自动给这个表加上 MDL,对一张表进行 CRUD 操作时,加的是 MDL 读锁;对一张表做结构变更操作的时候,加的是 MDL 写锁;MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。意向锁:当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。
意向锁的目的是为了快速判断表里是否有记录被加锁。
行级锁:InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。
-
- 记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的,满足读写互斥,写写互斥间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。
最熟悉的设计模式有哪些?
单例模式、工厂模式、策略模式、装饰器模式这些。
- 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。工厂模式:定义一个创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。该模式使得算法可以独立于使用它的客户而变化。装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
代理模式讲一下
代理模式为其他对象提供一种代理以控制对这个对象的访问。
在代理模式中,代理对象与目标对象实现相同的接口,客户端对目标对象的访问实际上是通过代理对象来进行的,代理对象可以在调用目标对象的方法前后进行一些额外的操作,如权限验证、缓存处理、日志记录等。
代理模式实现有这些:
-
- 静态代理:手动编写代理类,需实现与目标对象相同的接口。优点是简单直观,缺点是每个目标类需对应一个代理类,代码冗余。动态代码:运行时动态生成代理类,无需手动编写。JDK动态代理是基于接口,使用
java.lang.reflect.Proxy
- ,CGLIB动态代理:基于继承,可代理无接口的类。
聊聊网络,交换机和路由器的区别?
交换机:主要工作在数据链路层。它通过学习连接到其端口的设备的 MAC 地址,构建 MAC 地址表。当接收到数据帧时,根据数据帧中的目的 MAC 地址在 MAC 地址表中查找对应的端口,然后将数据帧从该端口转发出去,实现本地网络内设备之间的数据交换。
路由器:主要工作在网络层。它依据 IP 地址进行数据转发,路由器中保存着路由表,路由表包含了网络地址与下一跳地址等信息。当路由器接收到数据包时,会根据数据包中的目的 IP 地址,在路由表中查找最佳路径,然后将数据包沿着该路径转发到下一个路由器或目标设备。
现在我们视频聊天,涉及到的网络协议是什么?
对于实时性要求极高的视频和音频数据,会优先使用 UDP 进行传输,并通过一些额外的技术手段来弥补 UDP 的不可靠性;而对于控制信息和一些对可靠性要求较高的数据,则会使用 TCP 来传输,以确保视频聊天的稳定性和正确性。
分布式事务的解决方案你知道哪些?
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致性 | 低 | 中 | 传统数据库、XA协议 |
| 3PC | 强一致性 | 中低 | 高 | 需减少阻塞的强一致场景 |
| TCC | 最终一致性 | 高 | 高 | 高并发业务(支付、库存) |
| Saga | 最终一致性 | 中 | 高 | 长事务、跨服务流程 |
| 消息队列 | 最终一致性 | 高 | 中 | 事件驱动架构 |
| 本地消息表 | 最终一致性 | 中 | 低 | 异步通知(订单-积分) |
- 两阶段提交协议(2PC):为准备阶段和提交阶段。准备阶段,协调者向参与者发送准备请求,参与者执行事务操作并反馈结果。若所有参与者准备就绪,协调者在提交阶段发送提交请求,参与者执行提交;否则发送回滚请求。实现简单,能保证事务强一致性。存在单点故障,协调者故障会影响事务流程;性能低,多次消息交互增加延迟;资源锁导致资源长时间占用,降低并发性能。适用于对数据一致性要求高、并发度低的场景,如金融系统转账业务。
- 三阶段提交协议(3PC):在 2PC 基础上,将准备阶段拆分为询问阶段和准备阶段,形成询问、准备和提交三个阶段。询问阶段协调者询问参与者能否执行事务,后续阶段与 2PC 类似。降低参与者阻塞时间,提高并发性能,引入超时机制一定程度解决单点故障问题。无法完全避免数据不一致,极端网络情况下可能出现部分提交部分回滚。用于对并发性能有要求、对数据一致性要求相对较低的场景。
- TCC:将业务操作拆分为 Try、Confirm、Cancel 三个阶段。Try 阶段预留业务资源,Confirm 阶段确认资源完成业务操作,Cancel 阶段在失败时释放资源回滚操作。可根据业务场景定制开发,性能较高,减少资源占用时间。开发成本高,需实现三个方法,要处理异常和补偿逻辑,实现复杂度大。适用于对性能要求高、业务逻辑复杂的场景,如电商系统订单处理、库存管理。
- Saga:将长事务拆分为多个短事务,每个短事务有对应的补偿事务。某个短事务失败,按相反顺序执行补偿事务回滚系统状态。性能较高,短事务可并行执行减少时间,对业务侵入性小,只需实现补偿事务。只能保证最终一致性,部分补偿事务失败可能导致系统状态不一致。适用于业务流程长、对数据一致性要求为最终一致性的场景,如旅游系统订单、航班、酒店预订。
- 可靠消息最终一致性方案:基于消息队列,业务系统执行本地事务时将业务操作封装成消息发至消息队列,下游系统消费消息并执行操作,失败则消息队列重试。实现简单,对业务代码修改小,系统耦合度低,能保证数据最终一致性。消息队列可靠性和性能影响大,可能出现消息丢失或延迟,需处理消息幂等性。适用于对数据一致性要求为最终一致性、系统耦合度低的场景,如电商订单支付、库存扣减。
- 本地消息表:业务与消息存储在同一个数据库,利用本地事务保证一致性,后台任务轮询消息表,通过MQ通知下游服务,下游服务消费成功后确认消息,失败则重试。简单可靠,无外部依赖。消息可能重复消费,需幂等设计。适用场景是异步最终一致性(如订单创建后通知积分服务)。
阿里的seata框架了解过吗?
Seata 是开源分布式事务解决方案,支持多种模式:
AT模式:是 Seata 默认的模式,基于支持本地 ACID 事务的关系型数据库。在 AT 模式下,Seata 会自动生成回滚日志,在业务 SQL 执行前后分别记录数据的快照。当全局事务需要回滚时,根据回滚日志将数据恢复到事务开始前的状态。
TCC模式:需要开发者手动编写 Try、Confirm 和 Cancel 三个方法。Try 方法用于对业务资源进行预留,Confirm 方法用于确认资源并完成业务操作,Cancel 方法用于在业务执行失败时释放预留的资源。
SAGA 模式:将一个长事务拆分为多个短事务,每个短事务都有一个对应的补偿事务。当某个短事务执行失败时,会按照相反的顺序执行之前所有短事务的补偿事务,将系统状态回滚到初始状态。
算法
- 组合总和
1736
