图解学习网站:https://xiaolincoding.com
大家好,我是小林。
有读者跟我说,看腻了互联网面经,想看看国企的软开面试,想针对性准备一下。
说来就来!这次带大家看看中国移动的面经。
中国移动校招年总包有 20w+,不过可能实际每月到手可能是 1w 多一些,因为很多平摊到奖金和公积金里面了,所以一共加起来20万左右。
中国移动的面试相比互联网中大厂的面试强度会弱一些,通常一场技术面可能是 20 分钟左右,相比互联网中大厂能少 50-60%的强度,所以难度不会太难。
我一般会建议想冲央国企的同学,如果一开始按照中大厂面试难度准备的话,那么后面去面央国企就会感觉很简单,有一种降维打击的感觉了。
这次,我们来看看中国移动 Java 软开的校招一面,主要是问了 Java、Spring、数据结构与算法这三大块知识了,这场面试就问了 8 个技术问题,面试时长是 15 分钟。
首先,Java的优势,我记得跨平台应该是一个大点,因为JVM的存在,一次编写到处运行。然后面向对象,这个可能也是优势,不过现在很多语言都支持面向对象,但是Java的设计从一开始就是OOP的。还有强大的生态系统,比如Spring框架,Hibernate,各种库和工具,社区支持大,企业应用广泛。另外,内存管理方面,自动垃圾回收机制,减少了内存泄漏的问题,对开发者友好。还有多线程支持,内置的线程机制,方便并发编程。安全性方面,Java有安全模型,比如沙箱机制,适合网络环境。还有稳定性,企业级应用长期使用,版本更新也比较注重向后兼容。
劣势的话,性能可能是一个,虽然JVM优化了很多,但相比C++或者Rust这种原生编译语言,还是有一定开销。特别是启动时间,比如微服务场景下,可能不如Go之类的快。语法繁琐,比如样板代码多,之前没有lambda的时候更麻烦,现在有了但比起Python还是不够简洁。内存消耗,JVM本身占内存,对于资源有限的环境可能不太友好。还有面向对象过于严格,有时候写简单程序反而麻烦,虽然Java8引入了函数式编程,但不如其他语言自然。还有开发效率,相比动态语言如Python,Java需要更多代码,编译过程也可能拖慢开发节奏。
在传统编程中,当一个类需要使用另一个类的对象时,通常会在该类内部通过new
关键字来创建依赖对象,这使得类与类之间的耦合度较高。
而依赖注入则是将对象的创建和依赖关系的管理交给 Spring 容器来完成,类只需要声明自己所依赖的对象,容器会在运行时将这些依赖对象注入到类中,从而降低了类与类之间的耦合度,提高了代码的可维护性和可测试性。
具体到Spring中,常见的依赖注入的实现方式,比如构造器注入、Setter方法注入,还有字段注入。
@Service
public class UserService {
private final UserRepository userRepository;
// 构造器注入(Spring 4.3+ 自动识别单构造器,无需显式@Autowired)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
public class PaymentService {
private PaymentGateway gateway;
@Autowired
public void setGateway(PaymentGateway gateway) {
this.gateway = gateway;
}
}
@Autowired
注解字段,代码简洁但隐藏依赖关系,不推荐生产代码。@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
}
MyBatis 在 SQL 灵活性、动态 SQL 支持、结果集映射和与 Spring 整合方面表现卓越,尤其适合重视 SQL 可控性的项目。
<!-- 示例:XML 中定义 SQL -->
<select id="findUserWithRole" resultMap="userRoleMap">
SELECT u.*, r.role_name
FROM user u
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{userId}
</select>
<if>
, <choose>
, <foreach>
等标签动态生成 SQL,避免 Java 代码中繁琐的字符串拼接。<select id="searchUsers" resultType="User">
SELECT * FROM user
<where>
<if test="name != null">AND name LIKE #{name}</if>
<if test="status != null">AND status = #{status}</if>
</where>
</select>
<resultMap id="userRoleMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<collection property="roles" ofType="Role">
<result property="roleName" column="role_name"/>
</collection>
</resultMap>
@Intercepts({
@Signature(type=Executor.class, method="query", args={...})
})
public class PaginationPlugin implements Interceptor {
// 实现分页逻辑
}
@MapperScan
快速扫描 Mapper 接口,结合 Spring 事务管理,配置简洁高效。@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
// 数据源和 SqlSessionFactory 配置
}
可以通过这些方法来保证:
1、可变性 :String
是不可变的(Immutable),一旦创建,内容无法修改,每次修改都会生成一个新的对象。StringBuilder
和 StringBuffer
是可变的(Mutable),可以直接对字符串内容进行修改而不会创建新对象。
2、线程安全性 :String
因为不可变,天然线程安全。StringBuilder
不是线程安全的,适用于单线程环境。StringBuffer
是线程安全的,其方法通过 synchronized
关键字实现同步,适用于多线程环境。
3、性能 :String
性能最低,尤其是在频繁修改字符串时会生成大量临时对象,增加内存开销和垃圾回收压力。StringBuilder
性能最高,因为它没有线程安全的开销,适合单线程下的字符串操作。StringBuffer
性能略低于 StringBuilder
,因为它的线程安全机制引入了同步开销。
4、使用场景 :如果字符串内容固定或不常变化,优先使用 String
。如果需要频繁修改字符串且在单线程环境下,使用 StringBuilder
。如果需要频繁修改字符串且在多线程环境下,使用 StringBuffer
。
对比总结如下:
特性 | String | StringBuilder | StringBuffer |
---|---|---|---|
不可变性 | 不可变 | 可变 | 可变 |
线程安全 | 是(因不可变) | 否 | 是(同步方法) |
性能 | 低(频繁修改时) | 高(单线程) | 中(多线程安全) |
适用场景 | 静态字符串 | 单线程动态字符串 | 多线程动态字符串 |
例子代码如下:
// String的不可变性
String str = "abc";
str = str + "def"; // 新建对象,str指向新对象
// StringBuilder(单线程高效)
StringBuilder sb = new StringBuilder();
sb.append("abc").append("def"); // 直接修改内部数组
// StringBuffer(多线程安全)
StringBuffer sbf = new StringBuffer();
sbf.append("abc").append("def"); // 同步方法保证线程安全
在 Java 中,对于重写 equals
方法的类,通常也需要重写 hashCode
方法,并且需要遵循以下规定:
equals
方法比较结果为 true
,那么它们的 hashCode
值必须相同。也就是说,如果 obj1.equals(obj2)
返回 true
,那么 obj1.hashCode()
必须等于 obj2.hashCode()
。hashCode
值相同,它们使用 equals
方法比较的结果不一定为 true
。即 obj1.hashCode() == obj2.hashCode()
时,obj1.equals(obj2)
可能为 false
,这种情况称为哈希冲突。hashCode
和 equals
方法是紧密相关的,重写 equals
方法时必须重写 hashCode
方法,以保证在使用哈希表等数据结构时,对象的相等性判断和存储查找操作能够正常工作。而重写 hashCode
方法时,需要确保相等的对象具有相同的哈希码,但相同哈希码的对象不一定相等。
操作 | AVL 树 | 红黑树 |
---|---|---|
查询 | ⭐⭐⭐⭐⭐(更快) | ⭐⭐⭐⭐ |
插入/删除 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐(更快) |
平衡开销 | 高 | 低 |
1、查询性能的对比:
在查询性能上,AVL 树由于其严格的平衡特性,表现会稍好于红黑树,但差距通常不大。
2、插入性能的对比:
在插入性能上,红黑树由于其弱平衡特性,表现优于 AVL 树。
在实际应用中,如果查询操作频繁,对查询性能要求较高,且插入和删除操作相对较少,可以选择 AVL 树;如果插入和删除操作较为频繁,对插入性能有较高要求,同时查询性能也能接受一定的损耗,则红黑树是更好的选择。例如,Java 中的 TreeMap
和 TreeSet
底层使用的就是红黑树,以兼顾插入、删除和查询操作的性能。
取常见的解决方案有几种:
import java.util.Arrays;
public class TopKBySorting {
public static int[] topK(int[] arr, int k) {
Arrays.sort(arr);
int n = arr.length;
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = arr[n - k + i];
}
return result;
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int k = 3;
int[] topK = topK(arr, k);
for (int num : topK) {
System.out.print(num + " ");
}
}
}
import java.util.PriorityQueue;
public class TopKByMinHeap {
public static int[] topK(int[] arr, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
for (int num : arr) {
if (minHeap.size() < k) {
minHeap.offer(num);
} else if (num > minHeap.peek()) {
minHeap.poll();
minHeap.offer(num);
}
}
int[] result = new int[k];
for (int i = k - 1; i >= 0; i--) {
result[i] = minHeap.poll();
}
return result;
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int k = 3;
int[] topK = topK(arr, k);
for (int num : topK) {
System.out.print(num + " ");
}
}
}
import java.util.Arrays;
public class TopKByQuickSelect {
public static int[] topK(int[] arr, int k) {
quickSelect(arr, 0, arr.length - 1, k);
int[] result = Arrays.copyOfRange(arr, arr.length - k, arr.length);
Arrays.sort(result);
return result;
}
private static void quickSelect(int[] arr, int left, int right, int k) {
if (left < right) {
int pivotIndex = partition(arr, left, right);
if (pivotIndex > arr.length - k) {
quickSelect(arr, left, pivotIndex - 1, k);
} else if (pivotIndex < arr.length - k) {
quickSelect(arr, pivotIndex + 1, right, k);
}
}
}
private static int partition(int[] arr, int left, int right) {
int pivot = arr[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] < pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, right);
return i + 1;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int k = 3;
int[] topK = topK(arr, k);
for (int num : topK) {
System.out.print(num + " ");
}
}
}
具体怎么选,可以根据具体需求选择合适的方法: