转载自: 去邮储银行面试了,压力不大!

图解学习网站:https://xiaolincoding.com

大家好,我是小林。

最近写了很多互联网大厂面经(已累计 100 篇啦),这分享点不一样的。

今天分享一位同学邮储银行的后端开发的面经,邮储银行的总包据听说 26W,邮储试用期6个月,由总行随机分配去网点,实习期工资五、六千左右。

邮储银行的面试流程,先是笔试,笔试跟互联网还不太一样,互联网中大厂笔试都是算法题,邮储的笔试没有编程题,全是单选题,150分钟 。

  • EPI(45 题,言语理解,逻辑推理,数算,思维策略,资料分析) 。
  • 英语(40 题,其中 20 道单选,5 篇阅读理解) 。
  • 综合(60题,银行行业,信息科技,法律,时事政治)。

笔试通过之后,就会进行面试邀约了。主要经历了一二面,每一面大概问 15 分钟的问题,相比互联网中大厂的面试难度是降低了不少。

  • 第一面的时候是群面的,三个候选人一起面,每个人先自我介绍,然后面试官对每个人一个一个问题
  • 第二面的时候是多个面试官面一个人

面试过程中被问的一些问题:

  • 技术问题:Java、MySQL、Redis、网络、算法
  • 软问题:职业发展、银行的认识、遇到的困难的解决等等。

这次跟大家解析一波银行一二面的技术方面的问题,大家看看难度如何?

MySQL

聚簇索引和普通索引区别是什么?(一面)

在 MySQL 的 InnoDB 引擎中,每个索引都会对应一颗 B+ 树,而聚簇索引和非聚簇索引最大的区别在于叶子节点存储的数据不同,聚簇索引叶子节点存储的是行数据,因此通过聚簇索引可以直接找到真正的行数据;而非聚簇索引叶子节点存储的是主键id,所以使用非聚簇索引还需要回表查询。

因此聚簇索引和非聚簇索引的区别主要有以下几个:

  • 聚簇索引叶子节点存储的是行数据;而非聚簇索引叶子节点存储的是聚簇索引(通常是主键 ID)。
  • 聚簇索引查询效率更高,而非聚簇索引需要进行回表查询,因此性能不如聚簇索引。
  • 聚簇索引一般为主键索引,而主键一个表中只能有一个,因此聚簇索引一个表中也只能有一个,而非聚簇索引则没有数量上的限制。

如果聚簇索引的数据更新,它的存储要不要变化?(一面)

  • 如果更新的数据是非索引数据,也就是普通的用户记录,那么存储结构是不会发生变化
  • 如果更新的数据是索引数据,那么存储结构是有变化的,因为要维护 b+树的有序性

数据库的表锁和行锁有什么作用?(一面)

表锁的作用:

  • 整体控制:表锁可以用来控制整个表的并发访问,当一个事务获取了表锁时,其他事务无法对该表进行任何读写操作,从而确保数据的完整性和一致性。
  • 粒度大:表锁的粒度比较大,在锁定表的情况下,可能会影响到整个表的其他操作,可能会引起锁竞争和性能问题。
  • 适用于大批量操作:表锁适合于需要大批量操作表中数据的场景,例如表的重建、大量数据的加载等。

行锁的作用:

  • 细粒度控制:行锁可以精确控制对表中某行数据的访问,使得其他事务可以同时访问表中的其他行数据,在并发量大的系统中能够提高并发性能。
  • 减少锁冲突:行锁不会像表锁那样造成整个表的锁冲突,减少了锁竞争的可能性,提高了并发访问的效率。
  • 适用于频繁单行操作:行锁适合于需要频繁对表中单独行进行操作的场景,例如订单系统中的订单修改、删除等操作。

