真快啊!招银网络 26 届校招已经陆续开奖了,和去年差别不大,整体来看高了一点点。
我这里主要整理了软件开发岗位(Java)已经开的奖(数据来源于 offershow 和读者分享),大家感受一下:
- 软件开发:总包 27w,杭州,大白菜
- 软件开发:总包 31w,杭州,SP
- 软件开发:总包 27w,深圳,大白菜
- 软件开发:总包 30w,深圳,SP
- 软件开发:总包 26w,成都,大白菜
- 测开:总包 22w,成都,大白菜
可以看到招银网络软开的整体总包在 26w~31w(部分可能更低或者更高),一般是 12 薪。另外,招银网络的公积金是按照 12% 的标准缴纳,这点还是不错的。
招银网络的风评一般,很多人说这就是招银的外包罢了,存在加班问题,内部比较卷(各种指标,末尾淘汰),离职率相对较高。不过,薪资待遇其实还不错,相比较于其他中小厂来说。在当下,对于很多人来说是一个可行的选择。当然了,如果你是大佬有其他更好的 offer,那招银就没啥吸引力了。
招银网络一面还是比较简单的,基本都是一些比较重要且高频的常规八股,项目问的不多,有实习经历的话会简单问问实习,通常没有手撕算法。到了二面的时候, 会开始主要考察你的项目。
下面分享一位读者去年参加招银网络面试分享的一面面经。
重写和重载的区别
| 区别点 | 重载 (Overloading) | 重写 (Overriding) |
|---|---|---|
| 发生范围 | 同一个类中。 | 父类与子类之间(存在继承关系)。 |
| 方法签名 | 方法名必须相同 ,但参数列表必须不同(参数的类型、个数或顺序至少有一项不同)。 | 方法名、参数列表必须完全相同。 |
| 返回类型 | 与返回值类型无关,可以任意修改。 | 子类方法的返回类型必须与父类方法的返回类型相同 ,或者是其子类。 |
| 访问修饰符 | 与访问修饰符无关,可以任意修改。 | 子类方法的访问权限不能低于父类方法的访问权限。(public > protected > default > private) |
| 绑定时期 | 编译时绑定或称静态绑定 | 运行时绑定 (Run-time Binding) 或称动态绑定 |
可变长参数
从 Java5 开始,Java 支持定义可变长参数,所谓可变长参数就是允许在调用方法时传入不定长度的参数。就比如下面这个方法就可以接受 0 个或者多个参数。
java
public static void method1(String... args) {
//......
}
另外,可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数。
java
public static void method2(String arg1, String... args) {
//......
}
可变长参数的核心价值在于用一种优雅的方式替代了繁琐的方法重载和不便的数组传参,让代码更加灵活、简洁和易读。它的本质是编译器的语法糖,可变长参数在编译后,会被编译器自动转换成一个数组。
浮点运算为什么会丢失精度
《阿里巴巴 Java 开发手册》中提到:"为了避免精度丢失,可以使用 BigDecimal 来进行浮点数的运算"。
浮点数的运算竟然还会有精度丢失的风险吗?确实会!
示例代码:
java
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
为什么浮点数 float 或 double 运算的时候会有精度丢失的风险呢?
这个和计算机保存小数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么十进制小数没有办法用二进制精确表示。
就比如说十进制下的 0.2 就没办法精确转换成二进制小数:
java
// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,
// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。
0.2 * 2 = 0.4 -> 0
0.4 * 2 = 0.8 -> 0
0.8 * 2 = 1.6 -> 1
0.6 * 2 = 1.2 -> 1
0.2 * 2 = 0.4 -> 0(发生循环)
...
SPI 和 API 区别
SPI 即 Service Provider Interface ,字面意思就是:"服务提供者的接口",我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。
SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。

那 SPI 和 API 有啥区别?
说到 SPI 就不得不说一下 API(Application Programming Interface) 了,从广义上来说它们都属于接口,而且很容易混淆。下面先用一张图说明一下:

