JAVA面试题大全

JAVA面试题大全

1、Java基本数据类型

整数型:byte[1个字节]、short[2个字节]、int[4个字节]、long[8个字节]
浮点型:float[4个字节]、double[8个字节]
布尔型:boolean[1个字节]
字符型:char[2个字节]

2、描述Spring Bean的生命周期

在 Spring 框架中,Bean 的生命周期包括以下几个阶段:

1、实例化(Instantiation):在这个阶段,Spring 将根据配置文件或注解等方式创建 Bean 实例,并将其存储在容器中。
2、属性赋值(Populate Properties):在这个阶段,Spring 将会自动将 Bean 的属性值从配置文件或注解等方式中注入到 Bean 实例中。
3、初始化(Initialization):在这个阶段,Spring 会调用 Bean 实例的 init-method 方法,完成一些初始化的操作,例如建立数据库连接等。
4、使用(In Use):在这个阶段,Bean 实例已经可以正常使用,供应用程序调用。
5、销毁(Destruction):在这个阶段,Spring 会调用 Bean 实例的 destroy-method 方法,完成一些资源的释放和清理操作,例如关闭数据库连接等。

1 、解析类得到BeanDefinition
2 、如果有多个构造方法,则要推断构造方法
3 、确定好构造方法后,进行实例化得到一个对象
4 、对对象中的加了@Autowired注解属性进行属性填充
5 、回调Aware方法 比如BeanNameAware,BeanFactoryAware
6 、调用BeanPostProcessor的初始化前的方法
7 、调用初始化方法
8 、调用BeanPostProcessor的初始化后的方法,在这里进行AOP
9 、如果当前创建的bean是单例的则会把bean放入单例池里
10、使用bean
11、Spring 容器关闭时调用DisposableBean中的destory()方法

3、JDK 和 JRE 和 JVM 分别是什么,有什么区别?

1、JDK(Java Development Kit):JDK 是 Java 开发工具包,包含了编写、编译、调试和运行 Java 程序所需的所有工具和组件,比如编译器(javac)、Java API、调试工具等。JDK 是针对 Java 开发人员的,它包含了 JRE,还有编译器和其他工具,可以用来编写和调试 Java 程序。

2、JRE(Java Runtime Environment):JRE 是 Java 运行时环境,包括了 Java 虚拟机(JVM)和 Java 标准类库(Java API)。JRE 是针对 Java 应用程序的,它提供了在计算机上运行 Java 应用程序所需的最小环境。

3、JVM(Java Virtual Machine):JVM 是 Java 虚拟机,是 Java 程序运行的环境。JVM 负责将 Java 代码解释或编译为本地机器代码,并在运行时提供必要的环境支持,比如内存管理、垃圾回收、安全性等。JVM 的主要作用是将 Java 代码转换为可以在计算机上运行的机器码,并负责程序的执行。

综上所述,JDK、JRE 和 JVM 之间的区别可以总结如下:

JDK 是 Java 开发工具包,包括了编译器、Java API、调试工具等,用于开发 Java 应用程序。
JRE 是 Java 运行时环境,包括了 Java 虚拟机和 Java 标准类库,用于在计算机上运行 Java 应用程序。
JVM 是 Java 虚拟机,是 Java 程序运行的环境,负责将 Java 代码转换为可以在计算机上运行的机器码,并提供必要的环境支持。

4、什么是字节码?采用字节码的最大好处是什么?

字节码是 Java 程序编译后的中间代码,是一种可移植的二进制代码,可以在任何支持 Java 虚拟机(JVM)的平台上运行。字节码通过将 Java 源代码编译为字节码指令序列,使得 Java 程序可以跨平台运行,即使是在不同的操作系统和硬件平台上也可以运行。

1、字节码采用中间代码的形式,相比于直接将程序编译为特定平台上的机器码,有以下几个好处:

2、可移植性:由于字节码是中间代码,所以可以在任何支持 JVM 的平台上运行,使得 Java 程序具有很好的可移植性。这也是 Java 跨平台的重要特性之一。

3、安全性:由于字节码需要在 JVM 中运行,所以可以对字节码进行安全检查,以确保程序不会对系统造成威胁。

4、性能:由于字节码是一种紧凑的二进制格式,相比于直接编译为机器码,可以更快地加载和传输,同时也可以在运行时进行动态优化,提高程序的执行效率。

5、可读性:相比于直接编译为机器码,字节码具有更好的可读性,可以方便地进行反汇编和调试。

因此,采用字节码作为中间代码的最大好处是提高了 Java 程序的可移植性、安全性、性能和可读性。这也是 Java 跨平台和安全性等特性的基础。


5、什么是数据库事务?讲一下事务的 ACID 特性?

数据库事务是指数据库管理系统(DBMS)中的一个操作序列,这些操作必须作为一个不可分割的单元执行,即要么全部执行成功,要么全部失败回滚。事务通常涉及到对数据库中的数据进行读写操作。

事务的 ACID 特性指四个关键特征:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

1、原子性(Atomicity):事务是一个原子操作,要么全部提交,要么全部回滚。当一个事务执行期间发生故障,操作系统会自动将其回滚到事务执行之前的状态,保证数据的一致性。

2、一致性(Consistency):事务执行结束后,数据必须保持一致性状态。在事务执行期间,数据库中的数据可以处于中间状态,但在事务完成时必须保证数据的一致性。

3、隔离性(Isolation):数据库系统必须保证事务之间相互隔离,不会互相干扰。隔离级别不同,会影响到事务的并发性和数据一致性,比如出现脏读、不可重复读、幻读等问题。

4、持久性(Durability):一旦事务提交,其所做的修改必须永久保存到数据库中。即使系统发生故障或宕机,数据也能够保持不变。

ACID 特性是保证事务正确性和数据一致性的重要手段。在设计数据库应用程序时,应该根据具体的业务需求和数据安全性要求,选择合适的隔离级别和事务提交策略,保证事务的可靠性和数据的一致性。

6、Java 和 C++、Go 语言的区别,各自的优缺点?

JAVA
优点
    简单易学,代码可读性强
    跨平台,一次编写可以在多个操作系统上运行
    面向对象,支持继承、多态等特性
    丰富的类库,可以快速开发应用程序
    自动内存管理,减少了内存泄漏的可能性
缺点:
    由于JVM的存在,运行速度相对较慢
    对于实时性要求较高的场景,Java的表现可能不如C++和Go
C++
优点:
    速度快,适合编写需要高性能的应用程序
    应用广泛,特别是在游戏开发、操作系统和嵌入式系统开发方面
    灵活性高,可以直接访问硬件和内存
缺点:
    学习难度较高,需要掌握指针、内存管理等底层知识
    容易出现内存泄漏和指针错误等问题
    编写代码过程中需要更多的手动管理,相比Java更容易出错
Go:
优点:
    高并发,天生支持协程,能够轻松编写高效的并发程序
    简单易学,语法简洁,上手容易
    静态类型语言,可以避免一些潜在的运行时错误
    快速编译,可以快速构建和部署应用程序
缺点:
    缺乏丰富的类库,与Java和C++相比有些不足
    在一些性能要求极高的场景中可能不如C++表现
    语言本身还比较年轻,相关生态和工具还需要进一步完善
使用场景:

JAVA
	适合开发企业级应用程序、后端服务等。
C++
	适合开发需要高性能和高可靠性的应用程序,特别是在游戏开发、操作系统和嵌入式系统开发方面。
GO
    适合开发高并发的后端服务、微服务、容器化应用程序等.
    当然,每种语言都有其独特的优势和适用场景,具体应根据项目需求和开发团队的技术背景来选择合适的语言。

如果当面试官问你这个问题时,他们可能想要了解你对不同编程语言的了解和理解程度,以及你是否能够根据不同的项目需求和特点选择合适的编程语言。

此外,面试官可能还想知道你是否有跨语言的经验和能力,以及你是否能够评估和解决跨语言集成和兼容性问题。

最后,通过你的回答,面试官还可以了解你对软件开发生态系统的了解程度,以及你是否能够在行业发展和趋势方面保持敏锐的洞察力。

7、什么是 Redis?Redis 有哪些特点?Redis 有哪些常见的应用场景?

Redis(Remote Dictionary Server)是一个开源的高性能键值存储系统,也被称为数据结构服务器。它支持多种类型的数据结构,如字符串、哈希、列表、集合、有序集合等,并提供了丰富的操作这些数据结构的命令。

Redis的特点包括:

    高性能:Redis使用内存来存储数据,并且数据存储在单一的进程中,因此速度非常快。
    多样的数据类型:Redis支持多种数据结构,包括字符串、哈希、列表、集合、有序集合等。
    持久化:Redis支持多种持久化方式,包括RDB快照和AOF日志。
    分布式:Redis支持分布式部署,可以将数据分布在多个节点上。
    简单易用:Redis提供了丰富的命令,使得操作数据非常方便。
Redis的常见应用场景包括:

    缓存:Redis可以作为缓存使用,加速数据读取和响应速度。
    消息队列:Redis提供了列表和发布/订阅功能,可以用来实现消息队列。
    计数器:Redis的计数器功能非常高效,可以用来实现页面访问量、点击量等的计数。
    排行榜:Redis的有序集合功能可以用来实现排行榜。
    分布式锁:Redis可以用来实现分布式锁,保证多个进程之间的互斥访问。
    实时数据分析:Redis可以作为实时数据分析的缓存层,加速数据分析速度。
总之,Redis具有高性能、多样的数据类型、分布式、简单易用等特点,可以应用于各种场景,特别适合用来解决读写频繁的问题。

8、简述计算机网络七层模型和各自的作用?

计算机网络七层模型是一个把网络通信协议分为七个层次的标准模型,其目的是为了让计算机网络的设计和管理更加灵活和模块化。这个模型被称为OSI模型(Open System Interconnection Model),它由国际标准化组织(ISO)于1984年发布,是一个开放的标准模型。

每个层次都有自己的独立功能和责任,这种分层的方式使得每个层次都可以独立工作,同时还能够很好地协调上下层之间的数据传输,而不需要依赖于其他层次的实现细节。以下是每个层次的具体功能和责任:

物理层:主要负责通过物理媒介传输比特流,如电缆、光纤、无线电波等。物理层规定了物理连接的规范,包括电缆的类型、接口的规范等。
数据链路层:主要负责把数据分成数据帧进行传输,并对错误进行检测和纠正。数据链路层还负责物理地址的分配、数据流量控制、错误校验等。
网络层:主要负责数据在网络中的传输,包括路由选择、分组转发、数据报文的封装等。网络层还处理数据包的寻址和控制流量等。
传输层:主要负责数据传输的可靠性和流量控制等,同时还包括分段、组装、连接建立和断开等功能。传输层的最重要的两个协议是TCP和UDP。
会话层:主要负责建立、管理和终止会话,提供会话控制和同步等服务。会话层还负责处理多个应用程序之间的数据交换。
表示层:主要负责数据格式转换、加密解密、压缩解压等服务。表示层使得应用程序可以使用不同的数据格式和编码,同时还提供了数据的安全性和完整性保护等服务。
应用层:主要提供各种服务和应用程序,如电子邮件、文件传输、远程登录、Web浏览等。应用层服务可以使用不同的协议实现,如HTTP、SMTP、FTP、TELNET等。

9、JDK 动态代理和 CGLIB 动态代理的区别是什么?

1、JDK动态代理具体实现原理:

通过实现InvocationHandler接口创建自己的调用处理器;

通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;

通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;

通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。



2、CGLib动态代理:

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

3、两者对比:

JDK动态代理是面向接口的。

CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);

如果要被代理的对象不是个实现类,那么Spring会强制使用CGLib来实现动态代理。

10、MySQL 日志有了解过吗?binlog、redolog、undolog 分别有什么作用、

binlog:即存档日志,是Server层生成的的日志,主要用于数据备份和主从复制;

redolog:即重做日志,是Innodb存储引擎层的日志,是Mysql实现原子性和一致性的重要保证,重要用于事务回归和MVCC;

uodolog:即回滚日志,是Innodb存储引擎层的日志,是Mysql实现持久性的重要保证,主要用于数据库事故故障恢复;

下面我们详细说说三种日志:

首先当我们执行一条增删改sql语句(没用begin)时,mysql会隐式开启事务执行该条语句,在执行完毕后mysql会自动提交事务,我们就能看见增删改的实际结果,但是如果在事务执行中,mysql在没提交事务的时候崩溃了,那么数据有问题了,此时就需要回滚到事务之前的数据,它本质就是用于撤销回退的日志(ctrl+z),在事务没提交之前,mysql会记录更新前的数据到undolog中,当事务回滚时,就利用uodolog日志进行回滚:

1、在插入一条记录时,要把这条记录的主键值记下来,这样之后回滚时只需要把这个主键值对应的记录删掉;
2、在删除一条记录时,要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中;
3、在更新一条记录时,要把被更新的列的旧值记下来,这样之后回滚时再把这些列更新为旧值。
一条记录的uodolog格式由trx_事务id+roll_pointer指针串成一个链表,即版本链。此外在读提交和可重复读隔离级别中,它和快照readView实现了MVCC。

然后是redolog,它是为了防止机器故障导致数据丢失的物理日志,它用于记录数据页做了什么修改,每当执行一条事务就会产生这样的一条或多条物理日志,然后通过顺序写写入磁盘。它和uodolog区别是:redolog 记录了此次事务完成后的数据状态,记录的是更新之后的值;undolog 记录了此次事务开始前的数据状态,记录的是更新之前的值;它保证了数据的持久性。

binlog是逻辑日志,在完成一条更新操作后,server层会生成binlog,等事务提交时,将此事务运行中产生的所有DDL记录通过追加写统一写入binlog日志文件,保存的是全量的日志,用于备份恢复和主从复制。

11、Spring 框架是什么?使用 Spring 框架有哪些好处?

Spring 是一个主流的 Java Web 开发框架,该框架是一个轻量级的应用框架,具有很高的凝聚力和吸引力。Spring 框架因其强大的功能以及卓越的性能而受到众多开发人员的喜爱。
Spring 是分层的 Java SE/EE full-stack 轻量级开源框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,使用基本的 JavaBean 完成以前只可能由 EJB 完成的工作,取代了 EJB 臃肿和低效的开发模式。
在实际开发中,通常服务器端采用三层体系架构,分别为表现层(web)、业务逻辑层(service)、持久层(dao)。
Spring 对每一层都提供了技术支持,在表现层提供了与 Struts2 框架的整合,在业务逻辑层可以管理事务和记录日志等,在持久层可以整合 Hibernate 和 JdbcTemplate 等技术。
从设计上看,Spring 框架给予了 Java 程序员更高的自由度,对业界的常见问题也提供了良好的解决方案,因此,在开源社区受到了广泛的欢迎,并且被大部分公司作为 Java 项目开发的首选框架。
Spring 具有简单、可测试和松耦合等特点,不仅可以用于服务器端的开发,也可以应用于任何 Java 应用的开发中。Spring 框架的主要优点具体如下。
1)方便解耦,简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。

2)方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。

3)降低 Java EE API 的使用难度
Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。

4)方便程序的测试
Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。

5)AOP 编程的支持
Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。

6)声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无须手动编程。

12、Java 中 final 关键字有什么用?

在 Java 中,final 关键字用于表示一个不可变的常量或一个不可变的变量。

在 Java 中,final 关键字可以修饰类、方法和变量,作用如下:

    1、final 修饰类,表示该类不能被继承。final 类中的方法默认都是 final 的,不能被子类重写。

    2、final 修饰方法,表示该方法不能被子类重写。

    3、final 修饰变量,表示该变量只能被赋值一次。final 修饰的变量必须在声明时或构造函数中初始化,且不能再被修改。常用于定义常量。

另外,使用 final 修饰的变量在编译时就已经确定了其值,因此在运行时访问时比非 final 变量更快。

使用 final 关键字可以带来一些好处,例如:

	安全性:将变量声明为 final 可以防止它被改变,从而提高安全性。

	可读性:将常量声明为 final 可以提高代码的可读性,因为常量的值不会被修改。

	优化:final 变量在编译时被转换成常量,这可以提高程序的性能。

总之,final 关键字在 Java 中非常有用,可以提高程序的安全性、可读性和性能

13、HTTP 是哪一层的协议?简述它的作用?

HTTP 是应用层协议,主要用于在 Web 浏览器和 Web 服务器之间传递数据。它是一种无状态的协议,即服务器不会保存关于客户端的任何信息,每次客户端发送请求,服务器都会返回响应。HTTP 协议通常基于 TCP 协议,使用 TCP 的 80 端口作为默认的传输端口。HTTP 协议主要作用包括:

1、建立连接:客户端与服务器建立 TCP 连接,然后发送 HTTP 请求,服务器接收请求并处理。
2、发送请求:客户端发送HTTP请求到服务器,包括请求方法(GET、POST、PUT等)、请求头(如User-Agent、Accept等)和请求正文(可选)等信息。
3、处理请求:服务器接收并解析 HTTP 请求,执行请求操作(如查询数据库等),并将处理结果返回给客户端。
4、返回响应:服务器返回HTTP响应,包括响应状态码(如200 OK、404 Not Found等)、响应头(如Content-Type、Cache-Control等)和响应正文(可选)等信息。
5、关闭连接:客户端接收到响应后,关闭TCP连接。
HTTP 的请求报文主要包括以下几个部分:

1、请求行:包括请求方法、URL 和 HTTP 协议版本。
2、请求头:包括一些请求头部信息,例如 User-Agent、Host 等。
3、空行:表示请求头结束。
4、请求体:实际请求的数据,例如表单提交的数据等。
HTTP 的响应报文主要包括以下几个部分:

1、状态行:包括 HTTP 协议版本、状态码和状态消息。
2、响应头:包括一些响应头部信息,例如 Server、Content-Type、Content-Length 等。
3、空行:表示响应头结束。
4、响应体:实际响应的数据,例如网页的 HTML 代码、图片、音频等。
HTTP 的状态码指示了服务器对请求的处理结果。常见的状态码包括 200 OK(请求成功)、301 Moved Permanently(永久重定向)、404 Not Found(未找到资源)和 500 Internal Server Error(服务器内部错误)等。

总之,HTTP 协议的作用是规定了 Web 应用程序中客户端和服务器之间的通信方式和数据传输格式,是支持 Web 应用程序开发的基础协议。

14、什么是进程和线程?它们有哪些区别和联系?

进程与线程的区别

1、进程是资源分配最小单位,线程是程序执行的最小单位。

2、进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;

3、CPU切换一个线程比切换进程花费小

4、创建一个线程比进程开销小

5、线程占用的资源要比进程少很多。

6、线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;

加强理解, 进程 = 火车 , 线程 = 车厢

线程在进程下行进(单纯的车厢无法运行)
1、一个进程可以包含多个线程(一辆火车可以有多个车厢)
2、不同进程间的数据很难共享(一辆火车上的乘客很难到另外一辆火车)
3、同一进程下不同线程间的数据很容易共享(A车厢换到B车厢很容易)
4、进程要比线程消耗更多计算机资源(采用多列火车相比多个车厢更耗资源)
5、一个线程挂掉不一定会导致整个进程挂掉,因为每一个线程都有自己的栈空间和寄存器,每一个线程都是独立执行的,如果一个线程挂了,一般只会影响到他自己。这就好比如某一节车厢着火了,但是这个车厢有屏蔽措施,可以保证其它车厢没事。 而如果线程访问共享资源出现了错误,例如内存泄漏、死锁等,就会导致进程出现问题。或者是主线程出现了问题(好比火车的动力车厢奔溃了)。

15、Java 中 hashCode 和 equals 方法是什么?它们和 == 各有什么区别?

在 Java 中,hashCode 和 equals 方法都是 Object 类的方法。它们的作用分别如下:

