招银网络今年薪资开奖了,荣获大白菜!
创始人
2025-10-22 17:55:41
0

面试刷题网站:

大家好,我是小林。

每年一到 10 月中旬往后,招银网络就开始陆续开奖了。我整理了下 26 届开发岗的薪资情况,你们瞅瞅:

  • 后端开发,总包 27w,杭州,硕士 211

  • 后端开发,总包 27w,深圳,硕士 211

  • 后端开发,总包 30w,深圳,硕士 985

  • 测试开发,总包 22w,成都,硕士 211

后端开发的总包差不多就在 27 到 30 万之间。要是 27 万的总包,拆开来算的话是这样:15.5k 乘以 12 个月,再加上 9 万的津贴和绩效,公积金是按 12% 交的。

我感觉今年招银网络 26 届校招薪资,跟去年基本没差。去年我也统计过 25 届开发岗的开奖情况,这么一对比,简直一模一样。

去年 25届校招招银网络开奖情况

招银网络科技其实是招商银行的软件中心,好多人都说它是银行的内包。

上班时间比正经银行要多几个小时,就拿深圳这边说,早上 8 点半到下午 5 点半,中午有两小时午休,但也会加班。听说一周基本要加三天班,有时候得加到晚上八九点,不过这也看项目情况。周末一般是正常双休,整体来看,加班强度比互联网公司会少一些。

但招银网络在网上的风评不太行,主要是内部挺卷的,据说还有末尾淘汰制。简单说就是,要是你工作绩效一直不怎么样,后面想续签合同就难了,躺平不了一点。

其实跟很多中小厂比,招银网络的薪资待遇还算不错。不过我发现,能拿到招银网络 offer 的同学,学历背景都挺好,基本都是 211、985 的,差点的也是双一流学历,所以它家对学历应该是有一定要求的。

但这些高学历的同学,手上大多都有互联网中大厂的 offer 可以选。跟大厂比起来,招银网络就差点意思了,很难成为他们的第一选择。

就像我里有个同学,今年秋招拿到了招商银行的 offer,但他手上有更好的选择,再加上也考虑到招银网络风评不好,后面大概率会把这个 offer 拒了。不过话说回来,有 offer 还是得先接着,offer 这东西不嫌多,能接就接,手里拿着也能当谈薪的筹码不是。

那话说回来,招银网络的面试难度怎么样呢?

招银网络科技的面试难度相对低一些,比进大厂的概率要高。它的面试流程是两轮技术面加一轮 HR 面,等所有面试都走完,就等着谈薪资、开奖就行。

这次,来一起看看今年招银网络的 Java 后端面经,一面主要是考八股,重点问了 Java、MySQL、网络这些知识点,大家来看看这难度咋样?

招银网络(后端一面)1. ArrayList和LinkedList的区别?

ArrayList和LinkedList都是Java中常见的集合类,它们都实现了List接口。

  • 底层数据结构不同:ArrayList使用数组实现,通过索引进行快速访问元素。LinkedList使用链表实现,通过节点之间的指针进行元素的访问和操作。

  • 插入和删除操作的效率不同:ArrayList在尾部的插入和删除操作效率较高,但在中间或开头的插入和删除操作效率较低,需要移动元素。LinkedList在任意位置的插入和删除操作效率都比较高,因为只需要调整节点之间的指针。

  • 随机访问的效率不同:ArrayList支持通过索引进行快速随机访问,时间复杂度为O(1)。LinkedList需要从头或尾开始遍历链表,时间复杂度为O(n)。

  • 空间占用:ArrayList在创建时需要分配一段连续的内存空间,因此会占用较大的空间。LinkedList每个节点只需要存储元素和指针,因此相对较小。

  • 使用场景:ArrayList适用于频繁随机访问和尾部的插入删除操作,而LinkedList适用于频繁的中间插入删除操作和不需要随机访问的场景。

  • 线程安全:这两个集合都不是线程安全的,Vector是线程安全的。

2. List<>里面填基本数据类型为什么会报错?

List<>等泛型集合类要求填充的必须是引用类型(对象类型),而不能直接使用基本数据类型(如 intchardouble等),否则会编译报错。

这是因为 Java 的泛型机制在设计时就只支持引用类型,不支持基本数据类型。例如,下面的代码会报错:

// 错误示例:List 中直接使用基本数据类型 int

List<int> list = newArrayList<>; // 编译报错

解决的办法是,使用基本数据类型对应的包装类。因此,正确的写法是:

// 正确示例:使用包装类 Integer

List list = newArrayList<>;