表级锁:MySQL 里面表级别的锁有这几种:

  • 表锁:通过lock tables 语句可以对表加表锁,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。
  • 元数据锁:当我们对数据库表进行操作时,会自动给这个表加上 MDL,对一张表进行 CRUD 操作时,加的是 MDL 读锁;对一张表做结构变更操作的时候,加的是 MDL 写锁;MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。
  • 意向锁:当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。意向锁的目的是为了快速判断表里是否有记录被加锁

行级锁:InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。

  • 记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的,满足读写互斥,写写互斥
  • 间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。
  • Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。

数据库隔离级别,每个级别会导致什么问题(二面)

SQL 标准提出了四种隔离级别来规避这些现象,隔离级别越高,性能效率就越低,这四个隔离级别如下:

  • 读未提交,指一个事务还没提交时,它做的变更就能被其他事务看到;
  • 读提交,指一个事务提交之后,它做的变更才能被其他事务看到;
  • 可重复读,指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别
  • 串行化;会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

按隔离水平高低排序如下:

针对不同的隔离级别,并发事务时可能发生的现象也会不同。 也就是说:

  • 在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象;
  • 在「读提交」隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象;
  • 在「可重复读」隔离级别下,可能发生幻读现象,但是不可能脏读和不可重复读现象;
  • 在「串行化」隔离级别下,脏读、不可重复读和幻读现象都不可能会发生。

最后一级隔离级别序列化是通过什么实现的(二面)

是通过行级锁来实现的,序列化隔离级别下,普通的 select 查询是会对记录加 S 型的 next-key 锁,其他事务就没没办法对这些已经加锁的记录进行增删改操作了,从而避免了脏读、不可重复读和幻读现象。

哪些场景不适合脏读,举个例子?(二面)

脏读是指一个事务在读取到另一个事务未提交的数据时发生。脏读可能会导致不一致的数据被读取,并可能引起问题。以下是一些不适合脏读的场景:

  • 银行系统:在银行系统中,如果一个账户的余额正在被调整但尚未提交,另一个事务读取了这个临时的余额,可能会导致客户看到不正确的余额。
  • 库存管理系统:在一个库存管理系统中,如果一个商品的数量正在被更新但尚未提交,另一个事务读取了这个临时的数量,可能会导致库存管理错误。
  • 在线订单系统:在一个在线订单系统中,如果一个订单正在被修改但尚未提交,另一个事务读取了这个临时的订单状态,可能导致订单状态显示错误,客户收到不准确的信息。

在以上这些场景中,脏读可能导致严重的问题,因此应该避免发生脏读,保证数据的一致性和准确性。

数据库的索引有什么作用,底层是什么数据结构(二面)

索引可以帮助数据库系统快速定位需要查询的数据,类似于书籍的目录,可以快速找到需要的内容,减少了全表扫描的时间,提高了查询效率。

mysql 的默认存储引擎是 innodb,innodb 默认采用了 b+树作为索引的数据结构。

image.png MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,原因有:

  • B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
  • B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;
  • B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而 B 树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。

Redis

redis怎么用?(一面)

  • redis 主要用来作为 mysql 的缓存,因为 redis 是内存数据库,读写性能很高,能达到 10w qps。
  • 而且也能通过 redis 来实现分布式锁,保证分布式环境下多机竞争的并发安全性。

redis怎么防止缓存击穿?(一面)

缓存击穿:如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题。 缓存击穿解决方案:

  • 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
  • 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;

网络

说一下tcp、udp区别(一面)

  • 连接:TCP 是面向连接的传输层协议,传输数据前先要建立连接;UDP 是不需要连接,即刻传输数据。
  • 服务对象:TCP 是一对一的两点服务,即一条连接只有两个端点。UDP 支持一对一、一对多、多对多的交互通信
  • 可靠性:TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议
  • 拥塞控制、流量控制:TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
  • 首部开销:TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
  • 传输方式:TCP 是流式传输,没有边界,但保证顺序和可靠。UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。

说一下tcp的头部(一面)