hashCode 方法返回对象的哈希码,用于支持基于哈希表的集合,如 HashMap、HashSet 等。如果两个对象的 equals 方法返回 true,则它们的 hashCode 方法必须返回相同的值,反之则不需要。
equals 方法用于比较对象是否相等。默认情况下,equals 方法使用的是 == 操作符,即只有两个对象引用指向同一个对象时才会返回 true。但是,大部分情况下,我们需要重写 equals 方法来实现自己定义的相等规则。
两者之间的区别在于,hashCode 方法返回的是一个 int 类型的数值,而 equals 方法返回的是一个 boolean 类型的值。

hashCode 方法用于快速比较两个对象是否不同,因为如果它们的哈希码不同,那么它们肯定不相等。equals 方法则用于判断两个对象是否真正相等,这个判断比较复杂,需要根据对象的实际情况来定义。

另外,需要注意的是,== 操作符用于比较两个对象的引用是否相等,即它们是否指向同一个对象。而 equals 方法则用于比较两个对象的值是否相等。(String类对equals()方法进行了重写用户比较字符串的值是否相等。)

在 Java 中,对象的值比较往往需要根据对象的实际情况来定义,因此一般需要重写 equals 方法。

16、Spring 的两大核心概念是什么?简单讲一下你对它们的理解

初始
Spring的两大核心概念是控制反转IOC和面向切面编程AOP:

控制反转IOC:将创建对象和管理项目对象依赖关系的工作交给Spring,可以使开发者更专注于业务逻辑的开发,需要使用到对象时,可以直接在IOC容器中取,降低了代码的复杂度,提高了系统的可维护性。
面向切面编程AOP:开发者可以将一些通用代码,例如日志管理,事务等从业务中抽离出来,定义为切面,使其更易于管理,解耦合,提高了代码的可重用性。
想法
个人认为大家在背这些八股的时候,可以把知识都串联起来,类似思维导图一样,看到这里我们又能想到什么,层层递进。

再问问
那AOP是如何实现的呢?
(见 面试题挑战 Day3 JDK 动态代理和 CGLIB 动态代理的区别是什么?)
如果你简历上写了设计模式,那么这两个核心概念中有涉及到什么设计模式可以讲讲吗?
工厂设计模式:spring中使用了BeanFactory和ApplicationContext创建了Bean对象。
单例模式:在IOC中的对象默认都是单例的,可以通过配置文件修改。
代理模式:AOP就是基于动态代理的,如果对象实现了接口,使用JDK的动态代理,如果对象没有实现接口则使用CGLIB的动态代理。(这里可以暗示往这两个动态代理方面问,就又撞到前几天刷过的题了)
学识浅薄,应该还有其他,小伙伴们可以补充

17、死锁是什么?如何预防和避免死锁?

死锁是指两个或多个进程互相等待对方释放资源而陷入无限等待的状态,导致程序无法继续执行下去,称为死锁。

预防和避免死锁需要:

避免使用多个锁:在设计程序时,应尽量减少使用多个锁,或者在使用多个锁时,尽量将锁的持有时间缩短到最小。这可以减少死锁的概率。
避免嵌套锁:在使用嵌套锁时,需要注意锁的顺序,以避免不同线程获取锁的顺序不同而导致死锁。如果必须使用嵌套锁,可以使用统一的锁顺序来避免死锁。
使用超时机制:在获取锁时,可以设置一个超时时间,如果在指定时间内没有获取到锁,就放弃锁的获取,避免一直等待锁而导致死锁。
使用死锁检测和恢复机制:可以使用死锁检测机制来检测死锁的发生,并采取恰当的措施来解决死锁问题,比如终止一个进程或者回滚一个事务。
避免循环等待:在获取锁的时候,应该避免循环等待,即每个线程只能持有一个锁,而获取其他锁时必须先释放原有锁。


死锁是指两个或多个进程在执行过程中因争夺资源而造成的一种僵局,当进程处于死锁状态时,它们将无法继续执行,而只能相互等待,直到被外部的程序干预或自行放弃。

预防和避免死锁需要采取一些措施,包括:

避免资源独占:尽量避免一个进程在获得了某些资源后再次请求其他资源,而应该将所有需要的资源一次性申请到位。
避免资源持有和等待:当一个进程占用了一些资源并等待另一些资源时,其他进程就无法使用这些资源,容易引发死锁。因此,尽可能减少资源持有和等待时间。
避免资源互斥:有些资源在同一时间只能被一个进程占用,比如打印机、磁带机等,需要采用一些技术手段来避免资源互斥的问题。
引入资源剥夺策略:当一个进程请求的资源被其他进程占用时,可以采取剥夺资源的策略,即暂停占用该资源的进程,直到该资源被释放后再恢复该进程的执行。
引入进程抢占策略:当一个进程等待时间过长时,可以采取抢占其资源的策略,即中断正在执行的进程,强制释放其占用的资源。

18、什么是反射机制?说说反射机制的优缺点、应用场景?

Java 反射机制是指在运行时动态地获取类的信息、创建对象以及调用对象的属性和方法的机制。Java 反射机制提供了运行时检查 Java 类型信息的能力,让 Java 程序可以通过程序获取其本身的信息。

Java 反射机制的优点:

    1、可以动态地获取类的信息,不需要在编译时就知道类的信息。
    2、可以动态地创建对象,不需要在编译时就知道对象的类型。
    3、可以动态地调用对象的属性和方法,可以在运行时动态地改变对象的行为。
Java 反射机制的缺点:

    1、由于反射是动态的,所以它的运行效率较低,不如直接调用方法或属性。
    2、由于反射是动态的,所以它会破坏 Java 的封装性,可能会使代码变得复杂和不稳定。
Java 反射机制的应用场景:

    1、动态代理。动态代理可以使用反射机制在运行时动态地创建代理对象,而不需要在编译时就知道接口的实现类。
    2、单元测试。JUnit 等单元测试框架可以使用反射机制在运行时动态地获取类和方法的信息,实现自动化测试。
    3、配置文件加载。许多框架(如 Spring)使用反射机制来读取和解析配置文件,从而实现依赖注入和面向切面编程等功能。

19、数据库索引是什么,有什么作用,什么场景适合使用索引?

数据库索引是一种数据结构,用于提高数据库表的查询效率。索引可以帮助数据库快速定位和检索存储在表中的数据,从而加快数据查询的速度。在数据量比较大时,使用索引可以极大地提高数据检索的效率。

索引的作用是通过构建一个额外的数据结构(B-tree、哈希表等)来加速数据的检索。它是在数据库表上创建的一种数据结构,它包含一些指向表中数据的指针,可以快速地定位到满足查询条件的数据行,从而提高查询效率。索引可以包含一个或多个列,可以使用单列索引、组合索引、全文索引等多种方式来创建。

适合使用索引的场景包括:

频繁查询的列,如主键、外键等。
经常作为查询条件的列,如WHERE、ORDER BY、GROUP BY 等语句中的列。
经常需要连接的列,如多表联合查询时的列。
数据量较大的表,通过索引可以加快数据检索速度。
索引的优点是可以提高数据库的查询速度,缩短数据检索的时间,提高系统的性能。但是索引也有一些缺点,包括:

占用额外的存储空间,增加了存储成本。
建立索引需要时间,增加了系统的开销。
数据库的更新操作(增删改)会导致索引的重建,影响系统的性能。
因此,需要根据实际情况进行索引的创建和使用,避免过度索引导致系统性能下降。

这题如果能举例你自己是如何在项目中应用索引的(比如检索用户消息)、或者说什么情况下你没有选择用索引的(比如性别字段),会很加分

20、HTTP 有哪些常见的状态码?

啥波一口诀 ( 也不用完全记忆 )
一信 二成 三重 四五败
1xx 一信
  提示信息,是协议处理的中间状态,还需要后续操作 ( 感觉一般也不会遇到 )
2xx 二成
200 : 常见的成功状态码
204 : 200状态码 - 无Body版
206 : 200状态码 - part版 ( 表示响应返回的body数据不是资源的全部 )
3xx 三重
301 : 永久重定向
302 : 301状态码 - 临时版 ( 临时重定向 )
304 : 不具有跳转意义,表示资源未修改,重定向到缓存 ( 304 Not Modified )
4xx 四败
客户端!!!
404 : 资源不存在或者未找到 ( 404 NOT FOUND )
400 : 请求报文有错误 ( 记忆方法: 400最小 发生在 Http 第一步(发送请求)中 )
401 : 请求需要认证
403 : 禁止访问 ( 403 Forbidden )
5xx 五败
服务端!! 
500 : 笼统错误码
502 : 服务器网关或者代理出现问题 ( 502 Bad GateWay )
503 : 服务器正忙,无法处理请求 ( 503 Unavailable )

21、Java 访问修饰符 public、private、protected,以及无修饰符(默认)的区别?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MiEl0MU-1683601393880)(.\面试题.assets\J2CEKIks-java.png)]

22、什么是分布式?为什么需要分布式?

布式是指在多台计算机上协同工作的系统,这些计算机通过网络连接在一起,共同完成一个任务。

分布式系统能够有效地解决单台计算机处理能力不足、系统容易宕机、数据存储容量有限等问题,同时能够提高系统的可靠性、可用性和性能,适用于数据量较大、并发量高、访问频繁的场景。此外,分布式系统还可以通过横向扩展的方式提高系统的性能和可靠性,同时降低单点故障的风险,提高了系统的可伸缩性,方便进行升级和维护。

在分布式系统中,由于数据和计算任务被分布在多台计算机上,不同计算机之间需要进行通信和协调,因此需要解决分布式一致性、负载均衡、故障恢复、数据共享和安全等问题,同时需要考虑数据的一致性和可靠性。因此,分布式系统的设计和实现比单机系统更加复杂和困难,需要考虑到多个因素的综合影响。

23、你是怎么做 MySQL 数据备份的?比如怎么恢复半个月前的数据?

MySQL 数据备份是一个非常重要的工作,保证数据的安全性和可靠性。备份数据的方式有很多种,以下是其中一种基本的备份和恢复方式:

一、备份 MySQL 数据库

我们可以使用 mysqldump 工具来备份 MySQL 数据库,该工具可以生成 SQL 脚本文件,包含数据库中所有表和数据的语句。在终端中运行以下命令:

mysqldump -u [username] -p [database_name] > [backup_file].sql

其中,[username] 是 MySQL 用户名,[database_name] 是需要备份的数据库名称,[backup_file].sql 是备份的文件名。

该命令会将 SQL 脚本文件导出到当前目录下。

二、恢复 MySQL 数据库

如果需要恢复之前备份的数据,可以运行以下命令:

mysql -u [username] -p [database_name] < [backup_file].sql

其中,[username] 是 MySQL 用户名,[database_name] 是需要恢复的数据库名称,[backup_file].sql 是备份的文件名。

该命令会将备份文件中的 SQL 语句执行,从而将数据恢复到指定的数据库中。

如果需要恢复半个月前的数据,可以选择备份文件中的某个时间点之前的数据,并使用以上方法进行恢复。

此外,还有其他的备份方式,如使用 MySQL 自带的 mysqlbinlog 工具进行增量备份,或使用第三方备份软件进行备份。根据实际需求选择合适的备份方式,并将备份文件存放在可靠的位置。

24、什么是消息队列?消息队列有哪些应用场景?

消息队列是一种用于异步通信的机制,用于在不同的应用程序之间传递消息。消息队列通常由消息生产者、消息队列和消息消费者三部分组成。

消息生产者将消息发送到消息队列中,而消息消费者则从消息队列中接收消息。消息队列负责存储和管理消息,确保消息传递的可靠性和稳定性。在实现过程中,消息队列还会提供一些额外的功能,如消息过滤、消息路由、消息持久化等。

消息队列的特点:

    异步通信:消息生产者和消息消费者之间采用异步通信模式,发送方无需等待接收方的响应即可继续执行。
    解耦合:消息队列可以将消息生产者和消息消费者解耦合,使得它们之间的关系更加灵活。
    可靠性:消息队列通常会提供一些保证消息传递可靠性的机制,如消息持久化、重试机制等。
    缓冲:消息队列可以缓冲来自多个消息生产者的消息,使得消息消费者可以按照自己的节奏进行消费,从而有效地平衡生产者和消费者之间的处理速度。		
消息队列的应用:

    异步任务处理:通过将任务发送到消息队列中,异步处理任务,提高系统的并发性能和吞吐量。
    解耦合系统:将不同的业务逻辑拆分成不同的服务,通过消息队列实现服务之间的通信,提高系统的可维护性和可扩展性。
    流量削峰:将流量通过消息队列分散到不同的服务中,避免单个服务被高并发流量打垮。
    日志收集:通过将日志消息发送到消息队列中,将日志收集和分析与业务逻辑解耦合,提高系统的可靠性和可维护性。
    应用解耦:将不同的应用程序通过消息队列进行集成,实现应用之间的解耦合和数据交换。

25、设计模式是什么?为什么要学习和使用设计模式?

设计模式是指在软件开发中经常遇到的一些重复性问题,通过对这些问题的总结、抽象、归纳和提炼,得到的一些解决问题的通用方案。学习和使用设计模式可以帮助开发人员提高代码的可重用性、可维护性、可扩展性和可读性,从而提高开发效率和代码质量。

设计模式的学习和使用,需要结合实际的业务场景进行理解和应用。以下是一些常见的现实业务场景和对应的设计模式:

工厂模式:当需要创建多种具有相同特征的对象时,使用工厂模式可以将对象的创建与业务逻辑分离,降低代码的耦合度。 例如,在电商平台上,不同种类的商品都需要进行库存管理和订单管理,可以使用工厂模式来创建对应的库存管理器和订单管理器。
单例模式:当需要确保系统中某个类只有一个实例时,可以使用单例模式,保证全局唯一性,避免资源的浪费。 例如,在 Web 应用中,有时需要保证所有请求都使用同一个数据库连接,可以使用单例模式来实现数据库连接池。
观察者模式:当一个对象的状态发生改变需要通知其他对象时,可以使用观察者模式,将对象的状态与业务逻辑分离,提高系统的灵活性和可扩展性。 例如,在多人在线游戏中,玩家的行为会影响其他玩家的状态,可以使用观察者模式来实现游戏中的事件处理和状态同步。
策略模式:当需要在运行时根据不同的情况采用不同的算法时,可以使用策略模式,将算法与业务逻辑分离,提高代码的可维护性和扩展性。 例如,在电商平台上,可以使用策略模式来实现不同的促销策略,例如满减、打折等。
面试官问到这个问题,可能想了解面试者是否熟悉常见的设计模式,并能够结合实际业务场景进行理解和应用,以提高代码质量和开发效率。同时,也想了解面试者是否有足够的设计能力和经验,能够在实际项目中使用设计模式来解决问题。

26、一条 SQL 语句在 MySQL 中的执行过程是怎样的?

在 MySQL 中,一条 SQL 语句的执行过程通常可以分为以下几个步骤:

词法分析和语法分析:MySQL 的 SQL 解析器会对输入的 SQL 语句进行词法分析和语法分析,以确定语句的结构和语法是否正确。
查询优化:MySQL 会对 SQL 语句进行优化,以确定最优的执行计划。在这个过程中,MySQL 会考虑许多因素,例如索引、表连接、统计信息等,以找到执行查询的最有效方式。
查询执行:在查询优化后,MySQL 开始执行查询,读取和处理数据。在执行过程中,MySQL 会根据查询中所涉及的表和列等信息,从磁盘中读取相应的数据,并进行计算和过滤操作。
结果返回:最后,MySQL 会将查询结果返回给客户端,完成整个查询过程。
需要注意的是,实际的执行过程可能会因为多种因素而不同,例如数据量、硬件配置等。另外,在并发环境下,多个查询可能会同时进行,需要使用锁和事务等机制来保证数据的一致性和正确性。

27、什么是 IOC,简单讲一下 Spring IOC 的实现机制?

什么是IOC容器,以及IOC的创建过程
	1、基本概念
        1、IOC(Inverse Of Controll,控制反转):就是原来代码里面需要自己手动创建的对象,依赖,反转给Spring来帮忙实现。我们需要创建一个容器,同时需要一种描述来让容器知道要创建的对象与对象之间的关系。
        2、在Spring中BeanFactory就是IOC容器,在Spring初始化的时候,创建容器,并将需要创建对象和对象的关系(xml,注解)通过BeanDefinitionReader加载到BeanDefinition中并保存在BeanDefinitionMap中,然后再由IOC容器创建bean对象.
    2、两种bean的注册方式
        方法1:通过@Bean+@Configuration的方式直接定义要创建的对象与对象的关系
        方式2:通过@Component定义类,这种方式必须使用@ComponetScan定位Bean扫描路径
    3、IOC的创建
        1、在Spring中BeanFactory就是IOC容器,在Spring初始化的时候,创建容器,并将需要创建对象和对象的关系(xml,注解)通过BeanDefinitionReader加载到BeanDefinition中并保存在BeanDefinitionMap中,在这个过程中会让BeanDefinitionProcesser(Bean的定义信息的后置处理器)进行增强,然后再由IOC容器创建bean对象.
    4、Bean的生命周期(面试官顺着问题往下问的拓展)
        1、bean的实例化:spring启动后,会查找和加载需要被spring管理的Bean,并且实例化
        2、bean的属性注入(bean的初始化):bean被实例化后将Bean的引入河值注入到bean的属性中
            1、查看是否调用一些aware接口,比如BeanFactoryAware,BeanFactoryAware,ApplicationContextAware接口,分别会将Bean的名字,BeanFactory容器实例,以及Bean所在用的上下文引用传入给Bean
            2、在初始化之前,会查看是否调用了BeanPostProcessor的预初始化方法,可以对bean进行扩展
        3、调用InitializingBean的afterPropertiesSet()方法:如果Bean实现了InitializingBean接口,spring将调用他们的afterPropertiesSet()方法,类似的,如果Bean使用init-method生命了初始化方法的话,这个方法也会被调用。
        4、初始化成功之后,会查看是否调用BeanPostProcessor的初始化后的方法:如果Bean实现了BeanPostProcessor接口,spring就将调用他们的postprocessAfterInitialization()方法。可以对bean进行扩展
        5、bean的正常使用:可以被应用程序正常使用了,他们将驻留在上下文中,直到应用的上下文被销毁
        6、bean的销毁:调用DisposableBean的destory()方法:如果Bean实现DisposableBean接口,spring将用他的destory()方法,相同的,如果Bean使用了destory-method生命销毁方法,该方法也会被调用。(但由于bean也分为单例和多例,单例bean会随着IOC容器的销毁而销毁,多例的bean不会随着IOC容器的销毁而销毁,他是通过JVM里面的垃圾回收器负责回收)

28、并发和并行有什么区别?同步和异步有什么区别?

并发和并行是两个计算机领域中经常被提到的概念,它们的含义有所不同。

1、并发(Concurrency):指的是系统中同时存在多个正在执行的任务,并且这些任务之间可能会相互影响。并发通常用来处理多个任务共享资源的情况。在单核 CPU 上,多个任务会轮流使用 CPU 时间片,表现为看似同时执行的情况,但实际上只有一个任务正在执行。
2、并行(Parallelism):指的是系统中同时存在多个并且相互独立的任务,并且这些任务可以在多个处理器上同时执行,真正意义上的同时处理多个任务。
3、同步(Synchronous):指的是程序按照代码的顺序执行,一行一行地执行,直到当前行执行完成后才能继续执行下一行。同步通常会阻塞调用者,直到任务完成才能返回。
4、异步(Asynchronous):指的是程序在执行某个任务时,不会一直等待任务完成,而是继续执行下一行代码,当任务完成后再进行相应的处理。异步通常不会阻塞调用者,可以提高系统的并发性能。
总的来说,"并发"和"并行"是针对多个任务的执行方式,"同步"和"异步"是针对任务执行的阻塞方式和返回方式。在实际应用中,可以根据不同的需求来选择合适的并发和同步方式,以提高系统的性能和可靠性。

29、String 和 StringBuffer、StringBuilder 的区别是什么?

String 和 StringBuffer/StringBuilder 是 Java 中两种不同的字符串处理方式,主要的区别在于 String 是不可变的(immutable)对象,而 StringBuffer 和 StringBuilder 则是可变的(mutable)对象。