list.add(10); // 自动装箱:int -> Integer

intnum = list.get(0); // 自动拆箱:Integer -> int

这么设计的原因是:

  • 泛型的类型擦除机制:Java 泛型在编译后会被擦除为 Object类型,而 Object只能接收引用类型,不能接收基本数据类型。

  • 历史原因:Java 最初设计时基本数据类型和引用类型是严格区分的,泛型是后期(JDK 1.5)才引入的特性,为了兼容已有的类型系统,选择只支持引用类型。

通过使用包装类,结合 Java 的自动装箱(基本类型 → 包装类)和自动拆箱(包装类 → 基本类型)机制,可以很方便地在泛型集合中操作基本数据类型的数据。

3. 值传递和引用传递的区别?

在 Java 中,参数传递只有值传递一种方式,不存在真正的 “引用传递”。但很多人会混淆这两个概念,核心区别在于传递的是 “值的副本” 还是 “引用的副本”。

值传递(Pass by Value)。传递的是实际值的副本,适用于基本数据类型(如 intchar等),修改方法内的参数副本,不会影响原变量的值。例子:

publicstaticvoidmain(String[] args){

intnum = 10;

changeValue(num);

System.out.println(num); // 输出 10(原变量未被修改)

}

publicstaticvoidchangeValue(inta){

a = 20; // 仅修改副本

}

引用传递的误解(本质仍是值传递)。对于对象(引用类型),传递的是对象引用的副本(而非对象本身)。

两个引用(原引用和副本)指向同一个对象,因此通过副本修改对象内部数据,会影响原对象。但如果修改副本的指向(如重新赋值),不会影响原引用的指向。示例:

publicclassPerson{

String name;

Person(String name) { this.name = name; }

}

publicstaticvoidmain(String[] args){

Person p = newPerson("Alice");

changeName(p);

System.out.println(p.name); // 输出 "Bob"(对象内部被修改)

changeReference(p);

System.out.println(p.name); // 仍输出 "Bob"(原引用指向未变)

}

// 修改对象内部数据

publicstaticvoidchangeName(Person obj){

obj.name = "Bob"; // 副本和原引用指向同一个对象

}

// 修改副本的指向(不影响原引用)

publicstaticvoidchangeReference(Person obj){

obj = newPerson("Charlie"); // 副本指向新对象,原引用仍指向旧对象

}

简单来说,Java 中所有参数传递都是值传递

  • 基本类型传递 “值的副本”,修改副本不影响原值。

  • 引用类型传递 “引用的副本”,通过副本可修改对象内容,但无法改变原引用的指向。

4. mysql 的 char和varchar的区别?

CHAR和VARCHAR都是MySQL中用于存储字符串的字段类型,它们都可以存储字符数据。

  • 存储方式不同:CHAR是固定长度,会占用指定长度的存储空间,存储时会自动填充空格至指定长度,读取时自动截断末尾空格。VARCHAR是可变长度,只占用实际字符串长度加1~2字节(用于记录长度)的存储空间,不会自动填充空格,末尾空格会被保留。

  • 长度限制不同:CHAR的最大长度为255个字符。VARCHAR最大长度为65535个字符。

  • 性能表现不同:CHAR由于长度固定,查询和处理速度更快。VARCHAR因长度可变,查询时需额外计算长度,性能略低,但能节省存储空间。

  • 适用场景不同:CHAR适合存储长度固定的数据,如手机号、身份证号等。VARCHAR适合存储长度不固定的数据,如用户名、描述信息等。

-- CHAR 示例:存储固定长度的手机号( 11 位)

CREATE TABLE user(

phone CHAR(11)-- 无论实际输入多少位(不超过11),都占用11字符空间

);

-- VARCHAR 示例:存储可变长度的用户名(最长20位)

CREATE TABLE user(

username VARCHAR(20)-- 实际占用长度 = 用户名长度 + 1字节

);

  • 字符与字节关系:两者的长度参数都指字符数,具体占用字节数与字符集有关(如UTF8中一个字符可能占1~3字节)。

5. 如果硬要用char会有什么问题?

