美团面经到店研发

三次握手四次挥手,为什么少一次都不行

为什么是三次握手?不是两次、四次?
相信大家比较常回答的是:“因为三次握手才能保证双方具有接收和发送的能力。”

这回答是没问题,但这回答是片面的,并没有说出主要的原因。

在前面我们知道了什么是 TCP 连接:

用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括 Socket、序列号和窗口大小称为连接。
所以,重要的是为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立 TCP 连接。

接下来,以三个方面分析三次握手的原因:

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

为什么挥手需要四次?
再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,因此是需要四次挥手。

拥塞控制和流量控制的区别是什么?怎么实现的?

拥塞控制(Congestion Control)的目的是防止网络拥塞,确保网络资源被高效利用。它通过调整发送方的数据发送速率来避免网络拥塞。

  • 实现方式
    • 慢启动(Slow Start):当一个连接刚开始时,发送速率迅速增加,直到达到某个阈值。超过这个阈值后,发送速率以指数级增长,直到达到一个最大值。
    • 拥塞避免(Congestion Avoidance):当发送速率超过某个阈值后,为了避免网络拥塞,发送速率会以线性增长,而不是指数增长。
    • 快速重传(Fast Retransmit):当发送方收到三个重复的ACK时,它会立即重传丢失的数据包,而不是等待超时。
    • 快速恢复(Fast Recovery):在快速重传后,发送方会减少其发送速率,然后逐渐恢复到拥塞避免状态。

流量控制

流量控制(Flow Control)的目的是防止发送方发送的数据过快,导致接收方来不及处理。它通过调整发送方的数据发送速率来避免接收方的处理瓶颈。

  • 实现方式
    • 滑动窗口(Sliding Window):发送方和接收方都维护一个窗口,用于控制可发送和可接收的数据量。当接收方处理完一定量的数据后,它会通知发送方增加窗口大小,允许发送方发送更多的数据。
    • 窗口大小调整:接收方会根据自己的处理能力来调整窗口大小,从而控制发送方的发送速率。

区别

  • 目的

    • 拥塞控制:防止网络拥塞,确保网络资源高效利用。
    • 流量控制:防止发送方发送数据过快,确保接收方能够及时处理。
  • 对象

    • 拥塞控制:主要针对网络拥塞,涉及网络中的多个节点。
    • 流量控制:主要针对发送方和接收方之间的通信,涉及两个节点。
  • 实现方式

    • 拥塞控制:通过调整发送方的发送速率来实现。
    • 流量控制:通过调整发送方的窗口大小来实现。

总结

拥塞控制和流量控制都是为了优化网络通信,但它们的目的和实现方式不同。拥塞控制关注于网络的整体性能,而流量控制关注于发送方和接收方之间的通信效率。在实际应用中,两者通常会结合使用,以提高通信的效率和稳定性。

线程进程

虚拟内存

讲讲final关键字?能修饰抽象类吗?

在Java中,final关键字是一个非访问修饰符,它可以用来修饰类、方法和变量。final关键字的主要作用是表示一旦某个对象被初始化后,它就不能被改变。下面是final关键字在不同场景下的应用和含义:

final变量:

局部变量:当一个局部变量被声明为final时,这意味着一旦该变量被初始化后,它的值就不能被改变。
成员变量(实例变量和类变量):当一个成员变量被声明为final时,这意味着它必须被初始化,并且在对象的整个生命周期内它的值不能被改变。对于实例变量,每个对象都有一个独立的副本,且在对象的构造过程中必须被初始化。对于类变量(静态变量),必须在声明时或者在静态代码块中初始化。
常量:当final关键字与static关键字结合使用时,它可以用来定义常量。这样的常量在类中只有一个副本,并且在整个应用程序中都是相同的。
final方法:

当一个方法被声明为final时,这意味着这个方法不能被它的子类覆盖(override)。如果一个类不希望它的方法被任何继承它的类改变,那么可以将这些方法声明为final。
final类:

当一个类被声明为final时,这意味着这个类不能被继承。任何试图继承final类的操作都会导致编译错误。

在Java中,final关键字不能用来修饰抽象类。这是因为final和抽象(abstract)关键字是互斥的。

  • final关键字用来修饰类、方法和变量,表示不可变。如果一个类被声明为final,则不能被继承;如果一个方法被声明为final,则不能被重写;如果一个变量被声明为final,则其值在初始化后不能被更改。

  • abstract关键字用来修饰类或方法,表示抽象。如果一个类被声明为抽象类,则不能被实例化,必须由其子类实现所有的抽象方法;如果一个方法被声明为抽象方法,则必须在子类中实现。

由于抽象类需要被继承和实现,而final类不能被继承,因此不能将final用于修饰抽象类。

抽象类一定要有抽象方法吗?抽象类可不可以全都是普通方法?

不需要,可以

讲讲JVM类加载过程

类从加载到虚拟机中开始,直到卸载为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)。

类加载过程详解

1.加载

通过类的全名,获取类的二进制数据流。

解析类的二进制数据流为方法区内的数据结构(Java类模型)

创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口

2.验证

验证类是否符合JVM规范,安全性检查

(1)文件格式验证:是否符合Class文件的规范(2)元数据验证 这个类是否有父类(除了Object这个类之外,其余的类都应该有父类) 这个类是否继承(extends)了被final修饰过的类(被final修饰过的类表示类不能被继承) 类中的字段、方法是否与父类产生矛盾。(被final修饰过的方法或字段是不能覆盖的) (3)字节码验证 主要的目的是通过对数据流和控制流的分析,确定程序语义是合法的、符合逻辑的。(4)符号引用验证:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量

比如:int i = 3;字面量:3符号引用:i

3.准备

为类变量分配内存并设置类变量初始值

static变量,分配空间在准备阶段完成(设置默认值),赋值在初始化阶段完成

static变量是final的基本类型,以及字符串常量,值已确定,赋值在准备阶段完成

static变量是final的引用类型,那么赋值也会在初始化阶段完成

4.解析

把类中的符号引用转换为直接引用

比如:方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接指向方法。

5.初始化

对类的静态变量,静态代码块执行初始化操作

如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

6.使用

JVM 开始从入口方法开始执行用户的程序代码

调用静态类成员信息(比如:静态字段、静态方法)

使用new关键字为其创建对象实例

7.卸载

当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存

讲讲JVM常用的垃圾回收器有哪些,讲讲CMS、G1、ZGC的优缺点,比较一下三者?

常用垃圾回收器:

  • 串行垃圾收集器(Serial作用于新生代,采用复制算法;Serial Old 作用于老年代,采用标记-整理算法 )

  • 并行垃圾收集器(JDK8默认使用此垃圾回收器,Parallel New作用于新生代,采用复制算法;Parallel Old作用于老年代,采用标记-整理算法)

  • CMS(并发)垃圾收集器

  • G1垃圾收集器

select * from t where a=xx and b=xx and c=xx;Q1:这条语句可以怎么优化?

  1. 尽量避免使用 SELECT *,只选择需要的字段,减少 IO 开销
  2. 用explain命令明确问题所在
  3. 创建联合索引:如果 a、b、c 是经常用于查询条件的字段,可以创建一个联合索引,以加快查询速度
  4. 使用覆盖索引:如果查询的字段在索引中都有涵盖,可以避免访问数据行,提高查询性能
  5. 避免索引失效的写法

相比于字符型为什么选择整形作为索引?

  1. 占用空间更小:整型数据类型通常比字符型数据类型占用的存储空间更小,这意味着索引的大小会更小,从而减少磁盘空间的占用和IO开销
  2. 提高缓存效率:使用空间更小的整型作为索引可以提高索引数据在内存中的缓存效率,减少内存的占用和浪费
  3. 提升查询效率:索引数据占用的空间越小,索引的高速缓存命中率就越高,查询效率就越高
  4. 比较效率更高:整型数据的比较操作比字符型数据的比较操作更高效
  5. 排序效率更高:整型数据类型可以更方便地进行排序操作