String 对象一旦被创建,就不可修改,任何的字符串操作都会返回一个新的 String 对象,这可能导致频繁的对象创建和销毁,影响性能。而 StringBuffer 和 StringBuilder 允许进行修改操作,提供了一种更高效的字符串处理方式。


StringBuffer 和 StringBuilder 的主要区别在于线程安全性和性能方面。StringBuffer 是线程安全的,所有方法都是同步的,因此可以被多个线程同时访问和修改。而 StringBuilder 不是线程安全的,适用于单线程环境下的字符串处理,但是相比于 StringBuffer,StringBuilder 具有更高的性能。


因此,当字符串处理需要频繁修改时,建议使用 StringBuffer 或 StringBuilder;而当字符串处理不需要修改时,可以使用 String。

30、MySQL 中的索引是怎么实现的?B+ 树是什么,B 树和 B+ 树的区别,为什么 MySQL 要用 B+ 树?

MySQL 中的索引是通过 B+ 树实现的。B+ 树是一种多叉树,它可以将数据按照一定的顺序组织起来,从而提高查询效率。


B+ 树与 B 树的区别在于,B+ 树的所有数据都存储在叶子节点上,而非叶子节点只存储索引,这样可以提高数据查询效率。B+ 树的叶子节点之间使用指针相连,这样可以实现区间查找,也就是说,可以快速定位某个区间内的数据。

MySQL 之所以采用 B+ 树作为索引的实现方式,主要是因为 B+ 树具有以下优点:

    1、能够支持高效的范围查找和排序。
    2、叶子节点之间使用指针相连,能够支持高效的区间查询。
    3、B+ 树具有较高的数据密度,可以减少磁盘 I/O 次数,提高查询效率。
    4、B+ 树对于插入和删除操作也比较高效。
在 MySQL 中,B+ 树的实现主要是通过 InnoDB 存储引擎来实现的。InnoDB 存储引擎中的索引主要有聚簇索引和辅助索引两种类型,聚簇索引是根据主键创建的索引,而辅助索引是根据非主键列创建的索引。对于辅助索引,MySQL 中会同时创建一个对应的聚簇索引,这样可以提高查询效率。

31、Spring 框架中都用到了哪些设计模式?

Spring 框架中使用了许多设计模式,以下列举一些比较重要的:

    1、单例模式:Spring 的 Bean 默认是单例模式,通过 Spring 容器管理 Bean 的生命周期,保证每个 Bean 只被创建一次,并在整个应用程序中重用。
    2、工厂模式:Spring 使用工厂模式通过 BeanFactory 和 ApplicationContext 创建并管理 Bean 对象。
    3、代理模式:Spring AOP 基于动态代理技术,使用代理模式实现切面编程,提供了对 AOP 编程的支持。
    4、观察者模式:Spring 中的事件机制基于观察者模式,通过 ApplicationEventPublisher 发布事件,由 ApplicationListener 监听事件,实现了对象间的松耦合。
    5、模板方法模式:Spring 中的 JdbcTemplate 使用了模板方法模式,将一些固定的流程封装在父类中,子类只需实现一些抽象方法即可。
    6、策略模式:Spring 中的 HandlerInterceptor 和 HandlerExecutionChain 使用了策略模式,允许开发者自定义处理器拦截器,按照一定顺序执行。
    7、责任链模式:Spring 中的过滤器和拦截器使用了责任链模式,多个过滤器和拦截器按照一定顺序执行,每个过滤器和拦截器可以拦截请求或者响应并做出相应的处理。
总之,Spring 框架中充分利用了许多设计模式,提供了良好的扩展性和灵活性,降低了代码的耦合度,提高了代码的可维护性。

32、MySQL 事务有哪些隔离级别、分别有什么特点,以及 MySQL 的默认隔离级别是什么?

MySQL 事务有四种隔离级别:

    1、读未提交(Read Uncommitted):事务可以读取未提交的数据,可能会读到脏数据,会导致幻读、不可重复读、脏读等问题;
    2、读已提交(Read Committed):只能读取已经提交的数据,可以避免脏读问题,但是可能会遇到不可重复读、幻读问题;
    3、可重复读(Repeatable Read):保证同一个事务中多次读取同一数据的结果是一致的,避免了脏读和不可重复读问题,但是可能会遇到幻读问题;
    4、序列化(Serializable):最高的隔离级别,可以避免所有并发问题,但是并发性能非常低,开销很大。
MySQL 的默认隔离级别是可重复读(Repeatable Read)。

其中,脏读指一个事务读到了另一个事务未提交的数据,不可重复读指同一个事务多次读取同一数据得到不同结果,幻读指同一个事务前后读取的数据集合不一致。

在实际使用中,应该根据具体情况选择合适的隔离级别,权衡数据的一致性和并发性能。

33、Redis 的单线程模型,IO 多路复用是什么?

Redis是一个基于内存的高性能键值数据库,其单线程模型是Redis的核心之一。Redis采用单线程模型来处理客户端请求,即一个Redis进程中只有一个线程来执行所有的请求,所有的请求都按照顺序执行。这种单线程模型的好处是可以避免多线程间的上下文切换,同时避免了多线程带来的竞争条件和死锁等问题,从而提高Redis的性能。

为了提高IO效率,Redis采用了IO多路复用技术。IO多路复用是指通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读或写就绪),能够通知程序进行相应的读写操作。Redis使用了epoll作为IO多路复用的技术实现,在一个线程中维护多个客户端的连接,并通过epoll实现对多个socket的监听和管理。

在Redis中,客户端请求首先被接受并存储在一个队列中,然后由Redis的单个线程从队列中获取请求并处理,处理完成后将响应发送给客户端。当Redis需要从外部读取数据时,采用异步IO方式发送读请求,然后将当前请求挂起,等待数据读取完成后再继续执行。

总的来说,Redis的单线程模型和IO多路复用技术是Redis高性能的关键所在,其通过避免多线程带来的性能损失和采用高效的IO多路复用技术来提高IO效率,从而使Redis在高并发和高负载下表现出色。niz

34、什么是 BIO、NIO、AIO?

BIO、NIO、AIO 是 Java 中常用的 I/O 模型,它们都是针对网络编程中 I/O 操作的不同实现方式。

BIO,全称 Blocking I/O,也称为同步阻塞 I/O。BIO 是最早的 I/O 模型,它是一种阻塞式的 I/O 模型,即当应用程序调用 I/O 操作时,该操作会一直阻塞线程直到操作完成,这会导致 I/O 性能低下。BIO 适用于连接数较小的场景,例如单线程的服务器模型。

NIO,全称 Non-Blocking I/O,也称为同步非阻塞 I/O。NIO 是一种同步非阻塞的 I/O 模型,它的核心是 Selector 和 Channel,利用 Selector 监听多个 Channel 上的事件,当一个 Channel 上的事件到达时,它会被 Selector 转发给注册在这个 Selector 上的其他 Channel,这样可以用一个线程来处理多个请求,提高了 I/O 的效率。NIO 适用于连接数多、连接时间短的场景,例如实时聊天室、在线游戏等。

AIO,全称 Asynchronous I/O,也称为异步非阻塞 I/O。AIO 是 JDK1.7 引入的 I/O 模型,它的特点是异步处理 I/O 操作,当操作完成时会通知应用程序,相比于 NIO 的同步非阻塞 I/O,AIO 无需通过轮询操作完成状态,从而提高了 I/O 的效率。AIO 适用于连接数多、连接时间长的场景,例如 HTTP 长连接、文件操作等。

面试官问到这个问题,可能想了解你对 Java 中不同的 I/O 模型的了解程度,以及你能否根据不同的业务需求选择合适的 I/O 模型。同时,他可能还想了解你在实际开发中使用过哪些 I/O 模型,以及它们的应用场景。

35、意向锁是什么?有什么作用?它是表级锁还是行级锁?

意向锁(Intention Lock)是 InnoDB 存储引擎中的一种锁机制,它是一种表级锁,用于提高事务并发性,减少锁冲突。

意向锁分为意向共享锁(IS)和意向排他锁(IX),它们都是表级锁,IS 锁表示事务想要对表进行读操作,IX 锁则表示事务想要对表进行写操作。当一个事务请求获取某一行的行级锁时,InnoDB 会根据事务所需要的锁类型(共享锁或排它锁),在对应的表上添加意向锁,以告诉其他事务该表已经被锁定了。

使用意向锁,可以提高锁定对象的访问效率,降低锁定冲突率,增加并发度,减少死锁的发生。

在实际的业务场景中,如果多个事务同时请求对同一张表进行操作,使用意向锁可以有效减少锁定冲突,提高事务并发性。面试官可能会想了解你对意向锁的理解以及在实际开发中的应用场景。

36、Spring、SpringMVC、SpringBoot 三者之间是什么关系?

Spring、Spring MVC 和 Spring Boot 是 Spring Framework 的三个重要组成部分,它们之间的关系可以概括为:

1、Spring 是一个开源的轻量级 Java 开发框架,它提供了丰富的基础设施和简化的编程模型,可以使 Java 开发变得更加简单、高效、灵活和可维护。
2、Spring MVC 是 Spring 框架中的一个模块,它是基于 MVC 设计模式的 Web 开发框架,可以帮助开发人员快速构建灵活可扩展的 Web 应用程序。
3、Spring Boot 是 Spring Framework 的另一个重要模块,它是一种快速开发框架,旨在简化 Spring 应用程序的搭建和部署。Spring Boot 提供了一个开箱即用的应用程序开发环境,可以自动配置许多常用的组件和库,从而简化开发过程,减少了配置文件的编写,使得开发人员可以更专注于业务代码的编写。
因此,可以将 Spring 看作是整个框架的核心,Spring MVC 是针对 Web 开发的模块,而 Spring Boot 则是提供快速开发和部署的工具。它们之间是一种嵌套的关系,即 Spring Framework 包含 Spring MVC,而 Spring Boot 又是在 Spring Framework 的基础上构建的。

37、Redis 基础类型中的 String 底层实现是什么?

Redis 中的 String 是一种简单的键值类型,支持存储字符串、整数和浮点数等数据类型。String 的底层实现是基于字节数组的动态字符串(Dynamic String),即 Redis 的 sds 数据结构。

通过采用 sds 数据结构作为 String 的底层实现,Redis 可以实现高效的字符串存储和操作,具有良好的扩展性和性能。

sds(Simple Dynamic String)是 Redis 自己实现的字符串类型,具有如下特点:

1、二进制安全:可以存储任意二进制数据,不会被误解释为字符串。
2、动态扩容:可以动态地扩展字符串的长度,避免了固定长度字符串的空间浪费和长度限制问题。
3、缓存长度:可以缓存字符串的长度信息,避免了频繁计算字符串长度的开销。
sds 的底层实现采用了类似于 C++ 中的 std::string 的方式,即使用一个 struct sds 结构体来表示动态字符串,该结构体包含如下字段:

1、len:表示字符串的长度。
2、free:表示字符串中未使用的空间大小。
3、buf:表示字符串实际存储的空间,即字节数组。

38、有哪些注解可以注入 Bean?@Autowired 和 @Resource 的区别?

在 Spring 框架中,常用的注入 Bean 的注解包括:

1、@Autowired:自动注入,按照类型自动装配,如果有多个同类型的 Bean,则需要通过 @Qualifier 指定具体的 Bean。
2、@Resource:Java 自带的注入方式,按照名称自动装配,默认是按照属性名称进行匹配,如果需要按照 Bean 的名称进行匹配,可以使用 @Resource(name="beanName")。
3、@Inject:和 @Autowired 类似,也是按照类型进行自动装配,但是 @Inject 注解是 JSR-330 提供的,而 5、@Autowired 注解是 Spring 框架提供的。
4、@Value:用于注入配置文件中的属性值,可以指定默认值。
5、@Component:用于声明一个 Bean,作用类似于 XML 中的  标签。

39、请你介绍下 JVM 内存模型,分为哪些区域?各区域的作用是什么?

JVM 内存模型分为以下几个区域:

    1、程序计数器(Program Counter Register):每个线程都有自己的程序计数器,用于指示当前线程执行的字节码指令的行号,以便线程执行时能够回到正确的位置。
    2、虚拟机栈(JVM Stack):也称为 Java 方法栈,用于存储方法执行时的局部变量表、操作数栈、动态链接、方法出口等信息。每个线程在执行一个方法时,都会为该方法分配一个栈帧,并将该栈帧压入虚拟机栈,当方法执行完毕后,虚拟机会将其出栈。
    3、本地方法栈(Native Method Stack):与虚拟机栈类似,用于存储本地方法的执行信息。
    4、堆(Heap):用于存储对象实例,是 JVM 中最大的一块内存区域。堆是被所有线程共享的,当创建一个新对象时,对象实例存储在堆中,堆中存储的对象实例都有一个标记用于标记对象是否存活。垃圾回收器会周期性地回收那些没有被标记为存活的对象。
    5、方法区(Method Area):用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也是被所有线程共享的。
    6、运行时常量池(Runtime Constant Pool):是方法区的一部分,用于存储编译期间生成的各种字面量和符号引用,这些内容在类加载后进入常量池中。
    其中,程序计数器、虚拟机栈、本地方法栈是线程私有的,堆、方法区、运行时常量池是线程共享的。

40、Linux 中的硬链接和软连接是什么,二者有什么区别?

在 Linux 文件系统中,硬链接(hard link)和软链接(symbolic link)都是一种文件链接的方式,可以用于将一个文件链接到另一个文件上。它们的主要区别在于创建方式、所占空间和使用限制等方面。

硬链接是通过在文件系统中创建一个新的目录项(directory entry)指向同一文件 inode 的位置来实现的。因为硬链接实际上是指向同一 inode,所以如果原文件被删除,硬链接依然能够访问到原文件的内容。硬链接的使用范围比较受限,因为硬链接只能指向同一个文件系统内的文件,不能跨文件系统创建。

软链接是通过在文件系统中创建一个新的文件来实现的,该文件中包含指向另一个文件的路径。软链接可以跨文件系统创建,并且可以指向任何类型的文件。但是,当原文件被删除时,软链接将会失效。

总的来说,硬链接更加高效,因为它只是添加了一个新的目录项,所以对磁盘空间的消耗比软链接要小。但是,硬链接不能跨文件系统,所以在实际应用中需要根据具体的需求来选择使用哪种链接方式。

41、给出IPv4地址求出兼容的IPv6地址

135.75.43.52 按十六进制算出即87.4B.2B.34,

而87.4B.2B.34串地址一组还是8位,所以需要两组v4地址合成v6地址,

再把前96位补零,它可以被转化为

0000:0000:0000:0000:0000:0000:874B:2B34或者::874B:2B34。

以小数点为划分,每个除以10,结果是第一位然后余数是第二位【135/16=8余7】,前两个小数点数把中间小数点去删掉,最前面补::

42、给出子网掩码,求最大可以连接多少台主机

先将子网掩码转换成二进制,如:255:255:240:0转换成二进制为 11111111:11111111:11110000:00000000 然后计算有多少个0,上面有12个零,然后因为有两台主机需要分配给网络地址、广播地址 所以 最大连接主机为 2的12次方-2=4094台

43、给出非连通无向图的边数,求至少需要多少个顶点

公式:先看有多少条边然后利用公式连通图为:n(n-1)/2 = 边数点  非连通则需多加一个点 n+1 顶点

44、强引用,软引用,弱引用和虚引用的区别与用法

1.强引用
特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

应用场景:项目中到处都是。

2.软引用
特点:软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。

应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

具体实例:图片缓存框架中,“内存缓存”中的图片是以这种引用来保存,使得JVM在发生OOM之前,可以回收这部分缓存。

3.弱引用
特点:弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。【弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。】

应用场景:弱应用同样可用于内存敏感的缓存。

具体实例:在静态内部类中,经常会使用虚引用。例如,一个类发送网络请求,承担callback的静态内部类,则常以虚引用的方式来保存外部类(宿主类)的引用,当外部类需要被JVM回收时,不会因为网络请求没有及时回来,导致外部类不能被回收,引起内存泄漏。

4.虚引用
特点:虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

看到一个比较有意思的举例:

    1、强引用就像大老婆,关系很稳固。
    2、软引用就像二老婆,随时有失宠的可能,但也有扶正的可能。
    3、弱引用就像情人,关系不稳定,可能跟别人跑了。
    4、幻像引用就是梦中情人,只在梦里出现过。
    
    参考文章:https://blog.csdn.net/m0_45406092/article/details/108576301
    
    


45、java弱引用能转换为强引用吗?

弱引用可以被转化为强引用

import java.lang.ref.WeakReference;

public class WeakReferenceTest {

    public static void main(String[] args) throws InterruptedException {
        WeakReference<String> sr = new WeakReference<String>(new String("hello"));

        System.out.println(sr.get());
        String abc = sr.get(); // 被改为强引用
        System.gc(); // 通知JVM的gc进行垃圾回收

        Thread.sleep(2000); // gc可能存在延迟,确保下次打印之前一定先执行gc
        System.out.println(sr.get());
    }
}

46、MySQL的关键字执行顺序

from => on => joinleft joinright join、…)=> where => group by => having =>select=>distinct=>unionunion all=>order by=>limit

47、synchronized是否具有传递性(父类方法加了synchronized,子类方法是否也继承)

synchronized关键字不具备继承性,父类定义了方法A用synchronized修饰,子类继承父类,同样调用方法A,此时子类调用A是不具备同步功能的,需要添加synchronized关键字。主要是因为:synchronized非函数签名,因此无法被继承,所以无法保证子类调用同步.


1、synchronized修饰实例方法,这样的含义是锁住当前实例对象
2、synchronized修饰静态方法,这样的含义是锁住当前类对象
3、synchronized锁住代码块,对指定的对象加锁
4、synchronized是可以重入锁,无法被中断,而且在代码块中执行wait notify notifyAll,必须是锁住的对象才可以
5、synchronized不具备继承特性

48、最长连续序列

题目:给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 :

输入:nums = [100,4,200,1,3,2]

输出:4

解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

分析:这道题要求求最长连续序列,并给定了O(n)复杂度限制,我们的思路是,使用一个集合HashSet存入所有的数字,然后遍历数组中的每个数字,如果其在集合中存在,那么将其移除,然后分别用两个变量pre和next算出其前一个数跟后一个数,然后在集合中循环查找,如果pre在集合中,那么将pre移除集合,然后pre再自减1,直至pre不在集合之中,对next采用同样的方法,那么next-pre-1就是当前数字的最长连续序列,更新res即可。这里再说下,为啥当检测某数字在集合中存在当时候,都要移除数字。这是为了避免大量的重复计算,就拿题目中的例子来说吧,我们在遍历到4的时候,会向下遍历3,2,1,如果都不移除数字的话,遍历到1的时候,还会遍历2,3,4。同样,遍历到3的时候,向上遍历4,向下遍历2,1,等等等。如果数组中有大量的连续数字的话,那么就有大量的重复计算,十分的不高效,所以我们要从HashSet中移除数字

49、有一个文件里面存储了全国人口的年龄,年龄之间用逗号分隔,需要统计每个年龄的人数,年龄范围0~110 (没什么难度主要就是考验一下变成基本功,看看没有ide的情况能否写出来,使用io记得捕捉异常即可)

import java.util.*;