image.png 标注颜色的表示与本文关联比较大的字段,其他字段不做详细阐述。序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。****确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。****控制位:

  • _ACK_:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
  • _RST_:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
  • _SYN_:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
  • _FIN_:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。

了不了解dos攻击,怎么防范?(一面)

分布式拒绝服务(DDoS)攻击是通过大规模互联网流量淹没目标服务器或其周边基础设施,以破坏目标服务器、服务或网络正常流量的恶意行为。DDoS 攻击是通过连接互联网的计算机网络进行的。这些网络由计算机和其他设备(例如 IoT 设备)组成,它们感染了恶意软件,从而被攻击者远程控制。这些个体设备称为机器人(或僵尸),一组机器人则称为僵尸网络。 一旦建立了僵尸网络,攻击者就可通过向每个机器人发送远程指令来发动攻击。当僵尸网络将受害者的服务器或网络作为目标时,每个机器人会将请求发送到目标的 IP 地址,这可能导致服务器或网络不堪重负,从而造成对正常流量的拒绝服务。由于每个机器人都是合法的互联网设备,因而可能很难区分攻击流量与正常流量。

常见的DDoS攻击包括以下几类:

  • 网络层攻击:比较典型的攻击类型是UDP反射攻击,例如:NTP Flood攻击,这类攻击主要利用大流量拥塞被攻击者的网络带宽,导致被攻击者的业务无法正常响应客户访问。
  • 传输层攻击:比较典型的攻击类型包括SYN Flood攻击、连接数攻击等,这类攻击通过占用服务器的连接池资源从而达到拒绝服务的目的。
  • 会话层攻击:比较典型的攻击类型是SSL连接攻击,这类攻击占用服务器的SSL会话资源从而达到拒绝服务的目的。
  • 应用层攻击:比较典型的攻击类型包括DNS flood攻击、HTTP flood攻击、游戏假人攻击等,这类攻击占用服务器的应用处理资源极大的消耗服务器处理性能从而达到拒绝服务的目的。

为了防范DDoS攻击,可以采取以下措施:

  • 增强网络基础设施:提升网络带宽、增加服务器的处理能力和承载能力,通过增强基础设施的能力来抵御攻击。
  • 使用防火墙和入侵检测系统:配置防火墙规则,限制不必要的网络流量,阻止来自可疑IP地址的流量。入侵检测系统可以帮助及时发现并响应DDoS攻击。
  • 流量清洗和负载均衡:使用专业的DDoS防护服务提供商,通过流量清洗技术过滤掉恶意流量,将合法流量转发给目标服务器。负载均衡可以将流量均匀地分发到多台服务器上,减轻单一服务器的压力。
  • 配置访问控制策略:限制特定IP地址或IP段的访问,设置访问频率限制,防止过多请求集中在单个IP上。

Java

java创建对象有哪些方式?(二面)

在Java中,创建对象的方式有多种,常见的包括:

  1. 使用new关键字:通过new关键字直接调用类的构造方法来创建对象。
MyClass obj = new MyClass();  

  1. 使用Class类的newInstance()方法:通过反射机制,可以使用Class类的newInstance()方法创建对象。
MyClass obj = (MyClass) Class.forName("com.example.MyClass").newInstance();  

  1. 使用Constructor类的newInstance()方法:同样是通过反射机制,可以使用Constructor类的newInstance()方法创建对象。
Constructor<MyClass> constructor = MyClass.class.getConstructor();  
MyClass obj = constructor.newInstance();  

  1. 使用clone()方法:如果类实现了Cloneable接口,可以使用clone()方法复制对象。
MyClass obj1 = new MyClass();  
MyClass obj2 = (MyClass) obj1.clone();  

  1. 使用反序列化:通过将对象序列化到文件或流中,然后再进行反序列化来创建对象。
// SerializedObject.java  
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.ser"));  
out.writeObject(obj);  
out.close();  
  