如果硬要在不适合的场景使用 CHAR类型,可能会带来以下问题:

  • 存储空间浪费:CHAR会占用固定长度的空间,即使存储的字符串实际长度很短,也会占用指定的全部长度。例如,用 CHAR(20)存储仅 3 个字符的字符串,会额外浪费 17 个字符的存储空间,当数据量庞大时,这种浪费会非常显著。

  • 数据处理异常:CHAR存储时会自动在末尾填充空格,读取时又会自动截断末尾空格。如果业务中需要保留字符串末尾的空格(如某些特殊格式的编码),使用 CHAR会导致数据失真,而 VARCHAR能正常保留末尾空格。

  • 索引效率降低:对于长度较长的 CHAR字段(如 CHAR(255)),其索引会占用更多存储空间,导致索引文件变大,从而降低索引的查询效率和更新速度,尤其是在频繁进行索引操作的场景下。

  • 不适合变长数据场景:如果存储的字符串长度差异很大(如用户评论、商品描述等),强制使用 CHAR会同时加剧存储空间浪费和性能损耗,而 VARCHAR能根据实际长度动态调整存储,更适合此类场景。

6. 谈谈你对数据库内存碎片的理解?

MySQL 的内存碎片并非单一区域存在,而是主要集中在缓冲池(InnoDB Buffer Pool)日志缓冲区(如 Redo Log Buffer),其中缓冲池的碎片影响最大 , 因为缓冲池是 MySQL 缓存数据页和索引页的核心区域,一旦这里产生碎片,直接影响数据读取效率。

具体来说,缓冲池的内存管理是按 “页(Page)” 为单位的(默认 16KB),当 MySQL 执行增删改操作时,会出现两种典型的碎片场景:

  • 数据页内部碎片:比如一张表的行数据长度不固定(如包含 VARCHAR 字段),当某行数据被更新后长度变长,原数据页放不下,就会触发 “行溢出”, 把超出部分存到 “溢出页”,原数据页里就会留下一小块空闲空间;或者删除某行数据后,数据页内也会出现零散的空闲区域。这些小块空间因为太小,无法容纳新的完整行数据,就成了 “内部碎片”,导致数据页的实际利用率降低(比如 16KB 的页只存了 10KB 有效数据,剩下 6KB 是碎片)。

  • 缓冲池外部碎片:缓冲池是连续的内存区域,当某些数据页被 “淘汰”(比如 LRU 算法移除不常用的页)后,会释放出对应的内存块,但这些释放的块可能不是连续的 , 比如释放了第 10-11KB、第 15-16KB 的块,中间夹着正在使用的 12-14KB 块,这些零散的空闲块无法组合成一个完整的 16KB 数据页,导致后续需要加载新数据页时,即使总空闲内存足够,也无法利用这些零散块,只能申请新的连续内存,造成内存浪费。

再看内存碎片的核心影响:

  • 最直接的是内存利用率下降:明明缓冲池配置了足够大的内存(比如 8GB),但实际能用来缓存有效数据页的空间却因为碎片变少,导致 MySQL 不得不更频繁地从磁盘读取数据(磁盘 IO 远慢于内存),直接拖慢查询速度。

  • 间接导致缓冲池淘汰效率降低:因为碎片占用了内存,有效缓存的页变少,常用的页可能被频繁淘汰,出现 “缓存命中率下降” 的问题 , 比如刚缓存的索引页因为内存碎片导致空间不足被淘汰,下一次查询又得重新读磁盘。

接下来是关键:如何判断和处理 MySQL 内存碎片?

对于 InnoDB,可通过系统表或命令查看缓冲池的碎片情况。比如执行SHOW ENGINE INNODB STATUS\G,在 “BUFFER POOL AND MEMORY” 部分,关注 “Free buffers”(空闲缓冲块数量)和 “Database pages”(已使用的数据页数量), 如果 Free buffers 数值不低,但同时 MySQL 仍频繁发生磁盘 IO,大概率是存在碎片。

处理碎片的核心方式

  • 缓冲池预热与碎片整理:MySQL 5.6 + 版本支持缓冲池的 “在线碎片整理”,可通过设置innodb_buffer_pool_dump_pct(控制 dump 的页比例)和innodb_buffer_pool_load_at_startup(启动时加载),让缓冲池在重启或运行中,将零散的空闲块合并;也可以手动执行ALTER TABLE ... ENGINE=InnoDB(即 “表重建”),重建过程中会重新组织数据页,消除表级别的碎片,间接减少缓冲池的碎片。

  • 合理配置缓冲池参数:比如将innodb_buffer_pool_size设置为物理内存的 50%-70%(避免内存溢出),同时启用innodb_buffer_pool_instances(将缓冲池拆分为多个实例,每个实例独立管理,减少单个实例的碎片产生), 比如 8GB 缓冲池拆成 4 个 2GB 实例,每个实例的碎片影响范围更小。

  • 优化业务 SQL:减少频繁的小批量增删改(比如将多次单条插入改为批量插入),避免数据页频繁被修改导致的行溢出;对于 VARCHAR 等变长字段,合理设计长度,减少更新时的行长度变化,从源头减少碎片产生。