public class Test {
    public static Object count(String filePath) {
        try (BufferedReader br = new BufferedReader(new FileReader(new File(filePath)))) {
            String info = br.readLine();
            String[] ages = info.split(",");
            
            long[] result = new long[111];
            
            for (String age : ages) {
                int index = Integer.parseInt(age);
                result[index] = result[index]++;
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }
}

50、如何使用 Redis 实现一个排行榜?

Redis实现排行榜是Redis中一个很常见的场景,主要使用的是ZSet进行实现,下面是为什么选用ZSet:

有序性:排行榜肯定需要实现一个排序的功能,在Redis中有序的数据结构有List和ZSet;
支持分数操作:ZSet可以对集合中的元素进行增删改查操作,十分贴合排行榜中用户分数动态变化的场景,而List并不能针对分数进行操作,只有其中的value进行操作;
支持范围查询:ZSet可以按照分数进行范围查询,如排行榜中的Top10需求就可通过该特性进行实现;
支持去重:由于ZSet属于Set的特殊数据结构,因此同样拥有Set不可重复的特性,对于排行榜中不可出现重复项的需求也十分贴合,而List只能手动去重。
因此选择ZSet实现排行榜相对于List实现会更合适和高效。
# 添加示例数据
ZADD scores 90 "张三"
ZADD scores 85 "李四"
ZADD scores 95 "王五"
ZADD scores 92 "赵六"
# 查询排名前3的学生信息
ZRANGE scores 0 2 WITHSCORES
# 查询排名前3的打印
1) "王五"
2) "95"
3) "赵六"
4) "92"
5) "张三"
6) "90"
# 删除学生“李四”的成绩信息
ZREM scores "李四"
// 添加学生成绩
public void addScore(String name, int score) {
    redisTemplate.opsForZSet().add("scores", name, score);
}

// 查询排名前N的学生成绩
public List<Map.Entry<String, Double>> getTopScores(int n) {
    return redisTemplate.opsForZSet().reverseRangeWithScores("scores", 0, n - 1)
            .stream()
            .map(tuple -> new AbstractMap.SimpleEntry<>(tuple.getValue(), tuple.getScore()))
            .collect(Collectors.toList());
}

// 删除某个学生的成绩
public void removeScore(String name) {
    redisTemplate.opsForZSet().remove("scores", name);
}

51、什么是网关,网关有哪些作用?

网关(Gateway)是连接两个或多个不同网络的设备,可以实现协议的转换、数据的转发和安全策略的实现等功能。简单来说,网关是设备与路由器之间的桥梁,由它将不同的网络间进行访问的控制,转换,交接等等。

常见的网关有应用网关、协议网关、安全网关等。

网关的作用如下:

实现协议的转换:不同网络之间通常使用不同的协议,通过网关可以实现协议的转换,使得不同网络之间能够相互通信。
    1、提供数据转发功能:网关可以对传输的数据进行过滤、路由、转发等处理,确保数据的可靠传输。
    2、实现安全策略:网关可以对传输的数据进行加密、认证、授权等操作,保证数据的安全性和可靠性。
    3、提供缓存功能:网关可以将一部分数据缓存起来,提高数据的访问速度和响应性能。
    4、支持负载均衡:网关可以将请求分配到不同的服务器上,实现负载均衡,提高系统的可用性和性能。
    5、实现访问控制:网关可以对访问进行控制,防止未授权的访问和攻击,提高系统的安全性

52、线程的生命周期是什么,线程有几种状态,什么是上下文切换?

1、六种状态
Java中有六种状态:新建状态(New)、就绪状态(Runnable)、阻塞状态(Blocked)、等待状态(Waiting)、超时等待(Timed_Waiting)、终止状态(Terminated)

    1、NEW:初始状态,线程被创建出来但没有被调⽤ start() 。
    2、RUNNABLE:运⾏状态,线程被调⽤了 start() 等待运⾏的状态。
    3、BLOCKED:阻塞状态,需要等待锁释放。
    4、WAITING:等待状态,表示该线程需要等待其他线程做出⼀些特定动作(通知或中断)。
    5、TIME_WAITING:超时等待状态,可以在指定的时间后⾃⾏返回⽽不是像 WAITING 那样⼀直等待。
    6、TERMINATED:终⽌状态,表示该线程已经运⾏完毕。
2、五种状态

从操作系统层面的划分,线程有五种状态:新建、就绪、运行、阻塞和死亡状态。

    1、新建状态(New) : 新创建了一个线程对象
    2、就绪状态(runnable) : 线程对象创建后,其他线程调用了该对象的 start 方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
    3、运行状态(Running) : 就绪状态的线程获取了 CPU,执行程序代码
    4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
    5、死亡状态(Dead) :线程执行完了或者因异常退出了 run 方法,该线程结束生命周期。
3、阻塞情况又分为三种:

    1、等待阻塞:运行的线程执行 wait 方法,该线程会释放占用的所有资源,JVM会把该线程放入 "等待池"中,进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify 或者 notifyAll 方法才能被唤醒,wait 是object 类的方法。
    2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入 "锁池"中。
    3、其他阻塞:运行的线程执行 sleep 或者 join 方法,或者发出了 I/O请求时,JVM 会把该线程设置为阻塞状态。当 sleep 状态超时、join 等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入就绪状态。 sleep 是 Thread 类

3、上下文切换

    线程的状态变化通常是由操作系统进行管理和控制的,当线程状态发生变化时,需要进行上下文切换。
    
    上下文切换是指将当前线程的状态保存下来,并将CPU资源切换到另一个线程上运行的过程。上下文切换需要花费一定的时间和系统资源,因此,线程的上下文切换次数要尽量减少,以提高系统的性能。

53、MVCC 是什么?InnoDB 是如何实现 MVCC 机制的?

MVCC机制

我们先设定一个场景:

假设数据库表中存在一条记录row_old,这时事务A和事务B同时begin,事务A将该记录修改为了row_new,事务B读取行记录,事务A提交,事务B再次读取这条行记录。

本文中将使用该场景来分析“脏读”和“不可重复读”现象。

若事务B在A提交前读到row_new,即出现“脏读”现象;若事务B在A提交后读到row_new,即出现“不可重复读”现象。

但是,正常情况是,无论事务A是否提交,事务B读取该条记录,都只能读出row_old。

什么方法可以达到这种效果呢?可以很直观地想到,将事务A修改后的版本存起来。那么又有一系列问题,如何存,用什么结构来存?版本链便是为此而引入的。

版本链

版本链,实际上就是一条存储多个版本行记录的链表。数据库中的每一行数据都对应一个版本链。链表中每一个结点代表一个行记录。行记录中有两个重要的隐藏字段:

trx_id:记录修改成当前版本的事务编号;

roll_pointer:指向上一个版本的指针,即回滚指针。

版本链的最底层即为数据表中最原始的行记录,上层存储各个事务修改后的行记录,逐个用回滚指针相连接


还有一个问题,版本链是存储在哪的?没错,我们熟悉的undo log回滚日志就是用来存储版本链的 。

一致性视图

如果当前事务修改一条记录,这条更新过的记录被记录到版本链中,对于当前事务而言,由于自身事务id和版本链中最新一条行记录的trx_id相匹配,所以可以将其读取出来。但是对于其它事务而言,是不希望能读出这条记录的,而是希望它能顺着版本链,找出自己需要的版本的行记录。

那么如何找到正确的版本?这里涉及到一个快照机制。事务在执行select语句时,会生成一个一致性视图:read-view,相当于一个快照,记录正在活跃的事务的编号。

read-view里面包含一个数组,m_ids,该数组记录(产生快照的这一时刻)版本链中未提交的每个版本的trx_id组成的序列。同时,read-view还会记录一个最大已创建事务id,即 max_id,以及数组中最小id即 min_id。查询版本链时,会将行记录中的trx_id与read-view中的max_id、min_id、m_ids[]等进行比对。依据如下版本比对规则来进行比对。

版本链比对规则

如果trx_id小于min_id,说明该版本是已提交事务生成的,数据可见;

如果trx_id大于max_id,说明该版本是将来启动的事务生成的,数据不可见;

如果min_id<=trx_id<=max_id,就包括两种情况:

trx_id在m_ids数组中:表示这个版本是未提交事务生成的,数据不可见,本事务可见;

trx_id不在m_ids数组中:表示这个版本是已提交事务生成的,数据可见。

补充:删除的原理:

删除可以认为是update的特殊情况。假如要删除一行记录,会将版本链上最新一条记录复制一份,将行格式头信息中(record header)里面的(deleted flag)标志位置为true,表示当前记录已被删除。若顺着版本链访问到这条记录,(deleted flag)标志位为true,表示记录已删除,不返回数据。

54、Redis 的持久化机制有哪些?说说各自的优缺点和应用场景?

Redis 的持久化机制主要分为 RDB 和 AOF 两种。

RDB 持久化机制

RDB 持久化机制是指将 Redis 在内存中的数据以快照的形式写入磁盘中,可以手动或自动执行快照操作,将数据集的状态保存到一个 RDB 文件中。RDB 机制的优点在于:

    1、RDB 机制适合在数据集比较大时进行备份操作,因为它可以生成一个非常紧凑、经过压缩的数据文件,对于备份、恢复、迁移数据都很方便。
    2、RDB 机制在 Redis 重启时比 AOF 机制更快地将 Redis 恢复到内存中。
RDB 机制的缺点在于:

    1、RDB 机制可能会出现数据丢失,因为数据是周期性地进行备份,一旦 Redis 出现问题并且上一次备份之后还没有进行过数据变更,那么这部分数据将会丢失。
    2、RDB 机制会造成一定的 IO 压力,当数据集比较大时,进行备份操作可能会阻塞 Redis 服务器进程。
    AOF 持久化机制

AOF 持久化机制是指将 Redis 在内存中的操作命令以追加的形式写入到磁盘中的 AOF 文件,AOF 文件记录了 Redis 在内存中的操作过程,只要在 Redis 重启后重新执行 AOF 文件中的操作命令即可将数据恢复到内存中。AOF 机制的优点在于:

    1、AOF 机制比 RDB 机制更加可靠,因为 AOF 文件记录了 Redis 执行的所有操作命令,可以确保数据不丢失。
    2、AOF 机制在恢复大数据集时更加稳健,因为 AOF 文件记录了数据的操作过程,可以确保每一次操作都被正确地执行。
AOF 机制的缺点在于:

    1、AOF 机制生成的 AOF 文件比 RDB 文件更大,当数据集比较大时,AOF 文件会比 RDB 文件占用更多的磁盘空间。
    2、AOF 机制对于数据恢复的时间比 RDB 机制更加耗时,因为要重新执行 AOF 文件中的所有操作命令。
综上所述,RDB 适合用于数据集较大、备份、恢复数据和迁移数据等场景,AOF 适合用于数据可靠性要求高、数据恢复稳健等场景。

55、Ngnix 是什么?它有哪些应用场景?

Nginx(发音为“engine-x”)是一个高性能的开源Web服务器和反向代理服务器,可以处理大量的并发连接和请求。它使用事件驱动的异步架构和多线程设计,可以高效地处理并发请求,同时也支持反向代理、负载均衡、动态HTTP缓存、SSL/TLS终止、基于模块的扩展等功能。

Nginx 的应用场景非常广泛,以下是其中的几个:

    1、Web 服务器:Nginx 可以作为 HTTP 服务器,处理并发的静态请求和动态请求,并且可以支持负载均衡和缓存,为网站提供高性能和高可用性。
    2、反向代理服务器:Nginx 可以作为反向代理服务器,接收客户端请求并将其转发到后端服务器,同时也可以支持负载均衡和缓存。
    3、邮件代理服务器:Nginx 可以作为邮件代理服务器,支持 POP3、IMAP 和 SMTP 协议,并且可以支持 SSL/TLS 加密和反垃圾邮件功能。
    4、流媒体服务器:Nginx 可以作为流媒体服务器,支持 RTMP、HLS 和 DASH 协议,可以用于实现直播、点播和视频-on-demand(VoD)等场景。
总之,Nginx 具有高性能、可扩展性和可靠性等特点,被广泛应用于大型网站、互联网公司、云计算、视频流媒体、游戏等领域。

56、线性表的顺序存储结构是一种() 的存储结构,线性表的链式存储结构是一种顺序存取的存储结构。

随机存取  【存储跟存取不一样  存取是随机的 比如a[7][5]】

57、哪些Java中的流对象是字节流?

stream结尾都是字节流,reader和writer结尾都是字符流 两者的区别就是读写的时候一个是按字节读写,一个是按字符。 实际使用通常差不多。 在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。 只是读写文件,和文件内容无关的,一般选择字节流。

58、局部内部类

1、内部类不能被public、private、static修饰;

2、在外部类中不能创建内部类的实例;

3、创建内部类的实例只能在包含他的方法中;

4、内部类访问包含他的方法中的变量必须有final修饰;

5、外部类不能访问局部内部类,只能在方法体中访问局部内部类,且访问必须在内部类定义之后。

59、有一个用数组C[1…m]表示的环形队列,m为数组的长度。假设f为队头元素在数组中的位置,r为队尾元素的后一位置(按顺时针方向)。若队列非空,则计算队列中元素个数的公式应为?

分情况讨论:
1. 若f

60、创建线程的方法

1.继承Thread类,重载run方法;
2.实现Runnable接口,实现run方法 

61、表达式(short)10/10.2*2运算后结果类型是

	首先,要注意是(short)10/10.2*2,而不是(short) (10/10.2*2),前者只是把10强转为short,又由于式子中存在浮点数,所以会对结果值进行一个自动类型的提升,浮点数默认为double,所以答案是double;后者是把计算完之后值强转short。

62、Dubbo 是什么?是否了解过它的架构设计?

Dubbo是一个高性能、轻量级的开源Java RPC框架,可以用于构建分布式服务应用。

Dubbo的架构设计如下:

服务提供方:暴露服务的服务提供方。	
服务消费方:调用远程服务的服务消费方。
注册中心:服务注册与发现的中心。
监控中心:统计服务的调用次数和调用时间的监控中心。
配置中心:服务配置的中心化管理。
Dubbo的设计理念是面向接口代理的RPC框架。Dubbo的服务提供方将服务暴露在注册中心上,服务消费方通过注册中心发现服务并调用远程服务,Dubbo会自动将底层的通信细节和序列化过程封装起来,使得服务提供方和服务消费方可以像调用本地方法一样调用远程服务。Dubbo支持多种协议和序列化方式,并且提供了多种负载均衡和容错机制,具有很高的可扩展性和可定制性。

Dubbo的优点包括:
    1、高性能:Dubbo采用了多种优化手段,如线程池复用、请求响应式编程、异步执行等,以提高系统的并发能力和吞吐量。
    2、高可靠性:Dubbo提供了多种容错机制,如重试、熔断、降级等,以保证服务的可用性。
    3、易于扩展:Dubbo的设计理念是面向接口代理的RPC框架,支持多种协议和序列化方式,以满足不同的应用场景需求。
    4、易于集成:Dubbo提供了与Spring Framework、Spring Boot等常见开发框架的无缝集成,以方便开发者使用。
    Dubbo的应用场景包括:微服务架构、SOA架构、分布式系统、RPC调用等。

63、synchronized 关键字是什么,有什么作用?

synchronized 是 Java 中的一个关键字,用于实现线程同步。具体来说,synchronized 用于修饰方法或代码块,使得同一时刻只能有一个线程访问被修饰的代码,其他线程需要等待当前线程执行完毕后才能访问。

synchronized 主要用于解决多线程并发访问共享资源时出现的线程安全问题。如果多个线程同时访问一个共享资源,就会出现多个线程同时修改这个资源的情况,从而导致数据不一致等问题。而使用 synchronized 可以保证同一时刻只有一个线程访问该资源,从而避免了线程安全问题。

synchronized 的作用不仅限于线程同步,它还可以保证可见性和有序性,即保证在同一个锁上,一个线程修改了共享变量的值之后,另一个线程能够立即看到修改后的值,并且在多个线程执行顺序上保证了一致性。

需要注意的是,使用 synchronized 会带来一定的性能损失,因为每次进入同步块时都需要获得锁,这会增加线程的等待时间和上下文切换的开销。同时,如果同步块的代码执行时间很短,也会增加不必要的性能开销。因此,需要根据具体情况来判断是否需要使用 synchronized。

64、如何设计一个点赞系统?

设计一个点赞系统可以分为以下几个步骤:

    1、确定需求:需要明确点赞的对象是什么,是否需要计数等信息,同时需要考虑点赞的业务场景,如用户点赞、文章点赞等。
    2、数据库设计:需要设计点赞相关的数据表,可以包含点赞者 ID、被点赞对象 ID、点赞时间等字段。
    3、接口设计:需要设计点赞相关的接口,包括点赞、取消点赞、查询点赞数等操作。
    4、业务逻辑实现:在接口中实现点赞相关的业务逻辑,包括判断点赞状态、更新点赞数、更新点赞状态等操作。
    5、安全性考虑:需要考虑并发访问的情况,可以使用分布式锁来保证数据一致性和安全性。
    6、性能优化:如果点赞系统的访问量很高,可以使用缓存来提高性能,比如使用 Redis 来缓存点赞数等信息。
    7、监控和日志:需要对点赞系统进行监控和日志记录,以便及时发现和排查问题。
    8、定时任务设计:如何将缓存中的数据定时写入数据库中,避免因为缓存丢失而导致数据不一致的问题。
总之,设计一个点赞系统需要综合考虑需求、数据库设计、接口设计、业务逻辑实现、安全性、性能优化等方面,同时需要不断优化和完善。

65、各个jvm的垃圾回收方式采用的算法回收



两个最基本的java回收算法:复制算法和标记清理算法

1、复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
2、标记清理:一块区域,标记可达对象(可达性分析),然后回收不可达对象,会出现碎片,那么引出标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
两个概念:新生代和年老代

新生代:初始对象,生命周期短的
永久代:长时间存在的对象

整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收。

1、Serial New收集器是针对新生代的收集器,采用的是复制算法
2、Parallel New(并行)收集器,新生代采用复制算法,老年代采用标记整理
3、Parallel Scavenge(并行)收集器,针对新生代,采用复制收集算法
4、Serial Old(串行)收集器,新生代采用复制,老年代采用标记整理
5、Parallel Old(并行)收集器,针对老年代,标记整理
6、CMS收集器,基于标记清理
7、G1收集器:G1收集器从整体上来看是基于标记整理的,局部是采用复制算法实现的。

总结:新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理

65、设散列表中有 m 个存储单元,散列函数 H(key)= key % p ,则 p 最好选择

小于等于m的最大素数

66、查找哈希表,解决冲突的方法

1、链地址法:将哈希值相同的元素用链表进行相连 
2、线性探测再散列法:冲突后依次向下循环查找空位进行放置 BC为哈希值构造方法,并非解决冲突方法

67、哈希表的平均查找长度与冲突处理方法和装填因子有关,但与哈希表长无关

68、什么是内部类? 内部类的分类有哪些 ?内部类的优点 ,内部类有哪些应用场景?

内部类是指在一个类的内部定义的另一个类,内部类可以直接访问外部类的成员,包括私有成员。

内部类可分为四种类型:成员内部类、静态内部类、局部内部类和匿名内部类。

成员内部类是最普通的内部类,它是外部类的成员之一,可以访问外部类的所有成员和方法,包括私有的。
静态内部类是指被 static 修饰的内部类,它与外部类的关系并不密切,因此访问外部类的成员和方法时,需要使用外部类的对象来访问。
局部内部类是定义在方法或代码块内部的类,它的作用域仅限于方法或代码块内部,外部无法访问。
匿名内部类是没有类名的内部类,它一般用于创建实现某个接口或继承某个类的对象。
内部类的优点主要有以下几点:

    1、内部类可以访问外部类的所有成员,包括私有成员,增加了程序的灵活性和可读性。
    2、内部类可以实现多继承的效果,一个类可以实现多个接口。
    3、内部类可以隐藏实现细节,使程序更加安全。
    4、内部类可以让代码更加简洁,使得逻辑更加清晰。
内部类有很多应用场景,比如:

    1、实现事件监听器:事件监听器常常使用内部类来实现,以便于访问外部类的成员变量和方法。
    2、实现迭代器:内部类可以访问外部类的私有变量,因此可以用来实现迭代器。
    3、实现工厂模式:内部类可以访问外部类的私有成员,可以用来实现工厂模式。
    4、实现单例模式:内部类可以实现单例模式,因为内部类的静态成员只有在第一次被访问时才会被加载。