使用联合索引要注意什么

1.查询时遵循最左前缀原则

2.将范围查询放在最右侧,因为范围查询右侧的索引会失效。

什么是hash索引,和b+树有什么区别?

Hash索引

  • 原理:Hash索引使用哈希表来存储索引键和对应的记录指针。当进行索引查找时,它会计算索引键的哈希值,然后通过哈希表查找对应的记录。

  • 优势

    • 查找速度快:哈希表的查找时间复杂度为O(1),因此Hash索引的查找速度非常快。
    • 不需要排序:由于查找基于哈希值,因此不需要对数据进行排序。
  • 劣势

    • 不支持范围查询:Hash索引不支持范围查询,因为它基于哈希值,而不是数据本身的顺序。
    • 无法利用索引进行排序:由于索引是基于哈希值的,所以无法直接利用索引进行排序。

B+树索引

  • 原理:B+树是一种自平衡的树结构,用于存储键值对。它将键按照某种顺序(通常是排序后的)存储在节点中,并且每个节点都可能包含多个键和子节点。

  • 优势

    • 支持范围查询:B+树索引支持范围查询,因为它基于数据的顺序存储。
    • 顺序访问:B+树索引可以方便地进行顺序访问,因为它的键是按照顺序存储的。
  • 劣势

    • 查找速度相对较慢:B+树索引的查找时间复杂度为O(log n),相对于Hash索引来说,查找速度较慢。
    • 需要排序:为了支持范围查询,B+树索引需要对数据进行排序。

区别

  • 查找方式:Hash索引通过哈希值直接查找,而B+树索引通过键值对顺序查找。
  • 范围查询:Hash索引不支持范围查询,而B+树索引支持。
  • 排序:B+树索引需要对数据进行排序,而Hash索引不需要。
  • 性能:在理想情况下,Hash索引的查找速度更快,但B+树索引支持更复杂的查询操作。

MySQL一条查询语句的执行流程是什么?

1.和数据库建立连接

2.查询缓存(mysql8已删除)

3.语法解析和预处理得到一个新的解析树

4.查询优化与查询执行计划。使用查询优化器根据解析树生成不同的执行计划,然后选择最优的执行计划。

5.执行引擎使用执行计划去操作存储引擎。

mybatis相关,#和$怎么区分使用

  1. #{}语法

    • 安全性#{}语法是安全的,它会将参数值作为字符串处理,防止SQL注入攻击。
    • 性能:由于Mybatis会解析#{}并生成预处理语句(PreparedStatement),因此性能上会有一些开销。
  2. ${}语法

    • 安全性${}语法不安全,它会直接将参数值插入到SQL语句中,容易导致SQL注入攻击。
    • 性能${}语法在Mybatis中会被解析为字符串替换,性能上相对较好,因为它不需要生成预处理语句。
    • 场景:适用于需要在SQL中动态替换字符串的场景,例如在SELECT语句中根据条件动态拼接列名。

JVM的对象创建过程?

  1. 类加载

    • 首先,JVM需要加载要创建对象的类。
    • 如果类已经加载到JVM中,则直接使用已加载的类;否则,从类文件中加载类。
    • 类加载器(ClassLoader)负责从类文件中加载类,并验证其正确性。
  2. 分配内存

    • 对象创建时,JVM需要在堆内存中分配足够的空间。
    • 如果对象小于1个字节,则直接在栈内存中分配;否则,在堆内存中分配。
    • 分配内存的方式有多种,包括指针碰撞和空闲列表
  3. 初始化对象

    • 分配内存后,JVM需要初始化对象,包括调用对象的构造方法。
    • 如果构造方法中存在同步块或同步方法,则需要获取相应的锁。
    • 初始化完成后,对象就创建完成了。
  4. 设置对象引用

    • 创建完成后,如果对象被其他对象引用,JVM需要将引用设置到引用对象的引用中。
    • 如果没有其他对象引用该对象,则该对象为垃圾,等待垃圾收集器回收。
  5. 垃圾收集

    • 如果对象不再被任何引用引用,JVM会将其标记为垃圾,并在合适的时机回收。