7. http 中长连接和短连接的区别?

HTTP 协议采用的是「请求-应答」的模式,也就是客户端发起了请求,服务端才会返回响应,一来一回这样子。

由于 HTTP 是基于 TCP 传输协议实现的,客户端与服务端要进行 HTTP 通信前,需要先建立 TCP 连接,然后客户端发送 HTTP 请求,服务端收到后就返回响应,至此「请求-应答」的模式就完成了,随后就会释放 TCP 连接。

如果每次请求都要经历这样的过程:建立 TCP -> 请求资源 -> 响应资源 -> 释放连接,那么此方式就是 HTTP 短连接,如下图:

这样实在太累人了,一次连接只能请求一次资源。

能不能在第一个 HTTP 请求完后,先不断开 TCP 连接,让后续的 HTTP 请求继续使用此连接?

当然可以,HTTP 的 Keep-Alive 就是实现了这个功能,可以使用同一个 TCP 连接来发送和接收多个 HTTP 请求/应答,避免了连接建立和释放的开销,这个方法称为 HTTP 长连接

HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。

8. 手写一个深拷贝?

在 Java 中,实现对象深拷贝的方法有以下几种主要方式:

实现 Cloneable 接口并重写 clone 方法

这种方法要求对象及其所有引用类型字段都实现 Cloneable 接口,并且重写 clone 方法。在 clone 方法中,通过递归克隆引用类型字段来实现深拷贝。

classMyClassimplementsCloneable{

privateString field1;

privateNestedClass nestedObject;

@Override

protectedObject clonethrowsCloneNotSupportedException {

MyClass cloned = (MyClass) super.clone;

cloned.nestedObject = (NestedClass) nestedObject.clone; // 深拷贝内部的引用对象

returncloned;

}

}

classNestedClassimplementsCloneable{

privateintnestedField;

@Override

protectedObject clonethrowsCloneNotSupportedException {

returnsuper.clone;

}

}

使用序列化和反序列化

通过将对象序列化为字节流,再从字节流反序列化为对象来实现深拷贝。要求对象及其所有引用类型字段都实现 Serializable 接口。

importjava.io.*;

classMyClassimplementsSerializable{

privateString field1;

privateNestedClass nestedObject;

publicMyClass deepCopy{

try{

ByteArrayOutputStream bos = newByteArrayOutputStream;

ObjectOutputStream oos = newObjectOutputStream(bos);

oos.writeObject(this);

oos.flush;

oos.close;

ByteArrayInputStream bis = newByteArrayInputStream(bos.toByteArray);

ObjectInputStream ois = newObjectInputStream(bis);

return(MyClass) ois.readObject;

} catch(IOException | ClassNotFoundException e) {

e.printStackTrace;

returnnull;

}

}

}

classNestedClassimplementsSerializable{

privateintnestedField;

}

手动递归复制

针对特定对象结构,手动递归复制对象及其引用类型字段。适用于对象结构复杂度不高的情况。

classMyClass{

privateString field1;

privateNestedClass nestedObject;

publicMyClass deepCopy{

MyClass copy = newMyClass;

copy.setField1(this.field1);

copy.setNestedObject(this.nestedObject.deepCopy);

returncopy;

}

}

classNestedClass{

privateintnestedField;

publicNestedClass deepCopy{

NestedClass copy = newNestedClass;

copy.setNestedField(this.nestedField);

returncopy;

}

}

9. 如何实现一个分页查询?

实现 Java 分页查询其实思路挺直接的。首先得明白分页就是不想一次查所有数据,而是按页取,这样能减少数据传输量,页面加载也快。那具体怎么做呢?

首先得确定几个关键参数,比如用户要查第几页(pageNum),每页想显示多少条(pageSize)。有了这两个,就能算出从哪条数据开始查,也就是偏移量,一般是(pageNum-1)*pageSize。

publicPage(intpageNum, intpageSize, longtotal, List list) {

this.pageNum = pageNum;

this.pageSize = pageSize;

this.total = total;

this.list = list;

// 计算总页数

this.totalPages = (int) (total % pageSize == 0? total / pageSize : total / pageSize + 1);

}

然后就是数据库层面的操作了,比如 MySQL 用 LIMIT,后面跟偏移量和每页条数,一步的核心就是让数据库只返回当前页需要的数据,而不是全部。

SELECTid, username, age, email FROMuserLIMIT?, ?