一般模块之间都是通过接口进行通讯,因此我们在服务调用方和服务实现方(也称服务提供者)之间引入一个"接口"。
- 当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API。这种情况下,接口和实现都是放在实现方的包中。调用方通过接口调用实现方的功能,而不需要关心具体的实现细节。
- 当接口存在于调用方这边时,这就是 SPI 。由接口调用方确定接口规则,然后由不同的厂商根据这个规则对这个接口进行实现,从而提供服务。
举个通俗易懂的例子:公司 H 是一家科技公司,新设计了一款芯片,然后现在需要量产了,而市面上有好几家芯片制造业公司,这个时候,只要 H 公司指定好了这芯片生产的标准(定义好了接口标准),那么这些合作的芯片公司(服务提供者)就按照标准交付自家特色的芯片(提供不同方案的实现,但是给出来的结果是一样的)。
SPI 这个知识点还是蛮重要的,更详细的介绍,例如底层原理这些,可以阅读笔者写的这篇文章:javaguide.cn/java/basis/... 。
MySQL 索引了解吗?底层数据结构是什么?
索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。
索引的作用就相当于书的目录。打个比方:我们在查字典的时候,如果没有目录,那我们就只能一页一页地去找我们需要查的那个字,速度很慢;如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引底层数据结构存在很多种类型,常见的索引结构有:B 树、 B+ 树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyISAM,都使用了 B+ 树作为索引结构。
索引的优点:
- 查询速度起飞 (主要目的) :通过索引,数据库可以大幅减少需要扫描的数据量,直接定位到符合条件的记录,从而显著加快数据检索速度,减少磁盘 I/O 次数。
- 保证数据唯一性 :通过创建唯一索引 (Unique Index),可以确保表中的某一列(或几列组合)的值是独一无二的,比如用户 ID、邮箱等。主键本身就是一种唯一索引。
- 加速排序和分组:如果查询中的 ORDER BY 或 GROUP BY 子句涉及的列建有索引,数据库往往可以直接利用索引已经排好序的特性,避免额外的排序操作,从而提升性能。
索引的缺点:
- 创建和维护耗时 :创建索引本身需要时间,特别是对大表操作时。更重要的是,当对表中的数据进行增、删、改 (DML 操作) 时,不仅要操作数据本身,相关的索引也必须动态更新和维护,这会降低这些 DML 操作的执行效率。
- 占用存储空间 :索引本质上也是一种数据结构,需要以物理文件(或内存结构)的形式存储,因此会额外占用一定的磁盘空间。索引越多、越大,占用的空间也就越多。
- 可能被误用或失效:如果索引设计不当,或者查询语句写得不好,数据库优化器可能不会选择使用索引(或者选错索引),反而导致性能下降。
那么,用了索引就一定能提高查询性能吗?
不一定。 大多数情况下,合理使用索引确实比全表扫描快得多。但也有例外:
- 数据量太小:如果表里的数据非常少(比如就几百条),全表扫描可能比通过索引查找更快,因为走索引本身也有开销。
- 查询结果集占比过大:如果要查询的数据占了整张表的大部分(比如超过 20%-30%),优化器可能会认为全表扫描更划算,因为通过索引多次回表(随机 I/O)的成本可能高于一次顺序的全表扫描。
- 索引维护不当或统计信息过时:导致优化器做出错误判断。
在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构。详细介绍可以参考这篇文章:为什么要用索引?MySQL 索引底层数据结构是什么?。
MySQL 索引这个知识点真的太太太重要了,后端面试高频高点,性价非常高的 SQL 优化手段。
我专门写了一篇文章来总结 MySQL 索引常见的问题,传送门:MySQL 索引详解。这篇文章的最新版本推荐前往 JavaGuide 官方网站进行阅读,地址:javaguide.cn。