// 1、私有内部类 => 在外部类中编写方法,对外提供内部类对象
// 定义方法(外部类中)
public Inner getInstance(){
	return new Inner();
}
// 使用方法
Outer o = new Outer();
Object i = o.getInstance();

// 2. 非私有内部类 => 直接创建成员内部类
// 外部类名.内部类名 对象名 = new 外部类对象.new 内部类对象;
Outer.Inner oi = new Outer().new Inner();
分类 定义 作用范围 应用
成员内部类 与其外部类的其他他成员变量和方法平级的内部类 可以访问外部类的所有成员变量和方法 可以被外部类访问 为外部类提供服务的场景
静态内部类 static 修饰的内部类 只能访问外部类的静态成员变量和方法 只能被外部类的静态方法访问 与外部无关或者需要单例模式的场景
局部内部类 定义在方法中的内部类 在方法中直接实例化 可以访问外部类或代码块中声明为 final 的变量 作用域仅限于方法内 不能使用访问控制符修饰 执行特定任务或逻辑操作场景
匿名内部类 没有名字的内部类 使用 new接口 抽象类(){}new 父类(){} 没有名字、没有构造器、只能使用一次 必须继承一个父类型(接口或抽象类)或实现一个接口的局部内部类 创建回调函数、事件监听器、线程等临时性对象

69、覆盖索引和联合索引是什么?讲一下索引的最左前缀匹配原则。

   覆盖索引和联合索引是什么?

    1、覆盖索引指的是在查询语句中只需要使用到了索引列,而不需要回表查询数据页,即可以直接从索引中获取需要的数据,避免了回表操作,提高了查询效率。
    2、联合索引指的是在一个索引中包含多个列,可以同时按照多个列进行排序,减少了排序时的磁盘 I/O 操作和排序时间,提高了查询效率。
   最左前缀匹配原则

		最左前缀匹配原则是指在使用联合索引时,可以按照索引中的列顺序进行查询,但只能使用最左侧的列或列的组合进行查询,不能跳过中间的列进行查询。

例如,如果索引包含 A、B、C 三列,那么可以使用 A、A+B、A+B+C 进行查询,但不能使用 B、B+C 进行查询。

最左前缀匹配原则是因为联合索引是以多列为键值建立的,而每个键值都是由多个列组成的,因此查询时只能使用最左侧的列或列的组合进行查询。这种原则提高了查询效率,避免了无效的索引扫描,但也需要根据实际情况合理地设计索引,以满足查询需求。

70、Spring 如何处理线程并发问题,ThreadLocal 你了解过吗?

Spring 中处理线程并发问题的主要方式是使用线程安全的对象和并发包中提供的类来避免线程安全问题。

例如,Spring 中的单例 Bean 是线程安全的,因为 Spring 容器在创建单例 Bean 时会确保只有一个实例存在。Spring 还提供了对多线程的支持,例如使用 @Async 注解实现异步方法调用等。

ThreadLocal 你了解过吗?

    1、ThreadLocal 是 Java 中的一个线程局部变量,它可以为每个线程提供一个独立的变量副本,避免了多线程之间的数据共享和数据竞争问题。
    2、ThreadLocal 可以在每个线程中存储一个对象,并且每个线程只能访问自己的对象,而不会影响其他线程中的对象。
    3、ThreadLocal 主要用于解决线程安全问题,例如在 Web 应用中,可以使用 ThreadLocal 存储用户的会话信息,这样每个线程就可以独立地访问自己的会话信息,避免了多个线程之间的数据共享和数据竞争问题。
    4、在 Spring 中,ThreadLocal 通常用于存储和传递一些与线程相关的上下文信息,例如当前登录用户的信息、请求的 IP 地址等。可以将 ThreadLocal 对象定义为一个 Bean,然后在需要使用时注入到其他 Bean 中使用。
    5、Spring 还提供了一些与 ThreadLocal 相关的类和工具,例如 SimpleThreadScope,用于实现线程范围内的 Bean,以及 TaskExecutor,用于在多线程环境中执行任务。

71、设循环队列的容量为50(序号从0到49),现经过一系列的入队和出队运算后,有 front=16,rear=5(rear指向队尾元素的后一位置),当前循环队列中元素个数为

求循环队列中的元素个数的公式为:(rear-front+size)%size,size是循环队列的容量

72、一个 Queue 类型的队列 Q ,判断队列满的条件应是哪一个语句

Q.front==(Q.rear+1)%Maxsize;

73、什么是 MySQL 执行计划?如何获取执行计划并对其进行分析?

MySQL 执行计划是指 MySQL 查询优化器生成的一份详细的查询执行计划,它展示了 MySQL 在执行查询时所采取的具体执行计划,包括表的访问顺序、数据读取方式、使用的索引、使用的排序方式等等。通过分析执行计划,可以帮助我们找出查询性能瓶颈所在,进而进行优化,提高查询效率。

要获取执行计划,可以在执行 SQL 语句时在前面添加 explain 关键字,例如:

explain select * from table where id = 1;
这样,MySQL 会输出该查询语句的执行计划。 执行计划中的各个字段含义如下:

    1、id:每个 Select 子句或者是一个操作符或者是一个查询语句【id不同,则越大越先执行,id相同则按照从上到下执行】。
    2、select_type:查询类型,表示查询的类型(简单查询、联合查询、子查询等等)【simple代表普通查询,primary代表主查询,即外部的查询,subquery代表子查询,union代表union之后的第二个往后的查询】。
    3、table:查询涉及的表。
    4、partitions:匹配的分区。
    5、type:访问类型,表示 MySQL 在表中找到所需行的方式【性能由高到低分别是NULL,system,const,eq_ref,ref,range,index,all】。
    6、possible_keys:表示查询可能使用到的索引。
    7、key:实际使用到的索引。
    8、key_len:使用的索引长度。
    9、ref:列与索引的比较。
    10、rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
    11、filtered:返回结果的行数占总行数的比例。
    12、Extra:包含 MySQL 解决查询的详细信息。
分析执行计划时,需要注意以下几个方面:

    1、扫描行数:rows 字段,表示查询所需扫描的行数,如果该值过大,说明查询效率不高,需要优化。
    2、使用索引:key 字段,表示查询所使用的索引,如果没有使用索引或者使用的不是最优索引,需要考虑优化。
    3、排序:Extra 字段,如果查询需要使用 filesort 排序,说明查询效率不高,需要优化。
    4、嵌套循环:如果查询类型是 nested loop,说明查询中包含嵌套循环,也需要考虑优化。
    5、通过分析执行计划,可以确定查询优化的方向和方法,提高查询效率。
    【我们可以着重对key,type,extra进行分析,判断他有没有走索引,走的是哪一个索引,是否创建了中间表等等,然后具体sql具体修改】

74、什么是单例模式?使用单例模式有什么好处?有哪些常用的单例模式实现方式?各自的应用场景是什么?请你举例说明哪些地方用到了单例模式?

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。单例模式的目的是确保类的一个唯一实例,因此其他类可以轻松地从一个可知的地方访问它。

使用单例模式的好处包括:

    1、管理资源:对于一些需要管理共享资源的类,通过单例模式可以避免重复创建实例,节约资源。
    2、简化调用:由于单例模式只有一个实例,因此可以直接通过全局访问点调用方法,简化调用过程。
    3、控制实例数量:在某些场景下,需要控制一个类的实例数量,单例模式可以很好地实现这个需求。

常用的单例模式实现方式包括:
    1、饿汉式单例模式:在类加载时创建单例对象。缺点是不支持延迟加载。
    2、懒汉式单例模式:在第一次使用时才创建单例对象。缺点是需要考虑线程安全问题【可以通过加锁实现线程安全,但会影响性能】。
    3、双重检查锁单例模式:在第一次使用时创建单例对象,并使用双重检查锁定来确保线程安全。
    4、枚举单例模式:在枚举类型中创建单例对象,可以防止反射和序列化攻击【利用 Java 的类加载机制实现线程安全且懒加载的单例模式】。

各自的应用场景如下:

    1、饿汉式:适用于实例创建简单、初始化消耗小的场景。
    2、懒汉式:适用于实例创建比较复杂、初始化消耗大的场景,并且对于并发要求不高的情况下可以选择不加锁。
    3、双重检查锁式:适用于实例创建比较复杂、初始化消耗大的场景,并且对于并发要求较高的情况下可以选择使用。
    4、静态内部类式:适用于实例创建比较复杂、初始化消耗大的场景,并且对于并发要求较高的情况下可以选择使用。
以下是一些应用场景的示例:
    1、数据库连接池:通过单例模式,可以确保系统中只有一个数据库连接池。
    2、日志记录器:可以使用单例模式记录系统日志,这样可以确保系统中只有一个日志记录器。
    3、配置文件管理器:可以使用单例模式来管理应用程序的配置文件,这样可以避免重复读取配置文件的开销。
    4、线程池:可以使用单例模式来确保系统中只有一个线程池。
    5、一个例子是 Spring 框架中的 ApplicationContext,它是一个全局访问点,提供了一个管理 Bean 的中央注册表。由于 Spring 中的 Bean 只需创建一次,因此 ApplicationContext 使用单例模式确保只有一个实例。

75、什么是云原生?它有哪些优缺点?

云原生是一种开发和运行应用程序的方法,旨在利用云计算的弹性、可扩展性、可靠性和高可用性等优势。它通过将应用程序打包到容器中,使用容器编排工具进行管理,实现了应用程序在不同环境中的快速部署、弹性伸缩和高可用性。

云原生的优点包括:

    1、灵活性和可扩展性:容器可以快速部署和扩展,以满足不同的负载要求。
    2、高可用性:容器编排工具可以自动检测和恢复容器故障,提供高可用性。
    3、效率和成本优化:使用容器可以节省资源和成本,提高应用程序的运行效率。
    4、安全性:容器隔离应用程序的运行环境,减少了安全漏洞的风险。
云原生的缺点包括:

    1、学习曲线较陡峭:云原生技术较为复杂,需要学习一些新的技术和工具。
    2、可能存在依赖问题:应用程序可能依赖于某些特定的云原生技术或工具,这可能导致一些限制或局限性。
    3、管理和维护难度:容器编排工具可能需要额外的管理和维护,需要更多的操作和维护成本。
云原生的应用场景包括:

    1、微服务架构:云原生技术非常适合构建微服务架构,将应用程序拆分为小型、自治的服务。
    2、弹性伸缩:云原生技术可以根据应用程序的负载自动扩展或缩小容器的数量,以适应不同的负载要求。
    3、快速部署:使用云原生技术,可以快速地部署和更新应用程序,提高开发和部署效率。
    4、多云部署:云原生技术可以在多个云环境中运行,方便应用程序在不同云环境中的部署和迁移。
    5、数据处理和分析:云原生技术可以处理大规模的数据处理和分析任务,提高数据处理效率。

76、若串 S=“software”,则其子串的数目是()

字串: n(n+1)/2 + 1
非空子串:n(n+1)/2
非空真子串:n(n+1)/2 - 1

77、eden区计算

假如某个JAVA进程的JVM参数配置如下:
-Xms1G -Xmx2G -Xmn500M -XX:MaxPermSize=64M -XX:+UseConcMarkSweepGC -XX:SurvivorRatio=3,
请问eden区最终分配的大小是多少?


    先分析一下里面各个参数的含义: 
    -Xms:1G , 就是说初始堆大小为1G 
    -Xmx:2G , 就是说最大堆大小为2G 
    -Xmn:500M ,就是说年轻代大小是500M(包括一个Eden和两个Survivor) 
    -XX:MaxPermSize:64M , 就是说设置持久代最大值为64M 
    -XX:+UseConcMarkSweepGC , 就是说使用使用CMS内存收集算法 
    -XX:SurvivorRatio=3 , 就是说Eden区与Survivor区的大小比值为3:1:1
    题目中所问的Eden区的大小是指年轻代的大小,直接根据-Xmn:500M和-XX:SurvivorRatio=3可以直接计算得出
    500M*(3/(3+1+1)) 
    =500M*(3/5) 
    =500M*0.6 
    =300M  
    所以Eden区域的大小为300M。

78、TCP 和 UDP 协议有什么区别,分别适用于什么场景?

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种常用的传输层协议,它们有以下的区别:

1、连接方面

TCP 是面向连接的协议,而 UDP 是无连接的协议。在 TCP 中,发送方和接收方必须先建立连接,然后才能传输数据。UDP 则不需要建立连接,直接发送数据即可。

2、可靠性 TCP 保证数据传输的可靠性,通过序列号、确认应答和重传机制等方式来保证数据的完整性和正确性。UDP 则不保证数据传输的可靠性,因为它不提供确认和重传机制。

3、传输速度 因为 TCP 要保证数据传输的可靠性,所以在传输速度方面相对较慢。而 UDP 则不需要进行复杂的传输控制,因此传输速度更快。

4、传输内容 TCP 是一种面向字节流的协议,将数据看作是一连串的字节流,没有明确的消息边界。UDP 则是面向报文的协议,将数据看作是一系列的报文,每个报文是一个独立的单元,具有明确的消息边界。

基于以上的特点,TCP 和 UDP 适用于不同的场景。TCP 适用于对传输可靠性要求比较高的场景,例如网页浏览、文件传输、邮件等。而 UDP 则适用于对传输可靠性要求较低、传输速度要求较高的场景,例如在线游戏、视频直播等。

79、 什么是分布式的 CAP 理论?

分布式的 CAP 理论是指在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)这三个指标无法同时满足的问题。具体来说:

    1、一致性(Consistency):指多个副本之间数据保持一致,即在一个副本上的写操作会立即同步到其他所有副本,所有副本的数据都是最新的,保持强一致性。
    2、可用性(Availability):指系统在任何时候都能对外提供服务,即系统随时能够响应用户请求,不会因为节点故障或其他原因而导致服务中断。
    3、分区容错性(Partition Tolerance):指系统在出现网络分区(节点之间失去联系)时,仍能够继续工作,保证数据的一致性和可用性。
CAP 理论指出,一个分布式系统只能同时满足其中的两个指标,无法同时满足三个。例如,当出现网络分区时,如果要保证一致性,就必须停止对外服务,从而失去可用性;如果要保证可用性,就必须放弃一致性,从而可能导致不同节点之间数据不一致。因此,在设计分布式系统时,需要根据具体的场景和需求来选择合适的权衡方案,比如选择 CP(一致性和分区容错性)或者选择 AP(可用性和分区容错性)。

需要注意的是,CAP 理论只是一种理论框架,不能直接应用于实际的分布式系统设计。在实际应用中,还需要考虑系统的具体业务需求、数据访问模式、节点规模和部署环境等因素,综合权衡之后再选择合适的分布式架构和技术方案。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zaJ7h4zJ-1683601393882)(.\面试题.assets\0ecd1cd6d0f268bf1926608a116c6b7c.png)]

80、如何用 Redis 实现分布式 Session?

在分布式系统中,通常会将 Session 存储在 Redis 中来实现分布式 Session,这样就可以在多台服务器之间共享 Session 数据。实现分布式 Session 的方式有多种,其中一种常用的方式是使用 Redis 的数据结构 Hash。具体实现步骤如下:

    1、在用户登录成功后,将 Session 数据存储在 Redis 中。
    
    2、将 Redis 中的 Session 数据的 Key 设置为一个全局唯一的 ID,一般使用类似于“session:token”这样的格式,其中 token 是一个随机生成的字符串,用来标识这个 Session 数据。
    
    3、在客户端返回响应的同时,将 Session ID(即 token)以 Cookie 的形式返回给客户端。客户端在后续的请求中都会携带这个 Cookie。
    
    4、在后续的请求中,服务器会从客户端传递过来的 Cookie 中获取 Session ID,然后根据这个 ID 从 Redis 中获取对应的 Session 数据。如果 Redis 中没有找到对应的 Session 数据,那么就表示这个请求无法通过认证。
    
    5、在用户退出登录或 Session 失效时,需要将 Redis 中的对应 Session 数据删除。
    
    6、可以使用 Redis 的 EXPIRE 命令来设置 Session 数据的过期时间,这样可以自动删除已经过期的 Session 数据。同时,还需要注意保护 Redis 中的 Session 数据不被恶意攻击者窃取,一般可以通过设置 Session 数据的前缀和使用随机的 Session ID 等方式来提高安全性。


81、如何保证Redis和Mysql的数据同步问题

一、先删除缓存:

    存在问题:
        1、如果先删除Redis缓存数据,然而还没有来得及写入MySQL,另一个线程就来读取。
        2、这个时候发现缓存为空,则去Mysql数据库中读取旧数据写入缓存,此时缓存中为脏数据。
        3、然后数据库更新后发现RedisMysql出现了数据不一致的问题。

二、后删除缓存:

    存在问题:
        1、如果先写了库,然后再删除缓存,不幸的写库的线程挂了,导致了缓存没有删除
        2、这个时候就会直接读取旧缓存,最终也导致了数据不一致情况
        3、因为写和读是并发的,没法保证顺序,就会出现缓存和数据库的数据不一致的问题
三、延时双删策略【保证最终一致性】
2.1.1、基本思路
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。

伪代码如下:

public void write( String key, Object data ){ 
    //第一次删除
    redis.delKey( key ); 
    //更新数据
    db.updateData( data );
    //睡眠等待五秒【需大于更新时间】
    Thread.sleep( 500 ); 
    //第二次删除数据
    redis.delKey( key );
}
具体步骤
    1、先删除缓存
    2、再写数据库
    3、休眠xxx毫秒(根据具体的业务时间来定)
    4、再次删除缓存

问题:这个500毫秒怎么确定的,具体该休眠多久时间呢?

    1、需要评估自己的项目的读数据业务逻辑的耗时。
    2、这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
    3、当然这种策略还要考虑redis和数据库主从同步的耗时。
    4、最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。
设置缓存过期时间是关键点
    1、从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案
    2、所有的写操作以数据库为准,只要到达缓存过期时间,缓存删除
    3、如果后面还有读请求的话,就会从数据库中读取新值然后回填缓存
    结合双删策略+缓存超时设置,这样最差的情况就是:
方案缺点:	
    1、在缓存过期时间内发生数据存在不一致
    2、同时又增加了写请求的耗时。
为什么要双删呢:

因为第一次删除的是还没更新前的数据,第二次删除则是因为读取的并发性导致的缓存重新写入数据出现的垃圾数据。
    
如果你们的删缓存失败了,怎么办?那不是还是会出现缓存和数据库不一致的情况么?比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。

这时候我们就需要一个中间件的无私配合了,那就是使用消息来进行重试机制。
步骤:
    1、业务代码去更新数据库
    2、数据库的操作进行记录日志。
    3、订阅程序提取出所需要的数据以及key
    4、获得该信息尝试删除缓存,发现删除失败的时候,发送消息到消息队列
    5、继续重试删除缓存的操作,直到删除缓存成功。

其实这个方法与分布式事务的处理方式,就是保证数据的最终一致性,而在分布式事务中,则称之为这种为最大努力通知。那么最大努力通知又是什么样的流程呢?

    1、业务方把通知发送给 MQ
    2、接收通知方监听 MQ
    3、接收通知方接收消息,业务处理完成回应ack
    4、接收通知方若没有回应ack则MQ会重复通知,MQ 按照间隔时间从短到长的方式逐步拉大通知间隔,直到达到通知要求的时间上限,比如24小时之后不再进行通知。
    5、接收通知方可通过消息校对接口来校对消息的一致性
总结:
而为什么叫最大努力通知呢,实际上也很容易理解,他并没有从本质上解决问题,只是把问题数目从100 变成了 10 ,毕竟有些内容第一次没处理,第二次就可能会被处理掉。也就是说降低了这种有问题情况的发生,毕竟保证的都是最终一致性。
    方案:异步更新缓存(基于Mysql binlog的同步机制)
整体思路:
    1、涉及到更新的数据操作,利用Mysql binlog 进行增量订阅消费
    2、将消息发送到消息队列
    3、通过消息队列消费将增量数据更新到Redis4、操作情况

