本文题目来自于牛客上面的面经分享,会贴上来源帖子
本次题目来源:感谢分享
个人想法,结合其他资料整理的,文章有问题希望大佬可以指出,目前正在准备春招,希望上岸🙏🏻
跳过了实习和项目,直接看后面的面试题
数据库
redis的使用场景
最好结合项目,这里笼统说几个场景:
- 缓存:Redis以其卓越的读写性能和数据自动过期特性,它常用于缓存热点数据,如用户登录信息、商品信息等,以减少数据库的负担并提高响应速度。
- 会话管理:也就是Session的保存。
- 分布式锁:在分布式系统中,Redis的setnx命令可以用于实现分布式锁,以确保对共享资源的并发访问控制。
Redis的数据类型和对应使用的场景
介绍五种常用的
- String:一般用于存储单个值,如简单的键值对、计数器、会话Session等,也可以使用JSON格式来存储对象,还可以用来当做分布式锁使用。
- Hash:对个KV对组成,一般用于存储对象,例如购物车。
- List:被用来当做消息队列,使用LPUSH + RPOP。
- Set:集合中的值都是唯一的不重复的,可以被用来聚合运算,比如查看共同关注。
- ZSet:比Set多了一个用于排序的分数,一般被用来做排行榜等更新快的东西。
Redis的淘汰策略有哪些
内存淘汰策略指的是当内存不足够新的数据添加或修改时,也就是内存达到了设置的阈值所触发的机制,可能是直接报错,也有可能将内存中的数据删除一部分以便新数据添加成功。
一共有八种:
- noeviction:不做淘汰内存操作,而是直接报错。
- volatile-random:在设置了过期时间的数据中随机淘汰数据。
- allkeys-random:在所有数据中随机淘汰数据。
- volatile-lru:在设置了过期时间的数据中使用lru算法淘汰数据。
- allkeys-lru:在所有数据中使用lru算法淘汰数据。
- volatile-lfu:在设置了过期时间的数据中使用lfu算法淘汰数据。
- allkeys-lfu:在所有数据中使用lfu算法淘汰数据。
- volatile-ttl:在设置了过期时间的数据中淘汰最近过期的数据,也就是所剩时间最少的数据。
Redis持久化有哪些,区别是什么
持久化就是指将内存中的数据存储到磁盘中,Redis提供了三种方法:AOF,RDB和二者混合,AOF存储的是写指令,而RDB存储的是所有数据,下面讲讲二者的区别。
- 数据持久化方式:
也就是最本质的区别,RDB持久化是将Redis在内存中的数据保存到磁盘上的一个二进制文件中,该文件保存的是某个时间点上的数据快照;AOF持久化是将Redis接收到的写命令以追加方式记录到一个文本文件中,该文件保存的是一系列写命令的顺序记录。 - 恢复速度:
RDB持久化通常比AOF持久化快,因为在恢复时只需要加载一个二进制文件即可,而AOF还要解析指令。
还有一种是二者混合的方式,前面一半是RDB格式,后面一半是AOF格式,这样子就可以在恢复的时候使用RDB格式快速恢复,并且也能通过AOF机制保证数据尽可能少的丢失。
MySQL使用的索引引擎是什么,底层实现的数据结构
应该问的是存储引擎吧,没听过索引引擎,MySQL可以使用以下引擎:InnoDB、MyISAM、Memory、Archive等,其中一般问的多的就是InnoDB和MyISAM,下面讲讲各自的区别:
- 事务支持:InnoDB支持事务,而MyISAM不支持事务。
- 行级锁锁:InnoDB支持表锁、行级锁,而MyISAM只支持表锁。
- 崩溃恢复和故障恢复:因为InnoDB支持事务和日志,可以更好的确保数据的完整性和一致性,而MyISAM不支持事务,也没有日志,所以对数据的保障性差些。
- 外键约束(但是不建议使用外键):InnoDB支持外键约束,而MyISAM不支持。
至于底层实现的数据结构,应该针对的是索引的底层数据结构,一般有两种:B+树和Hash。
如何进行查询性能优化
下面介绍几种方法:
- 分析慢查询日志,找出具体的慢查询语句和原因。(慢查询日志可以看这篇文章:mysql进阶-查询优化-慢查询日志)
- 使用数据库性能分析工具来分析查询执行计划、索引使用情况等,例如我们在mysql中可以使用explain指令来查看执行计划。
- 分析SQL语句,尽量减少子查询和连表查询。
- 如果是数据量过于庞大,就可以进行数据库的分库分表分区(对于查询语句优化,一般采用分表)。
索引失效一般有哪些原因
- 使用函数或表达式进行查询,例如 select * from t where a+1=10; select * from t where year(a) = 2024;等
- 使用了左或左右模糊匹配,也就是like %xx 或者 like %xx%,例如 select * from t where a like ‘%哈哈’;
- 不符合最左匹配原则,举个例子,如果我们建立了a、b、c的一个联合索引,类似select * from t where b=1 and c=2; select * from t where b=1 ;这一类的语句,也就是不按照索引顺序的查询,都不会走索引查询。
- 使用了OR的查询,例如select * from t where b=1 or c=2;
- 对索引列进行类型转换也会产生索引失效的情况,例如如果索引列是整数类型,但在查询时使用了字符串进行比较,索引可能会失效。
- 数据量太小的时候,会被数据库优化器优化,直接走全表查询,不走索引。
数据过大,如何进行优化
- 定时删除无效数据,减少数据库的数据量。
- 数据量过多时,可以考虑分区分库分表。
- 如果是查询出问题,可以考虑加索引或者优化查询语句。
- 直接提高硬件配置,加磁盘大小。
b+树和b树的区别是什么
主要区别有:
- b树的叶子节点是用单向链表串起来的,而b+树则是双向链表。
- b树的每个节点都会存放真实数据,而b+树则只会在子节点存放真实数据。
通过主要区别就可以衍生出功能上的区别,比如使用b+树可以减少I/O操作次数、使用b+树可以更快的做范围查询等。
计网
简单说一下osi七层模型
对比于我们常见的TCP/IP四层模型来说,七层模型将应用层拆分为三层,将网络接口层拆分为两层,下面讲讲各层的作用:
- 应用层:
他是最高层级,负责提供网络服务和应用程序之间的接口。它包含了各种应用程序,如Web浏览器、电子邮件客户端等,以及与用户交互的各种协议。 - 表示层:
他负责数据的格式化、加密和压缩,以便于不同系统之间的数据交换。它处理数据的语法和语义,确保不同系统能够正确解释和处理数据。 - 会话层:
他负责建立、管理和终止会话连接,以确保应用程序之间的通信能够顺利进行。 - 传输层:
负责端到端的数据传输,为上层提供可靠的数据传输服务。 - 网络层:
负责在不同的网络之间进行数据包的路由和转发,他通过逻辑地址(如IP地址)来标识网络中的设备,并决定数据包的最佳路径。 - 数据链路层:
负责在直接连接的节点之间传输数据帧,并处理物理层面上的错误。他定义了数据的帧结构,进行数据的差错检测和纠正,并管理对物理介质的访问。 - 物理层:
负责传输原始比特流,他定义了物理介质的特性和接口标准,如电压、光信号、电缆规范等。
tcp的三次握手过程,如果是2次可以吗?
老题目了,直接上小林coding:
图片来源:小林coding
简单总结一下步骤:
- 客户端向服务端发起连接请求,初始化序列号并将syn标志设置为1,发送该报文并将自身设置为SYN-SENT状态,等待服务端应答。
- 服务端收到该数据包,发送一个应答报文,首先初始化序列号并将确认应答号设置为接受到的数据包的序列号+1,再将报文的syn和ack位设置为1,发送该报文并设置自身状态为SYN-RCVD(也就是syn报文接收到了),等待客户端应答。
- 客户端收到数据,发送该过程中的最后一个报文,首先将报文的确认应答号设置为收到报文的序列号+1,并将报文的ack位设置为1发送给服务端,将自身状态设置为ESTABLISHED状态,服务端在接收到该报文后也将自身状态设置为ESTABLISHED,至此三次握手结束,两端建立了连接。
至于为什么不能使用两次握手,举个简单的例子,假设客户端发送了一个请求连接的报文,但是这个报文由于网络波动没有准时送到,结果客户端就发送了一个新的报文到服务端,后面两边结束了连接,但这个时候最开始的报文终于送到了服务端,由于是两次握手,所以服务端在发送了应答报文以后就直接进入了连接状态,但是这个时候客户端已经关闭了,所以他不会发送任何数据到服务端,也就是服务端只是一厢情愿罢了,客户端不理他,这就导致了服务端这种无效连接变多,导致服务端资源浪费,所以不能使用两次握手。
tcp如何保证可靠传输的
三个机制:重传机制、流量控制、拥塞控制。
- 重传机制:如果发送方在发送数据后等待了一段时间仍然没有收到确认应答,就会认为数据可能丢失或损坏,于是会重新发送该数据段。TCP使用的重传机制可以确保数据的可靠传输,即使出现了网络丢包或延迟等问题。
- 流量控制:TCP使用滑动窗口机制进行流量控制,确保发送方不会发送过多的数据导致接收方无法处理。接收方可以通过调整窗口大小来控制发送方的发送速率,从而保护自己不被发送方的数据淹没。
- 拥塞控制:TCP通过拥塞控制算法来避免网络拥塞,并在出现拥塞时适当调整发送速率。拥塞控制算法包括慢启动、拥塞避免、快速重传和快速恢复等,通过监控网络的拥塞情况和调整发送速率,确保网络的稳定性和吞吐量。
对称加密和非对称加密有哪些,区别是什么
简单说说二者区别:
特点 | 对称加密 | 非对称加密 |
---|---|---|
使用的密钥 | 同一个密钥 | 一对密钥:公钥和私钥 |
加密速度 | 快 | 相对较慢 |
密钥管理 | 复杂(需要安全地共享密钥) | 相对简单(公钥可公开,私钥保密) |
安全性 | 密钥管理可能存在风险 | 密钥管理相对更容易 |
应用 | 加密和解密 | 加密、解密、数字签名 |
主要区别就是一个是使用同一个秘钥,一个是使用一对秘钥,其中公钥加密的数据不可被公钥解密。
常见的对称加密算法有DES、3DES、AES等;常见的非对称加密算法有RSA、DSA、ECC等。
如果问的是各个加密算法之间的区别,建议还是去看看各个算法的原理吧。
tcp和udp的区别
可以参考一下四点:
- 连接性:TCP 是面向连接的,而 UDP 是无连接的。
- 可靠性:TCP 提供可靠的数据传输,而 UDP 不保证数据的可靠性。
- 顺序性:TCP 保证数据按照发送顺序到达接收方,而 UDP 不保证数据的顺序性。
- 效率:UDP 比 TCP 更加轻量级和高效,适用于对实时性要求较高的场景。
Java
Java中的集合类有哪些
主要有三类:List、Map和Set。List有ArrayList、LinkedList、Vector等;Set有HashSet、TreeSet、LinkedHashSet等;Map有HashMap、LinkedHashMap、TreeMap等。如果还要拓展,那还有Queue(队列)、Deque(双端队列),Deque是通过继承Queue产生的。
hashmap的数据结构是怎么样的
常规面试题,在1.8以前使用的是数组+链表,在1.8及以后使用的是数据+链表/红黑树。
图片来源:javaguide
1.8以前:
1.8及以后:
hashmap和hashtable和concurrenthashmap的区别
特点 | HashMap | Hashtable | ConcurrentHashMap |
---|---|---|---|
线程安全性 | 非线程安全 | 线程安全 | 线程安全 |
性能 | 在单线程环境下较好 | 通常性能较低 | 在多线程环境下较好 |
空值处理 | 允许键和值为 null | 不允许键和值为 null | 允许键和值为 null |
主要区别就这三个,最主要的就是线程安全问题,还可以从各自的底层数据结构讲讲,这里就不说明了,大差不差。
hashmap如何扩容
直接引流:怒刷牛客JAVA面经(1)第7题,有详细代码讲解,这里就简单过一下主体流程。
扩容机制主要步骤:
- 设置新阈值和新容量。
- 生成新数组。
- 遍历老数组中的每个位置上的链表或红黑树。
- 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去。
- 如果是红黑树,则需要拆分红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置。
触发扩容机制的主要原因,是在put元素的时候,如果超过了当前数组的最大阈值,就会触发扩容机制,调用resize方法。
图片来源:javaguide
重载和重写的区别
特点 | 重载(Overloading) | 重写(Overriding) |
---|---|---|
定义位置 | 同一个类中 | 子类中对父类方法的重新定义 |
多态性 | 编译时多态 | 运行时多态 |
参数列表 | 不同 | 相同 |
返回类型 | 可以相同也可以不同 | 必须相同 |
访问权限 | 不受继承和方法访问权限影响 | 必须有相同或更宽松的访问权限 |
关键点 | 方法名相同,参数列表不同 | 方法名、参数列表、返回类型必须与父类中的方法一致 |
一句话就是:重载是在同个类中方法名相同,但是传参不同的方法,针对的是同一个类,而重写是指子类重新编写父类的某个方法,传参和方法名必须和父类相同,针对的是父子类。
线程有几种状态,状态之间的流转是怎么样的
再次引流:怒刷牛客JAVA面经(2)的第6题
这里就直接上图,里面包括了状态和相关函数起到的状态切换效果:
什么是线程池,如何实现
线程池是一种管理和重用线程的机制,它可以有效地管理线程的生命周期、提高线程的利用率,并且可以避免不必要的线程创建和销毁开销。
最好是使用自定义方法创建,也就是使用ThreadPoolExecutor构造方法创建,或者使用Executors工具类提供的方法,但是提供的方法创建的线程池都有可能出现问题,例如OOM问题,所以只推荐使用构造方法,并且构造方法还可以设置线程池的名字,在出现问题的时候可以更好的定位问题。
请你说说对synchronized的理解
synchronized 是 Java 中用于实现线程同步的关键字。当一个方法或一个代码块被 synchronized 修饰时,它将变成一个临界区,同一时刻只能有一个线程进入执行,其他线程必须等待。这样可以确保在多线程环境下,对共享资源的访问不会发生冲突,从而保证数据的一致性和线程安全性。(也就是一把锁)
方法上修饰和代码块修饰的底层实现是不同的,如果修饰的是代码块,则底层是通过monitorenter指令和monitorexit指令实现的,而修饰的是方法的情况,底层则是通过增加ACC_SYNCHRONIZED并发控制标识实现的。
还有就是synchronized锁升级问题,其作用就是减少获得锁和释放锁带来的消耗,其底层实现和Mark Word有关,也就是每个对象用来表示锁的一个64位无符号整型,偏向锁则是通过cas修改mark word,失败就会进入到轻量级锁获取过程,其实现也是通过修改cas,失败的化就会进入到获取重量级锁的状态。
cas有了解过吗?
cas,全称 compare and swap,翻译就是比较并且交换,其主要过程就是通过比较当前变量的值是否为我们期望的值,如果是则修改为我们所想要的值;由于 CAS 是基于硬件的原子操作,因此不需要使用锁来实现线程同步,这使得 CAS 比锁机制更加高效。
但是使用cas会产生ABA问题,而且只能保证一个变量的原子操作,如果还使用的是自旋cas,还会导致资源浪费,这些都是可能存在的问题。
jvm有那些垃圾回收算法
引流上瘾:怒刷牛客JAVA面经(1)第12题
直接总结,四种:标记-清除算法、复制算法、标记-压缩算法、分代收集。
- 标记-清除算法: 这是最基本的垃圾回收算法之一。它分为两个阶段:标记阶段和清除阶段。在标记阶段,从根对象出发,标记所有能够被访问到的对象;在清除阶段,清除所有未被标记的对象。标记-清除算法的主要缺点是会产生大量的内存碎片。
- 复制算法: 复制算法将堆内存分为两个区域,一半称为“活动区”,一半称为“空闲区”。当活动区的内存用尽时,将存活的对象复制到空闲区,然后交换两个区域的角色。复制算法的优点是简单高效,缺点是会浪费一半的内存空间。
- 标记-压缩算法: 这种算法结合了标记-清除和复制算法的优点。首先标记所有活动对象,然后将活动对象压缩到内存的一端,最后清理掉边界外的所有对象。标记-压缩算法解决了标记-清除算法产生的内存碎片问题。
- 分代算法: 这种算法基于一个假设:大部分对象的生命周期很短暂。分代算法将堆内存分为年轻代和老年代两部分,年轻代使用复制算法,老年代使用标记-压缩算法。通过这种方式,分代算法能够针对不同对象的生命周期采用不同的垃圾回收策略,提高了效率。
Jvm的运行时内存区域是怎么样的
还是引流:怒刷牛客JAVA面经(1)第11题
详细看引流,这里就简单一句话,对象信息放堆,属于线程共享,线程独占的方法调用放栈。
jvm如何判断对象是否存活
终极引流:怒刷牛客JAVA面经(1)第12题
总结,两种
-
引用计数法
每个对象都维护着一个引用计数器,当对象被引用时计数器加一,当对象的引用被释放时计数器减一。当计数器为0时,表示对象不再被引用,可以被回收。但Java虚拟机一般不采用这种算法,因为它无法解决循环引用的问题,也就是a引用了b,b引用了a,这样子形成了一个环,谁都没办法被回收。 -
可达性分析法
在可达性分析中,从一组称为“GC Roots”的对象出发,通过对象之间的引用关系,逐个标记所有能被引用到的对象,未被标记的对象即被判定为垃圾对象。
能被当做CG Roots节点的有虚拟机栈中引用的对象、静态变量引用的对象、常量引用的对象以及本地方法栈中引用的对象。
请你说说新生代和老年代
首先讲讲新生代:
新生代是存放新创建的对象的区域,大部分对象的生命周期很短暂。
典型的新生代分为两个部分:Eden空间和两个Survivor空间(通常称为From和To空间)。当新对象被创建时,它们会被放入Eden空间。当Eden空间满了之后,会触发一次Minor GC(年轻代垃圾回收),存活的对象会被移到Survivor空间。在经过多次Minor GC后,存活下来的对象会被晋升到老年代(默认是15次GC)。
再说说老年代:
老年代用于存放生命周期较长的对象,通常是在新生代经过多次垃圾回收后仍然存活的对象。
对于一些大对象或者经过多次垃圾回收的对象,它们会直接被分配到老年代。
当老年代的内存空间不足时,会触发一次Major GC(Full GC,完全垃圾回收),对整个堆内存进行垃圾回收。
g1和cms的区别
答案来自
方面 | G1 垃圾回收器 | CMS 垃圾回收器 |
---|---|---|
类型 | G1 是在 JDK 7 中引入的服务器式垃圾回收器。 | CMS 是在 JDK 5 中引入的低延迟垃圾回收器。 |
分代 | G1 将堆分成多个区域,而不是明确分成代。 | CMS 主要针对老年代(tenured space)。 |
收集类型 | G1 使用复制和标记-清除的组合方式进行垃圾回收。 | CMS 主要使用标记-清除方式进行垃圾回收。 |
暂停时间 | G1 通过在多个小暂停中分散收集工作,旨在提供更可预测的暂停时间。 | CMS 旨在通过与应用程序线程并发执行大部分垃圾回收工作来最小化暂停时间。 |
碎片化 | G1 通过同时收集年轻代和老年代,并在收集过程中压缩空闲空间来减少碎片化。 | CMS 由于其增量式收集方法,可能会受到碎片化的影响。 |
可预测性 | G1 提供更好的暂停时间可预测性,以及对最大暂停时间的更多控制。 | CMS 的暂停时间可能更加不可预测,特别是堆大小增加时。 |
主要还是区别在垃圾回收的过程,可以看看这篇文章:文章链接
fullgc多久一次正常(求解)
好问题,我也不知道多久一次算正常,只要在我们使用程序的时候感知最小应该就算正常,估计还得考虑具体场景。