MySQL 的日志讲一下, binlog 和 redolog 有什么区别?
MySQL 日志的内容非常重要,面试中经常会被问到。同时,掌握日志相关的知识也有利于我们理解 MySQL 底层原理,必要时帮助我们排查解决问题。
MySQL 中常见的日志类型主要有下面几类(针对的是 InnoDB 存储引擎):
- 错误日志(error log) :对 MySQL 的启动、运行、关闭过程进行了记录。
- 二进制日志(binary log,binlog) :主要记录的是更改数据库数据的 SQL 语句。
- 一般查询日志(general query log) :已建立连接的客户端发送给 MySQL 服务器的所有 SQL 记录,因为 SQL 的量比较大,默认是不开启的,也不建议开启。
- 慢查询日志(slow query log) :执行时间超过
long_query_time秒钟的查询,解决 SQL 慢查询问题的时候会用到。 - 事务日志(redo log 和 undo log) :redo log 是重做日志,可以保证事务持久性。undo log 是回滚日志,可以保证事务原子性。
- 中继日志(relay log) :relay log 是复制过程中产生的日志,很多方面都跟 binary log 差不多。不过,relay log 针对的是主从复制中的从库。
- DDL 日志(metadata log) :DDL 语句执行的元数据操作。
二进制日志(binlog)和事务日志(redo log 和 undo log)比较重要,需要我们重点关注。
binlog 和 redolog 的区别如下:
- binlog 主要用于数据库还原,属于数据级别的数据恢复,主从复制是 binlog 最常见的一个应用场景。redolog 主要用于保证事务的持久性,属于事务级别的数据恢复。
- redolog 属于 InnoDB 引擎特有的,binlog 属于所有存储引擎共有的,因为 binlog 是 MySQL 的 Server 层实现的。
- redolog 属于物理日志,主要记录的是某个页的修改。binlog 属于逻辑日志,主要记录的是数据库执行的所有 DDL 和 DML 语句。
- binlog 通过追加的方式进行写入,大小没有限制。redo log 采用循环写的方式进行写入,大小固定,当写到结尾时,会回到开头循环写日志。
- ......
排序算法有哪些
常见的内部排序算法有:插入排序 、希尔排序 、选择排序 、冒泡排序 、归并排序 、快速排序 、堆排序 、基数排序等。
| 排序算法 | 时间复杂度(平均) | 时间复杂度(最差) | 时间复杂度(最好) | 空间复杂度 | 排序方式 | 稳定性 |
|---|---|---|---|---|---|---|
| 冒泡排序 | O(n^2) | O(n^2) | O(n) | O(1) | 内部排序 | 稳定 |
| 选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 内部排序 | 不稳定 |
| 插入排序 | O(n^2) | O(n^2) | O(n) | O(1) | 内部排序 | 稳定 |
| 希尔排序 | O(nlogn) | O(n^2) | O(nlogn) | O(1) | 内部排序 | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 外部排序 | 稳定 |
| 快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(logn) | 内部排序 | 不稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 内部排序 | 不稳定 |
| 计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 外部排序 | 稳定 |
| 桶排序 | O(n+k) | O(n^2) | O(n+k) | O(n+k) | 外部排序 | 稳定 |
| 基数排序 | O(n×k) | O(n×k) | O(n×k) | O(n+k) | 外部排序 | 稳定 |
术语解释:
- n:数据规模,表示待排序的数据量大小。
- k:"桶" 的个数,在某些特定的排序算法中(如基数排序、桶排序等),表示分割成的独立的排序区间或类别的数量。
- 内部排序:所有排序操作都在内存中完成,不需要额外的磁盘或其他存储设备的辅助。这适用于数据量小到足以完全加载到内存中的情况。
- 外部排序:当数据量过大,不可能全部加载到内存中时使用。外部排序通常涉及到数据的分区处理,部分数据被暂时存储在外部磁盘等存储设备上。
- 稳定 :如果 A 原本在 B 前面,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> A = B A=B </math>A=B,排序之后 A 仍然在 B 的前面。
- 不稳定 :如果 A 原本在 B 的前面,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> A = B A=B </math>A=B,排序之后 A 可能会出现在 B 的后面。
- 时间复杂度:定性描述一个算法执行所耗费的时间。
- 空间复杂度:定性描述一个算法执行所需内存的大小。
快排详细介绍
快速排序的基本思想是通过一趟排序将待排序列分隔成独立的两部分,其中一部分记录的元素均比另一部分的元素小,则可分别对这两部分子序列继续进行排序,以达到整个序列有序。
快速排序使用分治法(Divide and conquer)策略来把一个序列分为较小和较大的 2 个子序列,然后递归地排序两个子序列。具体算法描述如下:
- 选择基准(Pivot) :从数组中选一个元素作为基准。为了避免最坏情况,通常会随机选择。
- 分区(Partition) :重新排列序列,将所有比基准值小的元素摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个操作结束之后,该基准就处于数列的中间位置。
- 递归(Recurse) :递归地把小于基准值元素的子序列和大于基准值元素的子序列进行快速排序。

关于性能,这也是它与归并排序的关键区别:
- 平均和最佳情况: 它的时间复杂度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n l o g n ) O(nlogn) </math>O(nlogn)。这种情况发生在每次分区都能把数组分成均等的两半。
- 最坏情况: 它的时间复杂度会退化到 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n 2 ) O(n^2) </math>O(n2)。这发生在每次我们选的基准都是当前数组的最小值或最大值时,比如对一个已经排好序的数组,每次都选第一个元素做基准,这就会导致分区极其不均,算法退化成类似冒泡排序。这就是为什么随机选择基准非常重要。
开源 Java 学习&面试(Go、Python 后端面试通用):JavaGuide (Github 收获155+k Star)。