@AutoWired 和 @Resource 注解的区别

@AutoWired

  • 默认值@AutoWired的默认行为是按类型自动注入。这意味着,如果Spring容器中有多个匹配类型的Bean,@AutoWired将自动选择一个进行注入。
  • 可配置性@AutoWired可以通过@Qualifier注解进行配置,以指定应该注入哪个Bean。
  • 自动装配@AutoWired是自动装配的一种形式,通常用于字段、构造器和方法。
  • 性能:由于@AutoWired需要进行类型匹配,因此它的性能可能会稍微低于@Resource

@Resource

  • 默认值@Resource的默认行为是按名称自动注入。这意味着,如果Spring容器中有多个匹配类型的Bean,@Resource将自动选择一个具有指定名称的Bean进行注入。
  • 不可配置性@Resource不支持通过@Qualifier注解进行配置。如果需要按名称注入,@Resource需要直接指定Bean的名称。
  • 不支持自动装配@Resource不支持自动装配,它总是通过名称进行注入。
  • 性能:由于@Resource不需要进行类型匹配,它的性能通常优于@AutoWired

Redis为什么快?

1、完全基于内存的,C语言编写

2、采用单线程,避免不必要的上下文切换可竞争条件

3、使用I/O多路复用模型,非阻塞IO

Redis怎么实现消息队列功能?其与常规MQ的区别和优缺点是什么?

使用Redis实现消息队列的常见方式:

  1. 发布/订阅模式

    • 工作原理:生产者(Publisher)将消息发布到指定的频道(Channel),消费者(Subscriber)订阅该频道,当有新消息发布时,消费者会接收到这些消息。
    • 优点:简单、实时性强。
    • 缺点:消息顺序可能不保证,因为消息可能不是按顺序到达。
  2. 列表(List)

    • 工作原理:生产者将消息推送到列表的末尾,消费者从列表的开头拉取消息。
    • 优点:可以保证消息的顺序,类似于传统的队列(FIFO)。
    • 缺点:如果生产者发送消息的速度远大于消费者消费消息的速度,列表可能会变得非常大,影响性能。
  3. 发布/订阅与列表的组合

    • 工作原理:使用发布/订阅来保证消息的实时性,同时使用列表来保证消息的顺序。
    • 优点:结合了两者的优点,既保证了实时性,又保证了消息的顺序。
    • 缺点:实现起来较为复杂。

与常规MQ的区别和优缺点:

  • 与RabbitMQ/Kafka等常规MQ的区别

    • 持久化:Redis的队列通常是非持久的,而RabbitMQ和Kafka可以提供持久化存储。
    • 消息顺序:Redis的队列可能无法保证消息的顺序,而RabbitMQ和Kafka通常可以保证消息的顺序。
    • 吞吐量:RabbitMQ和Kafka通常有更高的吞吐量,适用于大数据量的消息队列场景。
    • 复杂度:Redis的实现更简单,但功能相对有限;RabbitMQ和Kafka的功能更丰富,但实现和配置更为复杂。
  • Redis实现消息队列的优缺点

    • 优点:简单易用,响应速度快,适用于轻量级的、对实时性要求高的消息队列场景。
    • 缺点:不适合大数据量的场景,无法提供持久化存储,可能无法保证消息的顺序。

结论:

Redis实现的消息队列功能简单且快速,适用于对实时性要求高且数据量不大的场景。如果你需要一个持久化、高吞吐量的消息队列,那么RabbitMQ或Kafka可能是更好的选择。

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小猹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值