读取Redis缓存:热数据都在Redis上
写Mysql:增删改都是在Mysql进行操作
更新Redis数据:Mysql的数据操作都记录到binlog,通过消息队列及时更新到RedisRedis更新过程
数据操作主要分为两种:

1、一种是全量(将所有数据一次性写入Redis2、一种是增量(实时更新)

这里说的是增量,指的是mysql的update、insert、delate变更数据。

读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。
1、这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis
2Redis再根据binlog中的记录,对Redis进行更新
3、其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性

这里的消息推送工具你也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis!

3、总结
在高并发应用场景下,如果是对数据一致性要求高的情况下,要定位好导致数据和缓存不一致的原因。

解决高并发场景下数据一致性的方案有两种,分别是延时双删策略和异步更新缓存两种方案。

也可以采用别的第三方:kafka、rabbitMQ等来实现推送更新Redis!

82、MySQL 支持哪些存储引擎?默认使用哪个?MyISAM 和 InnoDB 引擎有什么区别,如何选择?

MySQL 支持多种存储引擎,包括 InnoDB、MyISAM、MEMORY、CSV 等。默认情况下,MySQL 使用的存储引擎是 InnoDB。

MyISAM 和 InnoDB 是 MySQL 中 最常用 的两种存储引擎,它们有以下区别:

    1、锁定方式不同:MyISAM 使用表级锁定,而 InnoDB 使用行级锁定。在并发访问时,InnoDB 的锁定方式更加精细,可以避免锁定整个表,提高了并发性能。
    2、数据完整性不同:MyISAM 不支持事务和外键约束,而 InnoDB 支持事务和外键约束,可以保证数据的完整性和一致性。
    3、读写性能不同:MyISAM 的读写性能相对较高,适合于读密集型应用;而 InnoDB 的写性能相对较高,适合于写密集型应用。
    4、空间利用率不同:MyISAM 不支持行级别的存储,存储空间利用率较低;而 InnoDB 支持行级别的存储,存储空间利用率更高。
在选择 MyISAM 和 InnoDB 引擎时,需要考虑应用场景和需求:

    1、如果应用主要是读操作,可以考虑选择 MyISAM 引擎,以提高读取性能。
    2、如果应用主要是写操作或需要支持事务和外键约束,可以考虑选择 InnoDB 引擎,以保证数据的完整性和一致性。
    3、如果需要高性能和高可用性,可以考虑选择使用 MySQL 集群或使用多个副本实例,并将数据分布在不同的节点上。
总结:MySQL 支持多种存储引擎,MyISAM 和 InnoDB 是其中最常用的两种,它们有不同的特点和优缺点,在选择时需要根据应用场景和需求进行考虑和权衡。

83、Spring 中的 BeanFactory 和 ApplicationContext 有什么区别和联系?

BeanFactory 和 FactoryBean 是在 Spring 框架中用于实现依赖注入的两个重要接口。

BeanFactory 是 Spring 的核心接口之一,它负责管理 Bean 的生命周期。它提供了创建 Bean、配置 Bean 以及管理 Bean 依赖关系的方法。

FactoryBean 是一个特殊的 Bean,当通过 BeanFactory 或 ApplicationContext 获取它的实例时,会调用它的 getObject() 方法来获取它创建的对象。它可以用来创建复杂的对象,如单例模式的对象、代理对象等。

简单来说,BeanFactory 是一个 Bean 管理工厂,用来管理 Bean 的生命周期,而 FactoryBean 是一种特殊的 Bean,它可以用来创建其他 Bean





BeanFactory和ApplicationContext是Spring框架中的两个核心接口,它们的主要区别如下:

1、容器加载方式不同:BeanFactory是Spring框架最基础的容器,提供了最基本的依赖注入和对象生命周期管理等功能。ApplicationContext是BeanFactory的子接口,它在BeanFactory的基础上增加了很多特性,比如国际化、事件机制等,并且在容器启动时就预先加载了所有的bean。

2、容器管理范围不同:BeanFactory是Spring框架的最基础容器,只提供了基本的Bean管理功能,通常在小型应用中使用。ApplicationContext是更高级的容器,能够在启动时预先加载所有的Bean,并提供了更多的应用功能,通常在中大型应用中使用。

3、容器启动速度不同:BeanFactory启动较快:BeanFactory只在实际获取Bean时才会去加载Bean,因此启动速度较快。而ApplicationContext在启动时会预先加载所有的Bean,因此启动速度较慢,但是获取Bean的速度较快。

4、自动装配方式不同:BeanFactory提供的自动装配功能比较有限,只能通过配置文件进行装配。ApplicationContext则提供了更强大的自动装配功能,可以通过注解、Java配置等方式进行装配。

5、容器销毁时的处理不同:BeanFactory容器中的Bean是延迟加载的,容器销毁时不会自动销毁Bean,需要手动进行销毁。ApplicationContext容器中的Bean在容器销毁时会自动进行销毁。

=> 综上考虑,在使用Spring框架时,通常会优先选择使用ApplicationContext,因为它提供了更多的功能,并且性能相对也更好。在特殊场景下,比如应用程序比较小,并且对性能要求较高,那么可以考虑使用BeanFactory。

84、Redis 中的内存淘汰机制、有哪些内存淘汰策略?

Redis 是一种基于内存的键值数据库,由于内存有限,当 Redis 占用的内存达到上限时,就需要进行内存淘汰,以腾出一些内存空间。

Redis 中的内存淘汰机制包括:

1、定期删除:Redis 可以设置一个定时器,定期扫描键空间中的键,并删除已经过期的键。
2、惰性删除:当一个键过期时,Redis 不会立即删除该键,而是等到该键被访问时再删除。
3、内存淘汰策略:当 Redis 内存占用达到上限时,会根据内存淘汰策略来选择一些键进行删除,以腾出更多的内存空间。
Redis 中的内存淘汰策略包括:

    1、noeviction:禁止删除键,即不做任何操作。
    2、allkeys-lru:从所有的键中选择最近最少使用的键进行删除。
    3、allkeys-random:从所有的键中随机选择一些键进行删除。
    4、volatile-lru:从已设置过期时间的键中选择最近最少使用的键进行删除。
    5、volatile-random:从已设置过期时间的键中随机选择一些键进行删除。
    6、volatile-ttl:从已设置过期时间的键中选择剩余时间最短的键进行删除。
    其中,noeviction 策略是最简单的策略,但可能会导致 Redis 内存占满,并导致 Redis 无法正常工作。其他策略则会根据不同的算法进行键的选择和删除,以尽可能地保留重要的键。

总之,Redis 中的内存淘汰机制是保证 Redis 正常运行的重要机制之一,内存淘汰策略则根据不同的场景选择合适的策略来删除不必要的键,以腾出更多的内存空间。

85、Redis 6.0 之后为何引入了多线程?6.0 之前为什么不使用多线程?

在 Redis 6.0 之前,Redis 是单线程的,这是因为 Redis 的主要瓶颈是在 CPU 上。但是随着硬件的发展,现代服务器的 CPU 核心数已经达到了几十个,这就导致 Redis 单线程模型无法充分利用多核处理器的性能。因此,Redis 6.0 引入了多线程,以提高 Redis 在多核处理器上的性能。

Redis 6.0 之前为什么不使用多线程,主要有以下几个原因:

1、Redis 单线程模型相对简单,容易维护和调试,代码逻辑也比较清晰。
2、Redis 的主要瓶颈在于 CPU,而不是 I/O,因此采用多线程模型并不能显著提高性能。
3、Redis 是一个内存型数据库,它的性能主要受到 CPU 和内存带宽的限制。采用多线程模型会增加线程之间的竞争和锁等开销,反而可能降低 Redis 的性能。
但是随着硬件的发展,多核处理器已经成为了现代服务器的标配,因此 Redis 引入多线程的举措可以更好地发挥硬件的性能,提高 Redis 的吞吐量和响应速度。

86、HTTP 协议中 GET 和 POST 有什么区别?分别适用于什么场景?

HTTP 协议中 GET 和 POST 是两种常用的请求方法,它们的区别如下:

1、参数传递方式不同 GET 请求参数是在 URL 中以键值对的形式传递的,例如:http://www.example.com/?key1=value1&key2=value2。 而 POST 请求参数是在请求体中以键值对的形式传递的。
2、参数传递大小不同 GET 请求参数有大小限制,因为 URL 长度有限制,不同的浏览器和服务器对 URL 长度的限制不同,一般为 2048 个字符。而 POST 请求参数没有大小限制,因为它们是以请求体的形式传递的。
3、安全性不同 GET 请求的参数是明文传输的,因为参数在 URL 中,如果涉及敏感信息(如密码),容易被窃取或暴露在浏览器历史记录、代理服务器日志等地方。而 POST 请求的参数在请求体中传输,相对安全一些,但是也需要注意参数加密和防止 CSRF 攻击等问题。

GET 和 POST 适用的场景不同:

1、GET 请求适用于获取数据,如浏览网页、搜索等。因为 GET 请求参数以明文形式传输,容易被拦截和篡改,所以不适用于提交敏感信息的操作。
2、POST 请求适用于提交数据,如登录、注册、发布内容等。因为 POST 请求参数在请求体中传输,相对安全一些,可以提交敏感信息,但是需要注意参数加密和防止 CSRF 攻击等问题。

87、什么是零拷贝?说一说你对零拷贝的理解?

零拷贝(Zero-Copy)是一种高效的数据传输技术,它可以将数据从内核空间直接传输到应用程序的内存空间中,避免了不必要的数据拷贝,从而提高了数据传输的效率和性能。

在传统的数据传输方式中,当应用程序需要从磁盘、网络等外部设备中读取数据时,操作系统需要先将数据从外部设备拷贝到内核空间的缓冲区,然后再将数据从内核空间拷贝到应用程序的内存空间中,这个过程中需要进行两次数据拷贝,浪费了大量的 CPU 时间和内存带宽。

而使用零拷贝技术,数据可以直接从外部设备复制到应用程序的内存空间中,避免了中间的内核空间缓冲区,减少了不必要的数据拷贝,提高了数据传输的效率和性能。

在网络编程中,零拷贝技术可以用于大文件的传输、网络文件系统的读写、数据库查询等场景中,提高数据传输的效率和响应速度。同时,零拷贝技术也可以减少系统内存的开销,提高系统的稳定性和可靠性。

88、什么是 RPC?目前有哪些常见的 RPC 框架?实现 RPC 框架的核心原理是什么?

RPC(Remote Procedure Call)是一种远程调用协议,允许一台计算机通过网络调用另一台计算机上的服务或方法。它可以让开发人员像调用本地方法一样调用远程方法,将网络通信细节封装起来,提高了分布式系统中各个模块之间的耦合性。


目前常见的 RPC 框架有:

    1、Dubbo:阿里巴巴开源的分布式 RPC 框架,支持多种协议和负载均衡策略。
    2、gRPC:Google 开源的高性能 RPC 框架,支持多种语言。
    3、Thrift:Facebook 开源的跨语言 RPC 框架,支持多种传输协议和数据编解码方式。
    4、Spring Cloud Netflix:Spring Cloud 的子项目之一,提供了基于 Netflix OSS 开源组件的微服务解决方案,包括服务发现、负载均衡、熔断器等功能。
    
RPC 框架的核心原理是基于网络传输协议实现的远程方法调用。RPC 框架通常由服务提供者和服务消费者两部分组成,服务提供者将本地方法暴露成远程服务,服务消费者通过远程代理对象调用远程方法。


在实现远程方法调用时,需要进行序列化和反序列化操作。序列化将对象转换为二进制数据流,以便于在网络中传输;反序列化则将接收到的二进制数据流转换为对象。


为了提高性能,一些 RPC 框架使用了二进制协议,如 Dubbo 使用的 Hessian2 协议和 gRPC 使用的 Protocol Buffers 协议,与基于文本的协议(如 XML 和 JSON)相比,二进制协议具有更小的传输体积和更高的解析速度,能够减少网络传输的开销。

89、设计模式可以分为哪几类?一共有多少种主流的设计模式?

设计模式可以分为三类:

1、创建型模式:这类模式关注对象创建的机制,包括单例模式、工厂模式、抽象工厂模式、建造者模式和原型模式等。
2、结构型模式:这类模式关注对象之间的组合关系,包括适配器模式、装饰器模式、代理模式、组合模式、桥接模式、外观模式和享元模式等。
3、行为型模式:这类模式关注对象之间的通信方式和协作方式,包括模板方法模式、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式和访问者模式等。



1、简单工厂模式(Simple Factory Pattern):一个工厂对象负责根据传入的参数创建不同的对象实例。

2、工厂方法模式(Factory Method Pattern):每个具体的类都有自己的工厂方法,负责创建对象实例。

3、抽象工厂模式(Abstract Factory Pattern):一组相关或相互依赖的对象,由一个抽象工厂对象负责创建。

4、单例模式(Singleton Pattern):确保类只有一个实例,并提供全局访问点。

5、建造者模式(Builder Pattern):将一个复杂对象的构建过程分解为多个简单对象的构建过程。

6、原型模式(Prototype Pattern):一种创建型设计模式,它通过克隆已有对象来创建新的对象,而不是通过实例化对象来创建。

7、适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口。

8、装饰器模式(Decorator Pattern):动态地为对象增加新的功能。

9、代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。

10、外观模式(Facade Pattern):为复杂的子系统提供一个简单的接口。

11、桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。

12、组合模式(Composite Pattern):将对象组合成树形结构以表示部分-整体的层次结构。

13、享元模式(Flyweight Pattern):通过共享技术来实现大量细粒度对象的复用。

14、策略模式(Strategy Pattern):定义一系列算法,将它们封装起来,并且使用他们可以相互替换。

15、模板方法模式(Template Method Pattern):定义一个算法的骨架,将一些步骤延迟到子类中实现。

16、观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都得到通知并自动更新。

17、迭代器模式(Iterator Pattern):提供一种方法顺序访问一个聚合对象中各个元素,而且又不暴露该对象的内部表示。

18、责任链模式(Chain of Responsibility Pattern):为某个请求创建一个对象链,处理该请求的对象沿着该练依次处理,知道一个对象处理它为止。

19、命令模式(Command Pattern):将一个请求封装成一个对象,使发出请求的责任和执行请求的责任分割开。

20、备忘录模式(Memento Pattern):它允许在不暴露对象实现细节的情况下,保存和恢复对象之前的状态。

21、状态模式(State Pattern):允许对象在其内部状态改变时改变它的行为。

22、访问者模式(Visitor Pattern):封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。

23、中介者模式(Mediator Pattern):通过封装一系列对象之间的交互,来降低对象之间的耦合度。

90、如何在 10 亿个数据中找到最大的 1 万个?(提示:最小堆)

在 10 亿个数据中找到最大的 1 万个,可以使用最小堆(Min-Heap)算法来实现。

最小堆是一种特殊的二叉树结构,每个节点的值都小于或等于其左右子节点的值。在使用最小堆来查找最大的 1 万个数据时,可以先创建一个大小为 1 万的最小堆,然后将 10 亿个数据逐个加入堆中。当堆的大小超过了 1 万时,将堆顶元素(即最小值)弹出,再将当前元素加入堆中。当遍历完所有数据后,堆中剩余的 1 万个元素就是最大的 1 万个数据。

这种方法的时间复杂度为 O(n log k),其中 n 是数据总数,k 是要查找的元素数量。因为要维护一个大小为 1 万的最小堆,所以空间复杂度也为 O(k)。
以下是一个示例代码:

import heapq

def find_largest_10000(data):
    heap = []
    for d in data:
        if len(heap) < 10000:
            heapq.heappush(heap, d)
        else:
            heapq.heappushpop(heap, d)
    return heap
                
                
  在这个示例代码中,我们使用了 Python 的内置模块 heapq 来实现最小堆。

函数 find_largest_10000 接受一个包含 10 亿个数据的列表,然后返回最大的 1 万个数据。这个函数使用了一个大小为 1 万的最小堆来维护当前找到的最大的 1 万个数据。对于每个数据,如果堆的大小小于 1 万,则直接将数据加入堆中;否则,先将数据加入堆中,再弹出堆顶元素,保证堆的大小不超过 1 万。最后,函数返回堆中剩余的 1 万个元素,这些元素就是最大的 1 万个数据。

91、MVC的执行流程

1、用户通过浏览器向服务器发送服务请求,请求会被Spring MVC的前端控制器 DispatcherServlet拦截。

2、DsipatcherServlet拦截请求后,会调用HandlerMapping处理器处理映射。

3、处理器根据URL找到具体的处理器,生成处理器对象 及 处理器拦截器,一并返回给DispatcherServlet

4、DispatcherServlet会通过返回的信息选择合适的HandlerAdapter。

5、HanandlerAdapter会调用执行Handler(处理器) 也就是Controller。

6、Controller执行玩后会返回一个ModelAndView对象,对象包含试图名或包含模型和视图名。

7、HandlerAdpater将返回值返回给DispatcherServlet

8、DispatcherServlet会根据ModelAndView选择一个合适的视图解析器。

9、返回给前端浏览器。

92、Redis 有哪些数据类型?基础数据结构有几种?你还知道哪些 Redis 的高级数据结构?

Redis 支持多种数据类型,不同的数据类型可以满足不同的需求。下面是 Redis 中常用的数据类型:

    1、String(字符串):Redis 中最基本的数据类型,可以存储任何形式的数据,例如整数、浮点数、二进制数据等。
    2、Hash(哈希):Redis 中的一种键值对类型,可以存储多个键值对,每个键值对又是一个键值对结构。
    3、List(列表):Redis 中的一个有序列表类型,可以存储多个元素,每个元素都有一个索引,支持多种列表操作,例如插入、删除、查找等。
    4、Set(集合):Redis 中的一种无序集合类型,可以存储多个元素,每个元素都是唯一的,支持多种集合操作,例如交集、并集、差集等。
    5、Sorted Set(有序集合):Redis 中的一种有序集合类型,可以存储多个元素,每个元素都有一个分值,支持根据分值进行排序和查询等操作。
    6、Redis 的基础数据结构有三种:字符串、列表和哈希,其他的数据类型都是基于这三种数据结构进行扩展和衍生的。例如,Redis 的 Set 数据类型就是基于字符串实现的。

除了基础数据结构之外,Redis 还提供了多种高级数据结构,例如:

    1、HyperLogLog:一种基数估计算法,用于估计一个数据集合的基数。
    2、GeoHash:一种地理位置编码算法,可以对地理位置信息进行编码和查询。
    3、Pub/Sub:一种消息队列机制,可以实现消息的订阅和发布。
    4、Bitmaps:一种位图数据结构,可以进行高效的位运算,用于统计用户在线时长、网站访问量等。
    5、Lua 脚本:Redis 中可以使用 Lua 脚本进行扩展和定制,可以实现一些复杂的业务逻辑和算法。

93、有哪些主流的消息队列,它们分别有什么优缺点、各自的适用场景是什么?

名称 优点 缺点 单机吞吐量 分布式 应用
ActiveMQ 时效性ms级,消息可靠性高 官方维护少、性能相对低 万级 Web服务器、企业应用集成 需要多种协议、多种消息类型的场景
Kafka 不易丢失数据 支持多种数据处理模式 Load明显飙高,可能出现消息乱序 社区活跃度低 百万级 大数据、日志采集 高吞吐量、高并发、数据处理流程复杂的场景
RocketMQ 消息零丢失 支持十亿级别消息堆积 支持的客户端语言不多 十万级 金融互联网、秒杀 海量数据和传输场景
RabbitMQ 高并发,支持多种语言 社区活跃度高 商业版收费 万级 数据量中小型 高可靠、多协议、多语言的分布式系统场景
Redis 速度快、支持数据结构丰富、支持事务 可靠性不如RabbitMQ和Kafka 缓存、计数器、实时信息 对性能要求极高的场景

94、Spring 支持哪几种事务管理类型,Spring 的事务实现方式和实现原理是?

Spring 支持以下几种事务管理类型:

    1、编程式事务管理:开发人员通过编写代码实现事务管理,比较繁琐,但灵活性较高。
    2、声明式事务管理:通过配置的方式实现事务管理,可以将事务和业务逻辑分离,简化代码,提高代码可读性和可维护性。
Spring 的事务实现方式是通过 AOP(面向切面编程)实现的。在使用声明式事务管理时,Spring 会在运行时动态地为符合条件的方法添加事务增强(Advice),从而实现事务管理。
Spring 声明式事务管理的实现原理主要包括以下几个方面:

    1、事务管理器(TransactionManager):用于控制事务的开始、提交、回滚等操作,可以支持不同的事务管理方式(如 JDBC、JTA 等)。
    2、事务定义(TransactionDefinition):定义事务的属性,如事务的隔离级别、超时时间等。
    3、事务通知(TransactionAdvice):用于在方法执行前后添加事务管理逻辑,如在方法执行前开启事务,在方法执行后提交或回滚事务等。
    4、切点(Pointcut):用于定义哪些方法需要进行事务管理。
    5、事务拦截器(TransactionInterceptor):用于将事务定义、事务通知、切点等组合起来,形成一个事务增强器,对符合条件的方法进行事务管理。
在 Spring 中,声明式事务管理的核心是使用了 AOP 技术,通过将事务管理逻辑从业务逻辑中抽离出来,达到了解耦的目的,提高了代码的可读性和可维护性。同时,Spring 还提供了多种事务管理方式,如本地事务管理、分布式事务管理等,可以根据业务场景的不同选择不同的事务管理方式。

95、 Redis 为什么快?

redis的性能高,速度快有三个方面的原因:网络,cpu,内存

    1、网络:redis采用了多路复用的设计,(提升了并发的处理连接数)。在redis6.0之后处理网络IO采用了多线程进行处理,但数据的处理还是单线程的。
    
    2、CPU:redis对数据的处理还是由redis的主线程来完成,是单线程的。redis不采用多线程进行数据的处理,是因为官方说明redis几乎不存在CPU的性能瓶颈问题。若redis采用多线程,需要对数据加同步锁,这会导致redis的性能下降。
    
    3、内存:redis的数据结构进行了许多优化,如采用压缩表合理利用内存,使用跳跃表设计降低时间复杂度。并且redis提供了许多不同时间复杂度的数据类型,有string,set,zset,list,hash,不同应用场景,采用合适的数据结构可以降低时间复杂度。

96、简述 TCP 三次握手、四次挥手的流程?为什么需要三次握手?为什么需要四次挥手?

TCP(Transmission Control Protocol)是一种面向连接的协议,为了保证数据传输的可靠性,TCP 使用了三次握手和四次挥手的过程。

三次握手的过程如下:

第一次握手:客户端向服务器发送 SYN 报文,请求建立连接。
第二次握手:服务器收到客户端的 SYN 报文,向客户端发送 SYN+ACK 报文,表示可以建立连接。
第三次握手:客户端收到服务器的 SYN+ACK 报文,向服务器发送 ACK 报文,表示连接已经建立。
为什么需要三次握手?

三次握手的目的是为了确认双方的收发能力和同步初始序列号。

四次挥手的过程如下:

第一次挥手:客户端向服务器发送 FIN 报文,请求关闭连接。
第二次挥手:服务器收到客户端的 FIN 报文,向客户端发送 ACK 报文,表示收到关闭请求。
第三次挥手:服务器向客户端发送 FIN 报文,请求关闭连接。
第四次挥手:客户端收到服务器的 FIN 报文,向服务器发送 ACK 报文,表示收到关闭请求。
为什么需要四次挥手?

四次挥手的目的是为了保证数据的完整性和可靠性。在关闭连接之前,双方需要确保所有数据都已经传输完毕,因此需要通过四次挥手的过程进行确认和处理。

总结:三次握手的本质是确认通信双方收发数据的能力 ,四次挥手的目的是关闭一个连接。

97、什么是 Spring 的依赖注入,依赖注入的基本原则以及好处?

依赖注入(Dependency Injection,简称DI)是Spring框架中的一种设计模式,用于实现控制反转(Inversion of Control,简称IoC)。它是一种将对象之间的依赖关系从硬编码中解耦的方法。通过依赖注入,Spring框架可以在运行时自动为类的属性、构造函数或方法参数提供所需的依赖对象,从而实现对象之间的松耦合。

依赖注入的基本原则:

    1、高层模块不应该依赖低层模块。它们都应该依赖抽象。
    2、抽象不应该依赖具体实现。具体实现应该依赖抽象。
依赖注入的好处:

    1、解耦:依赖注入降低了组件之间的耦合度,使得组件可以独立地进行开发、测试和维护。通过将组件的创建和管理交给IoC容器,组件之间的依赖关系变得更加清晰,有助于提高代码的可维护性。
    2、提高代码的可测试性:依赖注入使得对组件进行单元测试变得更加容易。通过使用依赖注入,我们可以轻松地为组件提供模拟(Mock)的依赖对象,从而实现组件在隔离环境中的测试。
    3、更好的代码重用:由于组件之间的依赖关系变得更加清晰,这有助于提高代码的可重用性。组件可以在不同的上下文中重用,而无需对其进行修改。
    4、更简洁的代码:依赖注入使得代码更加简洁,因为组件不再需要直接处理依赖对象的创建和管理。这使得开发人员可以更加专注于组件的核心功能,从而提高开发效率。
    5、更容易进行配置管理:依赖注入允许我们将组件的配置与实现代码分离,从而使得配置管理变得更加容易。通过使用外部配置文件或注解,我们可以在不修改代码的情况下,调整组件之间的依赖关系。

98、什么是注册中心?如何实现一个注册中心?

注册中心(Service Registry)是微服务架构中的一个关键组件,负责管理服务实例的信息,包括服务实例的地址、端口、元数据等。它允许服务实例在启动时注册自己,并在关闭时注销。服务消费者可以通过查询注册中心来发现可用的服务提供者,并根据负载均衡策略选择合适的服务实例进行调用。

实现一个注册中心需要以下几个关键功能:

1、服务注册:允许服务实例在启动时将自己的信息注册到注册中心,包括地址、端口、元数据等。
2、服务注销:允许服务实例在关闭时从注册中心注销自己。
3、服务发现:允许服务消费者查询可用的服务实例列表,以便找到合适的服务实例进行调用。
4、健康检查:注册中心需要定期检查已注册的服务实例的健康状况,以确保服务实例列表的准确性。如果发现某个服务实例不再可用,注册中心应将其从列表中移除。
实现一个简单的注册中心可以参考以下步骤:

1、选择一个合适的数据结构(例如哈希表)存储服务实例的信息。
2、实现一个HTTP API,允许服务实例在启动时向注册中心发送注册请求,将自己的信息添加到数据结构中。
3、实现一个HTTP API,允许服务实例在关闭时向注册中心发送注销请求,将自己的信息从数据结构中移除。
4、实现一个HTTP API,允许服务消费者查询可用的服务实例列表。
5、实现一个定时任务,定期检查已注册的服务实例的健康状况,如果发现某个服务实例不再可用,将其从数据结构中移除。

然而,实现一个可靠、高性能、可扩展的注册中心是一个复杂的任务。在实际应用中,通常推荐使用现有的成熟的注册中心组件,例如Eureka、Consul、Zookeeper等。这些注册中心提供了丰富的功能,包括高可用性、数据持久化、动态配置等。

99、什么是工厂模式?使用工厂模式有什么好处?工厂模式有哪些分类?各自的应用场景是什么?

工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种封装对象创建过程的方法。工厂模式将对象的创建和使用分离,让一个专门的工厂类负责创建对象实例,而不是在代码中直接使用new操作符。这有助于降低代码的耦合度,提高可维护性和可扩展性。

使用工厂模式的好处:

    1、降低耦合度:工厂模式将对象的创建与使用分离,使得客户端代码不直接依赖具体的类,降低了耦合度。
    2、提高可扩展性:当需要添加或修改产品类时,只需修改工厂类,而不需要修改客户端代码,提高了系统的可扩展性。
    3、提高可维护性:通过集中管理对象的创建,提高了代码的可维护性。
    4、提高代码复用性:工厂类可以被多个客户端代码复用,减少了重复代码。
工厂模式可以分为以下几类:

    1、简单工厂模式(Simple Factory Pattern):一个工厂类根据传入的参数决定创建哪个具体产品类的实例。简单工厂模式适用于产品种类较少且不易变化的场景。 应用场景:例如,一个简单的图形绘制工具,可以根据传入的参数创建不同类型的图形(如圆形、矩形等)。
    2、工厂方法模式(Factory Method Pattern):定义一个接口或抽象类来创建对象,将实际创建对象的工作推迟到子类中。工厂方法模式适用于产品种类较多且可能增加的场景。 应用场景:例如,一个日志记录器,可以根据不同的需求(如文件日志、数据库日志等)使用不同的工厂子类创建相应的日志记录器实例。
    3、抽象工厂模式(Abstract Factory Pattern):提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。抽象工厂模式适用于产品族的场景。 应用场景:例如,跨平台UI框架,可以为不同平台(如Windows、macOS等)提供不同的UI控件实现,通过抽象工厂来创建对应平台的一系列UI控件。
    
各种工厂模式的应用场景取决于实际需求,需要根据具体问题来选择合适的工厂模式。

100、什么是 AOP?Spring AOP 和 AspectJ AOP 有什么区别?有哪些实现 AOP 的方式?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它旨在解决软件开发中的横切关注点(cross-cutting concerns)问题。横切关注点是那些分布于多个模块或对象的功能,例如日志记录、安全检查、事务管理等。AOP通过将横切关注点与业务逻辑分离,从而提高了代码的模块化程度,使得开发更加简洁、易于维护。

Spring AOP和AspectJ AOP是两种不同的AOP实现。

Spring AOP:是Spring框架中的AOP实现,基于动态代理实现。Spring AOP主要用于解决Spring容器中Bean的横切关注点问题。由于它使用了动态代理,所以只支持方法级别的切面(即横切关注点只能织入方法的执行)。Spring AOP的性能略逊于AspectJ,但对于大部分应用来说,性能影响不大。
AspectJ AOP:是一个独立的、功能更强大的AOP实现,不仅支持方法级别的切面,还支持字段、构造器等其他切面。AspectJ可以通过编译时织入(编译时修改字节码)或加载时织入(在类加载时修改字节码)的方式实现AOP。Spring可以与AspectJ结合使用,以提供更强大的AOP功能。
实现AOP的方式主要有以下几种:

    1、动态代理:通过代理模式,为目标对象生成一个代理对象,然后在代理对象中实现横切关注点的织入。动态代理可以分为JDK动态代理(基于接口)和CGLIB动态代理(基于类)。
    2、编译时织入:在编译阶段,通过修改字节码实现AOP。AspectJ的编译时织入就是这种方式。
    3、类加载时织入:在类加载阶段,通过修改字节码实现AOP。AspectJ的加载时织入就是这种方式。
    AOP的实现方式取决于具体需求和技术选型。对于Spring应用来说,通常可以使用Spring AOP满足大部分需求,如果需要更强大的AOP功能,可以考虑使用AspectJ。

101、 什么是分布式的 BASE 理论,它与 CAP 理论有什么联系?

BASE理论是分布式系统中用于描述数据一致性的一个概念。它是一个缩写,分别代表:

    1、基本可用(Basic Availability):分布式系统在出现故障时,依然能够保证系统的可用性,但可能会出现部分功能或性能降低的情况。
    2、软状态(Soft State):由于分布式系统中各个节点的状态可能会有一定的延迟,系统允许在一定时间内存在数据不一致的情况。
    3、最终一致性(Eventual Consistency):在一段时间内,分布式系统中的数据可能不一致,但最终会达到一致状态。这个过程可能需要一定的时间。
CAP理论是另一个关于分布式系统的理论,它指出在分布式系统中,不能同时满足以下三个属性:

    1、一致性(Consistency):在分布式系统中的所有节点,在同一时刻具有相同的数据。
    2、可用性(Availability):分布式系统在任何时刻都能对外提供服务,响应用户请求。
    3、分区容错性(Partition Tolerance):分布式系统在遇到网络分区(部分节点之间通信中断)的情况下仍然能够正常运行。
BASE理论和CAP理论之间的关系是:BASE理论实际上是对CAP理论的一种实践和解释。在CAP理论中,由于无法同时满足三个属性,因此在实际的分布式系统设计中,通常需要在一致性和可用性之间做出权衡。BASE理论就是这种权衡的一种体现,它强调基本的可用性、软状态和最终一致性,而不是追求强一致性。在很多场景中,采用BASE理论能够带来更高的系统可用性和更好的性能表现。

102、如何使用 Redis 实现分布式锁?

使用 Redis 实现分布式锁:

    1、获取锁:使用 Redis 的 SETNX 命令尝试获取锁。
    2、设置过期时间:为了避免死锁,设置锁的过期时间,避免锁被长时间占用。
    3、使用 UUID 防止误删
    4、释放锁
代码示例:

 public class testLock() {

    @Autowired
    private StringRedisTemplate redisTemplate;

    //uuid
    String uuid = UUID.randomUUID().toString().replaceAll("-", "");

    //从redis中获取锁,setnx
    Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 7, TimeUnit.SECONDS);
    if (lock) {
       //从redis中查询num的值
       String value = (String)this.redisTemplate.opsForValue().get("num");
        
       //判断是否为空 
       if (StringUtils.isBlank(value)){
          return ;  //没有该值就直接返回return
       }
        
       //不为空,有值就转成int
       int num = Integer.parseInt(value);
       //把redis中的num值+1,存到redis
       this.redisTemplate.opsForValue().set("num", String.valueOf(++num));//判断
       if(uuid.equals(stringRedisTemplate.opsForValue().get("lock"))){
           //释放锁
           stringRedisTemplate.delete("lock");
       } 
    } else {
       //每隔1秒钟回调一次,再次尝试获取锁
       try {
          Thread.sleep(100); //睡眠100 ms
          testLock(); //重试调用
       } catch (InterruptedException e) {
          e.printStackTrace();
       }
    }

103、如何用 Redis 中的 HyperLogLog 统计页面 UV

在 Redis 中,HyperLogLog 是一种基数估算算法,可以用于快速计算一个集合中的不同元素数量的近似值。

在使用 HyperLogLog 统计页面 UV 时,我们可以将每个访问者的 IP 地址作为一个元素加入到 HyperLogLog 中,然后通过计算 HyperLogLog 中元素数量的近似值来估计页面的唯一访问者数量。

以下是一个示例代码,展示如何使用 Redis 的 HyperLogLog 实现页面 UV 统计:
import redis

# 连接 Redis 服务器
r = redis.Redis(host='localhost', port=6379)

# 记录访问者 IP 地址
ip_address = '192.168.0.1'

# 将 IP 地址加入到 HyperLogLog 中
r.pfadd('page_views', ip_address)

# 获取 HyperLogLog 中元素数量的近似值
uv = r.pfcount('page_views')

# 输出页面 UV
print('页面 UV:', uv)
    
    在上面的示例代码中,我们使用 Redispfadd() 方法将每个访问者的 IP 地址加入到名为 page_views 的 HyperLogLog 中。然后,我们使用 pfcount() 方法获取 page_views 中元素数量的近似值,并将其作为页面 UV 的估计值输出到控制台。

需要注意的是,由于 HyperLogLog 是一种基数估算算法,其结果是一个近似值,并不是精确值。因此,在实际使用中,需要根据具体情况调整参数和精度等级,以获得更准确的结果。

104、有哪些常见的消息队列模型?分别适用于什么场景?

消息队列是一种在分布式系统中用于异步通信的模型,它允许不同的组件通过将消息发送到队列来实现解耦和灵活性。以下是常见的消息队列模型及其适用场景:

1、点对点模型(Point-to-Point Model):这种模型中,消息生产者将消息发送到一个队列中,消息消费者从该队列中接收消息。一个消息只会被一个消费者接收,消费者在处理完消息之后会从队列中删除它。这种模型适用于需要保证消息只被一个消费者处理的场景,例如订单系统、日志处理等。
2、发布-订阅模型(Publish-Subscribe Model):这种模型中,消息生产者将消息发送到一个主题(Topic)中,多个消息消费者可以订阅该主题并接收到所有的消息。每个消息可以被多个消费者接收,消费者在处理完消息之后不会从主题中删除它。这种模型适用于需要将消息广播给多个消费者的场景,例如新闻订阅、实时数据分析等。
3、请求-应答模型(Request-Response Model):这种模型中,消息生产者发送一个请求消息到一个队列中,消息消费者从该队列中接收请求并返回一个响应消息。一个请求只会被一个消费者接收并处理,处理完成后返回响应消息给消息生产者。这种模型适用于需要请求-响应模式的场景,例如远程过程调用、微服务通信等。
4、推拉模型(Push-Pull Model):这种模型中,消息生产者将消息推送到一个队列中,消息消费者从该队列中拉取消息。消息生产者将消息发送到队列中,消费者按需从队列中拉取消息进行处理。这种模型适用于需要灵活控制消息消费速度的场景,例如数据采集、视频流传输等。
	需要注意的是,以上模型并不是完全独立的,实际使用中可以根据具体场景组合使用不同的模型。例如,可以将点对点模型和请求-应答模型结合使用,实现异步的 RPC 调用。另外,在选择消息队列模型时,还需要考虑消息传输的可靠性、顺序性、延迟等因素。

105、有几台机器存储着几亿的淘宝搜索日志,假设你只有一台 2g 的电脑,如何选出搜索热度最高的十个关键词

对于 top k 类文本问题,通常比较好的方案是【分治+hash/trie 树+小顶堆】,即先按照 hash 方法把数据集分解成多个小数据集,然后使用 trie 树或者 hash 统计每个小数据集中的词频,随后用小顶堆求出每个数据集中出现频率最高的前 K 个数,最后在所有 top K 中求出最终的 top K。

1、拆分成 n 多个文件:以首字母区分,不同首字母放在不同文件,如果某个文件长度仍过长,继续按照次首字母进行拆分。这样一来,每个文件的每个数据长度基本相同且首字母相同,就能保证数据被独立的分为了 n 个文件,且各个文件中不存在关键词的交集。

2、分别词频统计:对于每个文件,使用 hash 或者 Trie 树进行进行词频统计

3、小顶堆排序:依次处理每个文件,并逐渐更新最大的十个词

106、什么是双亲委派模式?有什么作用?

双亲委派模式(Parent-Delegate Model)是 Java 类加载器(ClassLoader)在加载类时所采用的一种设计模式。这种模式的核心思想是:当一个类加载器收到类加载请求时,首先不会尝试自己加载这个类,而是将请求委派给其父类加载器。依次递归,直到最顶层的启动类加载器(Bootstrap ClassLoader);如果父类加载器无法加载该类,子类加载器才尝试自己去加载。

双亲委派模式的作用主要有以下几点:

    1、避免类的重复加载:通过委派给父类加载器加载类,可以确保同一个类不会被多个类加载器重复加载。这有助于节省内存资源,并确保类之间的互操作性。
    2、保护 Java 核心类库:由于双亲委派模式的存在,用户自定义的类加载器无法直接加载 Java 核心类库(如java.lang.Object等)。这有助于确保 Java 核心类库的安全性,防止恶意代码篡改或破坏Java核心类。
    3、维护类加载器的层次结构:双亲委派模式使得各级类加载器可以按照一定的层次结构来组织和管理。这有助于降低类加载器的复杂性,简化类加载过程。
双亲委派模式在 Java 类加载器中的应用是一种优秀的设计原则,它有助于确保类加载过程的稳定性、安全性和可维护性。

然而,在某些特殊场景下(如 OSGi、Java 热加载等),双亲委派模式可能无法满足需求,需要采用其他类加载策略。在这些场景下,开发者需要充分了解类加载机制,以避免产生意外的问题。

107、什么是正向代理和反向代理,如何使用 Nginx 做反向代理?

正向代理和反向代理都是代理服务器的两种应用场景,它们在网络请求的处理过程中扮演不同的角色:

正向代理(Forward Proxy):正向代理位于客户端和目标服务器之间,客户端通过正向代理来访问目标服务器。正向代理代表客户端发起请求,隐藏客户端的真实身份。常见的应用场景包括科学上网、访问内网资源、缓存和过滤等。

反向代理(Reverse Proxy):反向代理位于客户端和目标服务器之间,客户端直接访问反向代理服务器,反向代理将请求转发给目标服务器。反向代理代表目标服务器接收请求,隐藏目标服务器的真实身份。常见的应用场景包括负载均衡、安全防护、缓存和SSL终端等。

使用Nginx作为反向代理的方法如下:

安装Nginx:根据操作系统和需求选择合适的Nginx版本进行安装。安装完成后,启动Nginx。

配置反向代理:编辑Nginx的配置文件(通常位于/etc/nginx/nginx.conf或/etc/nginx/sites-available/default),在http或server块中添加反向代理配置。

示例配置:

http {
    ...
    server {
        listen 80; # 监听的端口号
        server_name example.com; # 反向代理的域名

        location / {
            proxy_pass http://backend_server; # 将请求转发给目标服务器
            proxy_set_header Host $host; # 设置请求头部信息
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
    
}
在这个示例中,将客户端的请求转发给名为backend_server的目标服务器。同时设置了一些请求头部信息,以便目标服务器获取客户端的真实IP地址。

重启Nginx:保存配置文件并重启Nginx,使配置生效。通常可以使用nginx -s reload或systemctl restart nginx命令重启Nginx。

完成以上步骤后,Nginx就被配置为反向代理服务器,可以将客户端的请求转发给目标服务器。

108、什么是 Git 的 fork 命令?它和 clone 命令有什么区别?

实际上,Git 本身并没有一个名为 fork 的命令。Fork 是一种在代码托管平台(如GitHub、GitLab等)上进行协作开发的概念。

Fork 操作的本质是复制一个仓库到自己的账户下,这样你就能在自己的仓库中进行修改,而不影响原始仓库。当你对自己仓库中的代码进行了修改,并希望将这些修改合并到原始仓库时,可以发起一个Pull Request。仓库的所有者可以审核你的修改,并选择是否将其合并到原始仓库中。

git clone命令是一个Git命令,用于将远程仓库克隆到本地计算机上。当你克隆一个仓库时,Git会将远程仓库的所有提交历史、分支和标签下载到本地。这样你就可以在本地进行开发、修改和提交操作。

Fork和Clone的区别如下:

    1、操作层面:Fork是在代码托管平台上进行的操作,它会在你的账户下创建一个新的仓库,与原始仓库相互独立。而Clone是在本地计算机上进行的操作,用于将远程仓库的内容复制到本地。
    2、目的:Fork主要用于协作开发,它让你可以在自己的仓库中进行修改,然后通过Pull Request将修改提交给原始仓库。而Clone用于将远程仓库的内容下载到本地,以便你可以在本地进行开发和修改。
    3、权限:Fork操作不需要原始仓库的写权限,只需要读权限。而Clone操作通常需要在本地提交修改后,将修改推送到远程仓库。这可能需要原始仓库的写权限(除非你在自己Fork的仓库中进行开发)。
总之,Fork和Clone是两个不同层面的操作,它们在协作开发和版本管理中扮演不同的角色。在实际应用中,你可能需要同时使用Fork和Clone来进行协作开发。例如,在GitHub上,你可以先Fork一个仓库到自己的账户下,然后使用git clone命令将Fork的仓库克隆到本地进行开发。

109、常见的垃圾回收算法有几种类型?他们对应的优缺点是什么?

常见的垃圾回收算法有以下几种类型:

1、标记-清除算法(Mark-Sweep):分为标记和清除两个阶段。标记阶段遍历所有活动对象并打上标记,清除阶段将未被标记的对象删除。优点是不需要连续内存空间,缺点是清除后可能会产生内存碎片。
2、复制算法(Copying):将可用内存分为两块,只使用其中一块,当这一块满了后,将存活对象复制到另一块未被使用的空间,然后清除使用的那块。优点是简单高效,没有内存碎片问题,缺点是需要额外的空间来存储复制后的对象。
3、标记-整理算法(Mark-Compact):在标记阶段与标记-清除算法类似,但在清除阶段将存活对象整理到内存的一端,然后清除边界以外的所有对象。优点是不会产生内存碎片,缺点是比较慢。
4、分代收集算法(Generational):根据对象存活的时间将内存分为几个区域,每个区域采用不同的回收策略。一般将新生代分为 Eden 区和两个 Survivor 区,采用复制算法回收;将老年代采用标记-清除或标记-整理算法回收。优点是提高了回收效率,缺点是需要额外的维护成本。
这些算法各有优缺点,适用于不同的场景。标记-清除算法简单,但可能会产生内存碎片;复制算法适用于短时间内产生大量垃圾的场景,但需要额外的空间存储复制后的对象;标记-整理算法不会产生内存碎片,但比较慢;分代收集算法提高了回收效率,但需要额外的维护成本。

对于一个应用程序,选择适合的垃圾回收算法需要综合考虑应用场景、内存需求、性能要求等多个因素,以便达到最佳的效果。

110、CC 攻击是什么?什么叫 DDOS 攻击?什么是网站数据库注入?

1、CC 攻击(CC Attack)是一种网络攻击方式,全称为“压力测试(Concurrent Connections)攻击”。它通常是指对服务器进行大量并发请求的攻击,从而导致服务器的瘫痪。攻击者通过使用大量的机器或网络中的代理服务器,向目标服务器发送大量的请求,以消耗服务器的带宽和资源,从而使其无法正常提供服务。CC 攻击可以是攻击者自己编写的脚本,也可以是专门的 CC 攻击工具。
2、DDOS 攻击(Distributed Denial of Service Attack)是一种网络攻击方式,它通常是指利用大量的计算机或网络中的代理服务器,同时向目标服务器发送大量的请求,从而导致目标服务器的瘫痪。DDOS 攻击可以采用多种方式实现,如 SYN 攻击、UDP 攻击、ICMP 攻击等。
3、网站数据库注入(SQL Injection)是一种利用 Web 应用程序漏洞的攻击方式,攻击者通过将恶意 SQL 代码插入到 Web 应用程序的输入字段中,从而获取对数据库的未授权访问。网站数据库注入攻击可以导致许多问题,如破坏数据库的完整性、泄漏敏感数据、执行未经授权的操作等。常见的防御措施包括输入验证、参数化查询、使用安全的 API 等

111、你是否了解过新版本的 Java 特性?对 Java 未来的发展有什么看法?

Java 8(2014年):

    1、Lambda 表达式:简化函数式编程。
    2、Stream API:用于处理集合,支持函数式操作,如过滤、映射和聚合。
    3、默认方法:在接口中提供默认实现,提高接口的灵活性。
    4、Optional 类:减少空指针异常,提高代码可读性。
Java 9(2017年):

    1、模块系统(Project Jigsaw):将 Java 的庞大代码库划分为可重用的模块,简化大型应用的构建和维护。
    2、JShell:Java 的交互式命令行工具,用于快速尝试和测试 Java 代码片段。
    3、新的集合工厂方法:方便地创建不可变集合,如 List.of()、Set.of() 和 Map.of()。
Java 10(2018年):

    1、局部变量类型推断:使用 var 关键字自动推断局部变量的类型,简化代码。
    2、垃圾收集器接口改进:提高了垃圾收集器的可插拔性和灵活性。
Java 11(2018年,长期支持版本):

    1、新的 HTTP 客户端 API:支持 HTTP/2 和 WebSocket,提供了更现代化的编程方式。
    2、改进的垃圾收集:引入了 ZGC 和 Epsilon 垃圾收集器。
    3、String 类的新方法:如 lines()、isBlank()、strip() 等。
Java 12 - 17 的部分新特性:

    1、Switch 表达式:简化 switch 语句的编写,支持使用箭头语法。
    2、文本块:简化多行字符串字面量的表示。
    R3、ecords:简化数据类的定义,自动为其生成构造函数、getter、hashCode()、equals() 和 toString() 方法。
    4、Pattern Matching for instanceof(预览功能):简化 instanceof 操作符的使用,避免显式类型转换。
    5、密封类
    6、删除实验性 AOT 和 JIT 编译器
    7、特定于上下文的反序列化过滤器
Java 未来的发展趋势将是更加注重性能、安全和可靠性。以下是一些可能会影响 Java 未来发展的趋势:

云计算和容器化:Java 非常适合在云环境中使用,因为它具有高度的可移植性和跨平台性。Java 的未来将会更加注重云计算和容器化的支持。
数据科学和人工智能:Java 正在逐渐成为数据科学和人工智能领域的重要语言之一,未来 Java 可能会提供更多的支持和功能。
静态类型检查:静态类型检查是提高代码质量和可靠性的重要手段,Java 未来可能会提供更加严格和强大的类型检查机制。
简化语法:比如 Java 16 中的 Records 和 Sealed Classes 是一种更简洁的语法,未来 Java 可能会继续简化语法以提高代码的可读性和可维护性。
总之,Java 未来将继续发展和改进,以适应不断变化的编程环境和需求。

112、什么是 Git 的 cherry-pick?

Git 的 cherry-pick 是一种将指定的提交(commit)应用到当前分支的操作。它可以帮助我们将某个分支上的某次提交复制到另一个分支上,而无需将整个分支合并过来。

通常,我们在使用 Git 进行版本控制时,会在不同的分支上进行不同的开发工作。有时候,我们需要将某个分支上的某次提交(commit)应用到当前分支上,这时候就可以使用 cherry-pick 操作。

使用 cherry-pick 操作,我们可以复制指定的提交,然后将其应用到当前分支上,这个提交就成为了当前分支上的一个新的提交。cherry-pick 操作可以方便地将某个分支上的某个功能或修复应用到另一个分支上,而无需将整个分支合并过来。

cherry-pick 操作的使用方法如下:

git cherry-pick 
其中, 指定了要复制的提交的 ID。这个命令将复制指定的提交,然后将其应用到当前分支上。

需要注意的是,使用 cherry-pick 操作时,可能会出现冲突,这时候需要手动解决冲突,并提交一个新的提交来解决冲突。因此,在使用 cherry-pick 操作之前,我们需要仔细考虑哪些提交需要复制,以及是否会产生冲突等问题。5

113、 如何在 Linux 中查看系统资源使用情况?比如内存、CPU、网络端口。

在 Linux 中,可以使用一些命令和工具来查看系统资源使用情况。下面介绍一些常用的命令和工具:

1、top:top 命令可以实时地显示系统中各个进程的资源使用情况,包括 CPU 使用率、内存使用率、进程 ID 等信息。可以通过 top 命令来查看当前系统的 CPU 和内存使用情况。
2、free:free 命令可以查看系统中的内存使用情况,包括总内存量、已使用内存量和可用内存量等。可以通过 free 命令来查看当前系统的内存使用情况。
3、df:df 命令可以查看系统中磁盘的使用情况,包括磁盘总容量、已使用空间和可用空间等。可以通过 df 命令来查看当前系统的磁盘使用情况。
4、iostat:iostat 命令可以查看系统的 CPU 和磁盘 I/O 使用情况。可以通过 iostat 命令来查看当前系统的 CPU 和磁盘 I/O 使用情况。
5、netstat:netstat 命令可以查看系统中的网络连接情况,包括本地地址、远程地址、连接状态等信息。可以通过 netstat 命令来查看当前系统的网络连接情况。
6、lsof:lsof 命令可以查看系统中打开的文件和网络连接情况,包括文件名、文件描述符、进程 ID、进程名等信息。可以通过 lsof 命令来查看当前系统的文件和网络连接情况。
7、du:命令显示每个文件和目录的磁盘使用空间,也就是文件的大小。通常可以du -h以K/M/G的形式显示文件大小增加可读性
这些命令和工具都可以帮助我们了解当前系统资源的使用情况,方便我们对系统进行优化和管理。同时,也可以通过一些其他的工具,如 htop、iotop 等,来查看系统资源使用情况。

114、如何用 Nginx 做限流,有几种限流算法,分别如何实现?

下面分别介绍这两种方式以及常用的限流算法。

1、基于请求速率限制:基于请求速率限制是指限制每个客户端的请求速率,常用的限流算法有以下几种:

    1、漏桶算法:在单位时间内处理一定数量的请求,多余的请求则会放入一个“漏桶”中,随后以固定速率处理。

    2、令牌桶算法:在每个单位时间内,将一定数量的“令牌”放入桶中,每次请求需要获取一个令牌才能被处理,当桶中没有令牌时,请求将被拒绝。

    3、计数器算法:简单地对请求计数,并限制每个客户端在单位时间内最多可以处理的请求数量。

在 Nginx 中实现基于请求速率的限流通常需要使用模块,如 ngx_http_limit_req_module、ngx_http_limit_conn_module 等。

2、基于连接速率限制:基于连接速率限制是指限制每个客户端的连接速率,常用的限流算法有以下几种:

    1、并发连接数:限制每个客户端同时能够建立的连接数,以此来限制连接速率。

    2、队列长度:将每个连接加入到一个队列中,限制队列中同时存在的连接数量,以此来限制连接速率。

在 Nginx 中实现基于连接速率的限流通常需要使用模块,如 ngx_http_limit_conn_module 等。

要实现基于请求速率或连接速率的限流,我们需要在 Nginx 配置文件中设置相应的限流规则。以下是一个基于漏桶算法的 Nginx 配置示例:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

    server {
        location / {
            limit_req zone=one burst=20;
            proxy_pass http://backend;
        }
    }
}
其中,limit_req_zone 指定了一个名为 one 的共享内存区域,用于存储请求的统计信息,同时指定了速率为 10r/s。在 location 中,limit_req 指定了限流规则,并将请求转发到后端服务器。

需要注意的是,不同的限流算法适用于不同的场景,我们需要根据实际情况选择合适的算法和配置参数。同时,在实际应用中,为了防止恶意攻击和 DDOS 攻击,通常需要将多种限流算法组合起来使用。

115、git pull 和 git fetch 命令分别有什么作用?二者有什么区别?

git pull 和 git fetch 命令都是用于将远程仓库的代码同步到本地仓库。它们的作用如下:

    1、git pull 命令用于将远程仓库的代码同步到本地仓库,并且合并(merge)到当前分支。相当于先执行 git fetch 命令获取远程仓库的最新代码,然后执行 git merge 命令合并代码到当前分支。
    2、git fetch 命令用于将远程仓库的代码同步到本地仓库,但不会自动将远程分支合并到本地分支。它只是将远程仓库的代码下载到本地仓库,并更新本地仓库对应的远程分支的指针位置。
    二者的区别在于,git pull 命令会自动将远程分支合并到本地分支,而 git fetch 命令则只是更新本地仓库的远程分支指针位置,不会自动合并代码。

因此,在多人协作开发的场景下,如果本地分支与远程分支有冲突,建议先使用 git fetch 命令将远程代码同步到本地仓库,然后手动合并代码,再进行提交。这样可以避免自动合并代码导致的冲突问题。

总的来说,git pull 命令适用于简单的场景,而 git fetch 命令适用于复杂的场景,比如需要手动合并代码的情况。
在 Linux 中,可以使用一些命令和工具来查看系统资源使用情况。下面介绍一些常用的命令和工具:

1、top:top 命令可以实时地显示系统中各个进程的资源使用情况,包括 CPU 使用率、内存使用率、进程 ID 等信息。可以通过 top 命令来查看当前系统的 CPU 和内存使用情况。
2、free:free 命令可以查看系统中的内存使用情况,包括总内存量、已使用内存量和可用内存量等。可以通过 free 命令来查看当前系统的内存使用情况。
3、df:df 命令可以查看系统中磁盘的使用情况,包括磁盘总容量、已使用空间和可用空间等。可以通过 df 命令来查看当前系统的磁盘使用情况。
4、iostat:iostat 命令可以查看系统的 CPU 和磁盘 I/O 使用情况。可以通过 iostat 命令来查看当前系统的 CPU 和磁盘 I/O 使用情况。
5、netstat:netstat 命令可以查看系统中的网络连接情况,包括本地地址、远程地址、连接状态等信息。可以通过 netstat 命令来查看当前系统的网络连接情况。
6、lsof:lsof 命令可以查看系统中打开的文件和网络连接情况,包括文件名、文件描述符、进程 ID、进程名等信息。可以通过 lsof 命令来查看当前系统的文件和网络连接情况。
7、du:命令显示每个文件和目录的磁盘使用空间,也就是文件的大小。通常可以du -h以K/M/G的形式显示文件大小增加可读性
这些命令和工具都可以帮助我们了解当前系统资源的使用情况,方便我们对系统进行优化和管理。同时,也可以通过一些其他的工具,如 htop、iotop 等,来查看系统资源使用情况。

114、如何用 Nginx 做限流,有几种限流算法,分别如何实现?

下面分别介绍这两种方式以及常用的限流算法。

1、基于请求速率限制:基于请求速率限制是指限制每个客户端的请求速率,常用的限流算法有以下几种:

    1、漏桶算法:在单位时间内处理一定数量的请求,多余的请求则会放入一个“漏桶”中,随后以固定速率处理。

    2、令牌桶算法:在每个单位时间内,将一定数量的“令牌”放入桶中,每次请求需要获取一个令牌才能被处理,当桶中没有令牌时,请求将被拒绝。

    3、计数器算法:简单地对请求计数,并限制每个客户端在单位时间内最多可以处理的请求数量。

在 Nginx 中实现基于请求速率的限流通常需要使用模块,如 ngx_http_limit_req_module、ngx_http_limit_conn_module 等。

2、基于连接速率限制:基于连接速率限制是指限制每个客户端的连接速率,常用的限流算法有以下几种:

    1、并发连接数:限制每个客户端同时能够建立的连接数,以此来限制连接速率。

    2、队列长度:将每个连接加入到一个队列中,限制队列中同时存在的连接数量,以此来限制连接速率。

在 Nginx 中实现基于连接速率的限流通常需要使用模块,如 ngx_http_limit_conn_module 等。

要实现基于请求速率或连接速率的限流,我们需要在 Nginx 配置文件中设置相应的限流规则。以下是一个基于漏桶算法的 Nginx 配置示例:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

    server {
        location / {
            limit_req zone=one burst=20;
            proxy_pass http://backend;
        }
    }
}
其中,limit_req_zone 指定了一个名为 one 的共享内存区域,用于存储请求的统计信息,同时指定了速率为 10r/s。在 location 中,limit_req 指定了限流规则,并将请求转发到后端服务器。

需要注意的是,不同的限流算法适用于不同的场景,我们需要根据实际情况选择合适的算法和配置参数。同时,在实际应用中,为了防止恶意攻击和 DDOS 攻击,通常需要将多种限流算法组合起来使用。

115、git pull 和 git fetch 命令分别有什么作用?二者有什么区别?

git pull 和 git fetch 命令都是用于将远程仓库的代码同步到本地仓库。它们的作用如下:

    1、git pull 命令用于将远程仓库的代码同步到本地仓库,并且合并(merge)到当前分支。相当于先执行 git fetch 命令获取远程仓库的最新代码,然后执行 git merge 命令合并代码到当前分支。
    2、git fetch 命令用于将远程仓库的代码同步到本地仓库,但不会自动将远程分支合并到本地分支。它只是将远程仓库的代码下载到本地仓库,并更新本地仓库对应的远程分支的指针位置。
    二者的区别在于,git pull 命令会自动将远程分支合并到本地分支,而 git fetch 命令则只是更新本地仓库的远程分支指针位置,不会自动合并代码。

因此,在多人协作开发的场景下,如果本地分支与远程分支有冲突,建议先使用 git fetch 命令将远程代码同步到本地仓库,然后手动合并代码,再进行提交。这样可以避免自动合并代码导致的冲突问题。

总的来说,git pull 命令适用于简单的场景,而 git fetch 命令适用于复杂的场景,比如需要手动合并代码的情况。

你可能感兴趣的:(java)