// DeserializedObject.java  
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.ser"));  
MyClass obj = (MyClass) in.readObject();  
in.close();  

  1. 使用工厂模式:通过工厂方法或工厂类来创建对象,隐藏对象的创建细节。
public class MyClassFactory {  
    public static MyClass createObject() {  
        return new MyClass();  
    }  
}  
  
MyClass obj = MyClassFactory.createObject();  

Mybatis里的 # 和 $ 的区别?(一面)

  • Mybatis 在处理 #{} 时,会创建预编译的 SQL 语句,将 SQL 中的 #{} 替换为 ? 号,在执行 SQL 时会为预编译 SQL 中的占位符(?)赋值,调用 PreparedStatement 的 set 方法来赋值,预编译的 SQL 语句执行效率高,并且可以防止SQL 注入,提供更高的安全性,适合传递参数值。
  • Mybatis 在处理 ${} 时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时 MyBatis 将参数直接拼入到 SQL 里,不能防止 SQL 注入,因为参数直接拼接到 SQL 语句中,如果参数未经过验证、过滤,可能会导致安全问题。

SpringBoot里面有哪些重要的注解?还有一个配置相关的注解是哪个(一面)

Spring Boot 中一些常用的注解包括:

  • @SpringBootApplication:用于标注主应用程序类,标识一个Spring Boot应用程序的入口点,同时启用自动配置和组件扫描。
  • @Controller:标识控制器类,处理HTTP请求。
  • @RestController:结合@Controller和@ResponseBody,返回RESTful风格的数据。
  • @Service:标识服务类,通常用于标记业务逻辑层。
  • @Repository:标识数据访问组件,通常用于标记数据访问层。
  • @Component:通用的Spring组件注解,表示一个受Spring管理的组件。
  • @Autowired:用于自动装配Spring Bean。
  • @Value:用于注入配置属性值。
  • @RequestMapping:用于映射HTTP请求路径到Controller的处理方法。
  • @GetMapping、@PostMapping、@PutMapping、@DeleteMapping**:简化@RequestMapping的GET、POST、PUT和DELETE请求。

另外,一个与配置相关的重要注解是:

  • @Configuration:用于指定一个类为配置类,其中定义的bean会被Spring容器管理。通常与@Bean配合使用,@Bean用于声明一个Bean实例,由Spring容器进行管理。

算法

算法快慢指针和双指针适用于什么场景?(一面)

快慢指针:主要用于解决链表中的问题。通过设定两个指针,一个移动速度较快,一个移动速度较慢,来遍历链表。

  • 判断链表中是否有环:快慢指针可以用来判断一个链表中是否存在环,快指针每次移动两步,慢指针每次移动一步,如果它们相遇,则链表中存在环。
  • 链表的中间结点:快慢指针也可以用来找到链表的中间结点,快指针每次移动两步,慢指针每次移动一步,当快指针到达链表尾部时,慢指针指向的结点即为中间结点。
  • 判断回文链表:通过快慢指针,可以找到链表的中心,然后翻转后半部分链表,与前半部分比较来判断是否为回文链表。

双指针:

  • 数组中的两数之和:双指针技巧可以用于在有序数组中寻找两个数的和等于目标值的情况,一个指针从数组头部开始,另一个指针从数组尾部开始,根据和与目标值的大小关系来移动指针。
  • 归并两个有序链表:双指针合并两个有序链表时,可以使用双指针,一个指向每个链表的当前节点,根据大小关系来合并链表。
  • 滑动窗口问题:在某些字符串或数组相关的问题中,双指针可以用于滑动窗口技巧,通过调整左右指针的位置来滑动窗口,解决子串问题、子数组问题等。

软问题

  • 学习和生活中遇到了压力很大的问题,是怎么解决的?
  • 能不能接收加班?为什么想来银行?
  • 实习过程中遇到的较难的问题,怎么解决的
  • ........

推荐阅读:

艰难走到字节终面了!

有点慌!字节面试问一个不会一个。。。

背调不通过,到手的offer飞了