光查当前页数据还不够,还得知道总共有多少条记录,这样才能算出总共有多少页,用户界面上才能显示分页导航。所以通常需要再执行一个 COUNT 查询获取总记录数,总页数就是(总记录数 + pageSize-1)/pageSize,这样算出来比较准确。

SELECTCOUNT(*) FROMuser

拿到当前页数据和总记录数后,就需要把这些信息封装起来,方便返回给前端。一般会建一个分页结果类,里面包含当前页数据列表、当前页码、每页条数、总记录数、总页数,可能还有是否有上一页、下一页这样的判断,前端用这些信息就能展示分页控件了。

实际开发中,我们一般不会自己写原生 SQL 来处理分页,太麻烦了。通常会用一些框架的插件,比如 MyBatis 的 PageHelper,它能拦截 SQL,自动加上分页条件,用起来很方便。不过原理还是一样的,就是帮我们自动计算偏移量,拼接分页 SQL。

// Service 层

publicPageInfo getUserPage(intpageNum, intpageSize){

// 1. 开启分页(参数存入 ThreadLocal)

PageHelper.startPage(pageNum, pageSize);

// 2. 执行原查询(无需手动写分页 SQL)

List userList = userMapper.selectAll; // Mapper 方法正常查询全量数据

// 3. 封装分页结果(包含总记录数、总页数等)

returnnewPageInfo<>(userList);

}

// Mapper 接口(无需任何分页参数)

publicinterfaceUserMapper{

List selectAll;

}

// Mapper XML(正常查询,无需写 LIMIT)

"selectAll" resultType="com.example.User"> SELECT id, name, age FROM user

10. 数据量较大如何分页查询?

当数据量比较大的时候,分页查询就不能简单用常规的 limit 加偏移量的方式了,那样效率会很低。你想啊,当页码特别大的时候,比如查第 1000 页,数据库需要先扫描前面 999 页的所有数据,再取当前页的内容,这对大表来说简直是灾难,扫描的数据量太大,性能会越来越差。

比如用 MySQL,假设有一张 user 表,id 是自增主键且有索引。常规分页可能这么写:

-- 第1页,每页10条

select* fromuserlimit0, 10;

-- 第1000页,这时候offset=9990,需要扫描9990+10条数据

select* fromuserlimit9990, 10;

所以这时候得换思路,一般会用 "基于游标 / 主键的分页"。就是说,我们不用页码和偏移量,而是用上次查询结果的最后一条记录的主键(或者其他有序的唯一字段)作为起点,加上每页条数来查下一页。比如查完第 1 页,记住最后一条的 id 是 100,下一页就查 id>100 的前 10 条。这样数据库可以直接利用主键索引定位,不用扫描前面的无效数据,效率会高很多。

-- 第1页,取id最小的10条

select* fromuserorderbyidasclimit10;

-- 假设上一页最后一条id是10,下一页就从id>10开始取

select* fromuserwhereid> 10orderbyidasclimit10;

-- 再下一页,用上一页最后一条id=20作为条件

select* fromuserwhereid> 20orderbyidasclimit10;

相关内容

热门资讯

比亚迪“海狮06速度”震撼车市... 10月31日,比亚迪海洋网旗下“高能超享SUV”海狮06在上市第一百天,正式迎来第十万辆整车下线。这...
电车复购率近百分百背后:里程焦... 近期一项针对新能源汽车用户的调查显示,超过98%的电车车主在换购时仍会优先考虑电动汽车。这一数据初看...
为什么是欧拉5,成为了长城“猛... 长城纯电的开路先锋,小车,也有大野心。 用七年时间,从“更爱女人”走向“全球城市精品”,欧拉完...
鸿蒙赋能天籁,燃油车智能升级,... 央视财经“风云汽车”携手东风日产与华为乾崑,在华为园区共同打造了一场以“燃油车智能化的新答案”为主题...
比亚迪深化“纯电+混动”双线布... 在近日开幕的2025东京车展上,比亚迪以“ONE BYD”为主题,携乘用车与商用车全线产品重磅亮相,...
纯电TT具现化,奥迪Conce... 近日,奥迪为Concept C举办了一场试驾活动,媒体记者首次在意大利多洛米蒂山旁边的封闭道路上,试...
原创 一... 天然气清洁能源是目前商用车中占比比较大的能源类型,尤其是在气价动辄低至三块七八的西北地区,一辆优秀的...
比亚迪宋家族OTA升级来袭,老... 比亚迪近日通过官方渠道宣布,针对宋家族多款车型启动新一轮OTA升级。此次升级覆盖宋L DM-i天神之...