面试题目答案

1. 接口与抽象类的区别

在Java中,接口(Interface)和抽象类(Abstract Class)都是面向对象编程的重要概念,它们都是用来定义抽象行为的方式,但它们在设计和使用上存在一些关键区别。

(1)定义和实现:

接口:接口是一种完全抽象的类型,它只包含抽象方法(从Java 8开始,接口还可以包含默认方法和静态方法,以及从Java 9开始,还可以包含私有方法)。接口不能包含实例字段或实例方法的具体实现。

抽象类:抽象类是一个不完整的类,它至少包含一个抽象方法,但也可以包含非抽象方法、实例字段和构造器。抽象类不能被实例化,必须被其他类继承才能使用。

(2)继承与实现:

接口:一个类可以实现多个接口,这允许类继承多个类型的行为。接口提供了一种实现多重继承的方式,而Java不支持类的多重继承。

抽象类:一个类只能继承自一个抽象类(除了间接继承自Object类)。因此,抽象类在提供共享实现的同时,限制了类的继承结构。

(3)设计和使用场景:

接口:接口通常用于定义对象的行为,它定义了一组方法,但不关心这些方法的具体实现。接口非常适合定义一组相关操作的契约,而不关心这些操作是如何实现的。

抽象类:抽象类通常用于提供一组相关操作的共享实现,同时定义一些抽象方法供子类实现。抽象类适合在多个类之间有共享行为,但某些行为需要子类具体实现的情况下使用。

(4)默认方法和静态方法:

从Java 8开始,接口可以包含默认方法和静态方法。默认方法允许在接口中提供方法的默认实现,这样实现接口的类可以选择是否覆盖这些方法。静态方法则与接口的实现类无关,它们可以在接口中直接调用。

抽象类也可以包含默认方法和静态方法,但其用途和接口中的默认方法和静态方法有所不同。

(5)字段:

接口中只能定义常量字段(即使用public static final修饰的字段)。

抽象类可以定义实例字段和静态字段。

(6)设计角度来说:

接口: 自上而下的设计

抽象类: 自下而上的设计

在JDK8中,接口和抽象类都有了一些重要的改进和变化,主要体现在接口上。以下是对这些改进的具体说明:

(1)接口的变化:

默认方法(Default Methods):在JDK8中,接口允许定义默认方法,这些方法带有实现。这使得接口可以添加新的方法,而不会破坏现有的实现类。默认方法使用default关键字修饰,并且可以有方法体。这使得接口更加灵活,可以在不修改现有类的情况下扩展接口的功能。

静态方法(Static Methods):接口现在也可以包含静态方法。静态方法与接口的实现类无关,它们可以在接口中直接调用。静态方法使用static关键字修饰,并且也只能通过接口名来调用。

方法引用:JDK8引入了方法引用,这使得可以更加简洁地引用接口中的默认方法和静态方法。方法引用可以用于Lambda表达式中,提供了一种更简洁的方式来表示函数式接口的实例。

(2)抽象类的变化:

默认方法和静态方法:虽然抽象类本身在JDK8中没有引入新的特性,但抽象类可以包含默认方法和静态方法。这与接口中的默认方法和静态方法有相似之处,但它们的语义和使用场景是不同的。抽象类中的默认方法和静态方法主要提供了一种在抽象类中定义共享行为的方式。

2. ArrayList底层实现原理(/HashSet/HashMap/TreeMap)

ArrayList 是 Java 集合框架(Java Collections Framework)中的一个核心类,它实现了 List 接口,以动态数组的形式存储元素。ArrayList 的底层实现基于可变大小的数组,允许存储任何类型的对象,包括 null。下面详细解释 ArrayList 的底层实现原理:

(1)数据结构

ArrayList 内部使用一个 Object 类型的数组 elementData 来存储元素。数组的大小可以根据需要动态调整。

(2) 初始化

当创建一个新的 ArrayList 对象时,如果不指定初始容量,则会使用默认的初始容量(通常是 10)。

如果指定了初始容量,ArrayList 会根据这个容量来初始化内部数组的大小。

(3)添加元素

当向 ArrayList 添加元素时,首先检查当前数组是否有足够的空间来容纳新元素。

如果有足够的空间,则直接将新元素添加到数组的末尾。

如果空间不足,则需要进行扩容操作。扩容通常是通过创建一个新的、容量更大的数组,并将原数组的元素复制到新数组中来实现的。扩容后的数组容量通常是原容量的 1.5 倍(这个比例可能因 Java 版本和实现的不同而有所差异)。

(4)删除元素

当从 ArrayList 中删除元素时,需要将要删除元素之后的所有元素向前移动一位,以填补删除元素留下的空位。

同时,ArrayList 的 size 属性会减一,以反映集合中元素数量的减少。

(5)访问元素

由于 ArrayList 内部使用数组存储元素,因此可以通过索引快速访问任意位置的元素。访问元素的时间复杂度是 O(1)。

(6)容量和大小

ArrayList 的容量(capacity)是指其内部数组的大小,而大小(size)是指实际存储的元素数量。

容量总是大于等于大小,当添加元素导致大小超过容量时,ArrayList 会自动扩容。

(7) 性能特点

由于 ArrayList 内部使用数组存储元素,因此在内存使用上相对紧凑。

添加元素到末尾的操作非常高效,时间复杂度是 O(1)。

在数组中间插入或删除元素时,由于需要移动元素,所以效率较低,时间复杂度是 O(n)。

ArrayList 不支持并发修改,如果在迭代过程中修改了 ArrayList 的结构(如添加、删除元素),则会抛出 ConcurrentModificationException 异常。

(8)迭代和遍历

ArrayList 提供了多种迭代和遍历元素的方式,包括使用迭代器(Iterator)、增强 for 循环(for-each loop)以及传统的 for 循环。

总结

ArrayList 的底层实现基于可变大小的数组,通过动态调整数组容量来适应元素数量的变化。它在内存使用上相对紧凑,添加元素到末尾的操作非常高效,但在中间位置插入或删除元素时效率较低。因此,在需要频繁在中间位置插入或删除元素的场景中,可能需要考虑使用其他数据结构或集合类,如 LinkedList。

3. 线程有哪几种创建方式

(1)继承Thread类创建线程

这是创建线程的一种常用方式。你需要创建一个新的类,这个类继承自Thread类,然后重写其run()方法。在run()方法中,编写线程需要执行的代码。然后,创建这个新类的实例,并调用它的start()方法启动线程。需要注意的是,启动线程是通过start()方法实现的,而不是直接调用run()方法。

(2)实现Runnable接口创建线程

与继承Thread类创建线程不同,实现Runnable接口的方式更加灵活。因为Java不允许多继承,如果一个类已经继承了其他类,那么就不能再继承Thread类。此时,实现Runnable接口是一个很好的选择。同样,需要重写run()方法,并在其中编写线程需要执行的代码。然后,创建Runnable实现类的实例,将其作为参数传递给Thread类的构造器,创建Thread对象,再调用Thread对象的start()方法来启动线程。

(3)实现Callable接口创建线程

Callable接口是Java 5中新增的一个接口,它可以有返回值,并且可以声明抛出异常。Callable接口是Functional Interface,所以可以使用Lambda表达式创建Callable对象。创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。使用FutureTask对象作为Thread对象的target创建并启动新线程。调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

(4)使用线程池创建线程

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的ThreadFactory创建一个新线程。通过Executor框架的工具类Executors来实现。

4. session和cookie的区别

(1)存储区域与机制:

Session是服务器端的对象,用于存储和管理特定用户会话期间的信息。当用户访问Web应用程序时,服务器会为每个用户创建一个唯一的Session对象,并为其分配一个Session ID。这个Session ID通常存储在用户的浏览器中的Cookie中,以便在用户的不同请求之间进行传递。

Cookie则是一种在Web浏览器和服务器之间传递信息的机制。它是一小段文本信息,由服务器发送到客户端的浏览器,并存储在客户端的计算机上。浏览器在将来的请求中会自动将该Cookie信息包含在HTTP头中发送回服务器。

(2)保存内容:

Session能够存取任何类型的数据,包括用户的登录凭证、购物车中的商品或其他自定义的用户状态信息。Session的大小没有限制,可以存储大量的信息。

Cookie只能保存ASCII字符串,并且其保存的内容比Session小。如果需要存储复杂的数据结构或大量数据,Session更为适合。

(3)有效期:

Session的有效期相对较短。一旦用户关闭浏览器,Session通常会失效。如果需要长期保存用户信息,则需要采用其他方法,如将信息存储在数据库中,并在每次用户访问时通过Session或Cookie验证其身份。

Cookie的有效期则相对较长,可以设置过期时间,使其在用户关闭浏览器后仍然保留在客户端。这使得Cookie适用于需要跨多个会话或长时间保存的信息。

(4)对服务器资源的影响:

由于Session存储在服务器端,当并发访问用户多时,会产生大量的Session对象,占用服务器内存资源。因此,在高并发的场景下,需要合理管理Session,避免内存溢出。

Cookie存储在客户端,不占用服务器资源。这使得Cookie在处理大量用户请求时更加高效。

(5)安全性:

Session:存储在服务器上,无法伪造,因此Session的安全性相对较高。在Java中,Session默认是线程安全的,这意味着多个线程可以同时访问和操作同一个Session对象,而不会导致数据错误或冲突。Java Servlet容器会使用同步机制来确保多个线程之间对Session的访问不会发生冲突。

Cookie:存储在客户端,可以被用户或恶意软件篡改,因此安全性相对较低。为了防止Cookie被篡改,可以在生成Cookie时添加时间戳,并使用非对称加密算法对其进行签名。同时,可以设置Cookie的HttpOnly属性以防止通过JavaScript获取Cookie的值,以及设置Secure属性以确保Cookie只在HTTPS连接中进行传输。

(6)大小:

Session:在Java中,Session的大小没有严格的限制,但它会占用服务器运行内存。每个Session可以存储的数据量是有限的,如果Session中存储的数据量过大,可能会导致内存溢出或性能下降。因此,需要合理管理Session的大小,及时清理不再需要的数据,并避免在Session中存储大量数据。

Cookie:大多数浏览器支持最大为4096字节的Cookie。由于这个限制,Cookie更适合用于存储少量数据,如用户ID之类的标识符。同时,浏览器还限制了每个站点可以存储的Cookie数量,一般为20个,而浏览器通常对来自所有站点的Cookie总数也有一个绝对限制,通常为300个。

5.spring mvc执行流程

Spring MVC 的执行流程是一个相对复杂但有序的过程,它涉及多个组件的协同工作。以下是 Spring MVC 的主要执行流程:

(1)用户发起请求:

用户通过浏览器或其他客户端向服务器发送 HTTP 请求,请求中包含 URL、请求方法(GET、POST 等)、请求头、请求体等信息。

(2)前端控制器(DispatcherServlet)接收请求:

Spring MVC 的前端控制器(通常是 DispatcherServlet)接收到用户的请求。DispatcherServlet 是整个 Spring MVC 流程的核心,它负责协调整个流程。

(3)解析请求并确定处理器:

DispatcherServlet 根据请求的 URL 和配置的处理器映射(HandlerMapping)来确定处理该请求的处理器(通常是 Controller 中的某个方法)。这个过程涉及到解析 URL、查找对应的 Handler。

(4)处理器适配器调用处理器:

确定处理器后,DispatcherServlet 会使用处理器适配器(HandlerAdapter)来调用实际的处理器(Controller 方法)。处理器适配器是 DispatcherServlet 和处理器之间的桥梁,它负责将请求转换为处理器能够理解的格式。

(5)处理器处理请求并返回模型视图:

处理器(Controller)处理请求,并返回一个包含数据和视图的模型(ModelAndView)。模型通常包含业务逻辑处理后的数据,视图则指定了数据的呈现方式。

(6)处理器适配器返回模型视图给前端控制器:

处理器处理完毕后,处理器适配器将模型视图返回给 DispatcherServlet。

(7)视图解析器解析视图:

DispatcherServlet 使用视图解析器(ViewResolver)来解析模型视图中的视图部分,确定用于渲染数据的具体视图技术(如 JSP、Thymeleaf 等)。

(8)前端控制器渲染视图:

DispatcherServlet 将模型和解析后的视图结合起来,交给视图进行渲染。视图根据模型中的数据生成最终的 HTML 页面或其他格式的响应。

(9)响应返回给客户端:

渲染完成后,DispatcherServlet 将响应返回给客户端(浏览器或其他客户端),用户看到最终的页面或数据。

(10)流程结束:

至此,整个 Spring MVC 的执行流程结束。

在整个流程中,Spring MVC 提供了丰富的扩展点,如拦截器(Interceptor)、异常处理器(ExceptionResolver)等,开发者可以根据需要定制和扩展流程中的各个环节。

6. mybatis如何实现模糊查询(#{}和${}区别)

#{}, 预编译sql,使用占位符. 会自动添加引号.

${}, 静态sql, sql注入风险, 不会自动添加引号.

#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。

${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。

select * from t_user where username like "%"#{name}"%". ?????

select * from t_user where username like  #{name} , ok可以使用.

select * from t_user where username like  '%${name}%' , 可以使用,但是不推荐.有sql风险.

select * from t_user where username like  concat('%', #{name}, '%') , 推荐写法.

7. 谈谈你对面向切面编程(答题范围非常广)

面向过程 -> 面向对象 -> 面向切面

(1)面向切面编程(Aspect Oriented Programming,AOP)是一种编程范式,旨在通过预定义的方式将横切关注点(cross-cutting concerns)模块化,以便将它们从业务逻辑中分离出来。这些横切关注点通常包括日志记录、事务管理、安全控制等,它们在多个地方重复出现,但又不属于任何特定的业务逻辑。

(2)一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务

这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。

如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

● 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。

● 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。

使用AOP可以很轻松的解决以上问题。

(3)用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

AOP的优点:

● 第一:代码复用性增强。

● 第二:代码易维护。

● 第三:使开发者更关注业务逻辑。

(4)AOP的七大术语

● 连接点 Joinpoint

  ○ 在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。

● 切点 Pointcut

○ 在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)

● 通知 Advice

○ 通知又叫增强,就是具体你要织入的代码。

○ 通知包括:

■ 前置通知

■ 后置通知

■ 环绕通知

■ 异常通知

■ 最终通知

● 切面 Aspect

  ○ 切点 + 通知就是切面。

● 织入 Weaving

  ○ 把通知应用到目标对象上的过程。

● 代理对象 Proxy

  ○ 一个目标对象被织入通知后产生的新对象。

● 目标对象 Target

  ○ 被织入通知的对象。

8. spring扩展接口

BeanFactory

1. FactoryBean, 将复杂对象放到容器当中,譬如说, spring 整合mybatis . getObjcet,将这个方法的返回值放到了容器当中,我们获取的也是这个方法的返回值.

该接口允许你自定义bean的创建过程。

当你需要返回一个复杂对象或需要根据特定逻辑创建对象时,可以实现这个接口。

2. BeanPostProcessor, 后置处理器/后置增强器.bean初始化前后对bean功能进行增强.

该接口允许你在Spring IoC容器实例化bean之后、初始化方法执行之前或之后执行自定义逻辑。

常见的使用场景包括AOP代理的创建、日志记录、安全检查等。

3. BeanFactoryPostProcessor, 对bean进行增强

该接口允许你在Spring IoC容器实例化bean之前,修改或重新定义bean的属性。

常见的实现有PropertyPlaceholderConfigurer,它用于将属性文件中的值注入到bean的属性中。

4. ApplicationContextAware, 从容器当中获取对象.

(5)Aware接口:

Spring提供了一系列的Aware接口(如ApplicationContextAware、BeanNameAware等),允许bean获取Spring容器的上下文或其他bean的元数据。

这些接口通常通过依赖注入的方式与bean关联,以便bean能够访问容器的特定功能或信息。

(6)ApplicationListener:

该接口允许你监听Spring容器发布的事件,并在事件发生时执行自定义逻辑。

常见的使用场景包括监听容器的启动和关闭事件、监听特定业务事件等。

(7)EnvironmentAware 和 ResourceLoaderAware:

这些接口允许bean访问Spring的环境信息和资源加载器,从而能够读取外部配置文件或加载资源文件。

(8)MessageSource 和 MessageSourceAware:

用于国际化支持,允许你定义和访问多语言环境下的消息。

(9)AspectJ的切面接口:

Spring AOP与AspectJ集成,允许你使用AspectJ的注解(如@Aspect、@Before、@After等)定义切面。

这些接口和注解使得AOP编程更加灵活和强大。

(10)HandlerInterceptor:

在Spring MVC中,该接口用于定义请求处理过程中的拦截器。

你可以使用它来实现诸如认证、授权、日志记录等功能。

(11)TaskExecutor:

用于定义自定义的线程池,以便在Spring中执行异步任务。

你可以实现这个接口或使用Spring提供的ThreadPoolTaskExecutor等实现类。

(12)PropertyEditor 和 CustomEditorConfigurer:

用于自定义类型转换逻辑,允许你在Spring中将字符串转换为自定义类型的对象。

Initializer 和 WebApplicationInitializer:

在Spring Boot和Spring MVC中,这些接口用于替代传统的web.xml配置文件,以编程方式配置Servlet容器和Spring MVC。

9. spring事务机制【重点】

● 什么是事务

○ 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。

  ○ 多条DML要么同时成功,要么同时失败,这叫做事务。

○ 事务:Transaction(tx)

● 事务的四个处理过程:

  ○ 第一步:开启事务 (start transaction)

  ○ 第二步:执行核心业务代码

  ○ 第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)

  ○ 第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)

● 事务的四个特性:

  ○ A 原子性:事务被视为一个最小的工作单元,其操作要么全部完成,要么全部不做。即事务中的各项操作在一次执行过程中,只允许出现两种状态之一,要么都成功,要么都失败。任何一项操作的失败都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚。只有所有的操作全部成功,整个事务才算是成功完成。

  ○ C 一致性:事务必须使数据库从一个一致性状态变换到另一个一致性状态。也就是说,一个事务执行之前和执行之后,数据库都必须处于一致性状态。如果事务执行失败,数据库的状态将回滚到事务开始前的状态。

  ○ I 隔离性:在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。

  ○ D 持久性:一旦事务提交,那么对数据库中的数据的改变就是永久性的。即使系统遇到故障,事务执行的结果也不会丢失。

Spring实现事务的两种方式

● 编程式事务

  ○ 通过编写代码的方式来实现事务的管理。

● 声明式事务

  ○ 基于注解方式

  ○ 基于XML配置方式

事务传播行为

什么是事务的传播行为?

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

事务传播行为在spring框架中被定义为枚举类型:

一共有七种传播行为:

● REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】

● SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】

● MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】

● REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】

● NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】

● NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】

● NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

事务隔离级别

事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越高表示墙体越厚。隔音效果越好。

数据库中读取数据存在的三大问题:(三大读问题)

● 脏读:读取到没有提交到数据库的数据,叫做脏读。

● 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。

● 幻读:读到的数据是假的。

事务隔离级别包括四个级别:

● 读未提交:READ_UNCOMMITTED

  ○ 这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。

● 读提交:READ_COMMITTED

○ 解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。

● 可重复读:REPEATABLE_READ

○ 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。

● 序列化:SERIALIZABLE

  ○ 解决了幻读问题,事务排队执行。不支持并发。

平台事务管理器TrancationManager

提交,回滚

事务定义

事务状态

声明式事务, rollbackfor.exception

10. 设计模式:

单例模式是一种设计模式,用于限制某个类只能创建一个实例。这个模式常用于一些全局配置类、数据库连接池等,以确保全局只有一个实例对象。

(1)饿汉式(线程安全)

饿汉式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。由于JVM在类加载时采用单例模式,所以线程安全。

(2)懒汉式(线程不安全)

懒汉式在第一次调用getInstance()方法时才进行初始化,所以类加载较快,但获取对象的速度稍慢。这种写法在多线程环境下是不安全的。

(3)懒汉式(线程安全,同步方法)

为了解决懒汉式线程不安全的问题,可以在getInstance()方法上加上synchronized关键字,但这会导致每次调用getInstance()方法时都要进行同步,效率较低。

(4)懒汉式(线程安全,双重检查锁定)

双重检查锁定是懒汉式线程安全的推荐写法。它只在第一次初始化时同步,之后直接返回已创建的实例,因此效率较高。但需要注意JVM的内存模型问题,通常需要volatile关键字来保证多线程环境下的正确性。

(5)枚举(线程安全)

枚举方式是单例实现的最佳方法。它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

(6)静态内部类(线程安全)

静态内部类方式同样实现了懒加载,且是线程安全的。这种方式利用了类加载器的机制来保证初始化instance时只有一个线程,并且只有在第一次调用getInstance()方法时才会加载SingletonHolder类,从而初始化instance。

11. mybatis动态sql标签有哪些(也可以使用mp实现)

if标签 需求:多条件查询。作用:用于根据条件决定是否包含某个 SQL 片段。如果条件满足,则包含该 SQL 片段;否则,忽略该片段。

where标签的作用:让where子句更加动态智能。

处理 SQL 语句中的 WHERE 条件,自动添加 WHERE 关键字,并处理多余的 AND 或 OR。

● 所有条件都为空时,where标签保证不会生成where子句。

● 自动去除某些条件前面多余的and或or。

<choose>, <when>, <otherwise>作用:类似于 Java 中的 switch-case-default 语句,用于根据多个条件生成不同的 SQL 片段。

set标签    处理 SQL 语句中的 SET 子句,自动添加 SET 关键字,并处理多余的逗号。

主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”

trim标签的属性:通用的处理标签,可以自定义前缀和后缀的添加和移除规则。

● prefix:在trim标签中的语句前添加内容

● suffix:在trim标签中的语句后添加内容

● prefixOverrides:前缀覆盖掉(去掉)

● suffixOverrides:后缀覆盖掉(去掉)

foreach标签  作用:用于处理集合类型的参数,生成 IN 查询语句或其他需要迭代集合的场景。

循环数组或集合,动态生成sql,批量删除,批量添加

12. Servlet的体系结构

首先Servlet是一个接口,有两个实现类,GenericServlet实现了Servlet接口,是一个抽象类,在Servlet中有五个抽象方法,GenericServlet将Servlet中的除了service之外的四个方法都做了默认空实现

只抽象了service一个方法,

因为在service方法中需要获取数据,而在获取数据之前,需要判断请求方式,所以就有了HttpServlet

HttpServlet继承了GenericServlet,也是一个抽象类,是对http协议的一种封装,可以简化操作.可以定义一个类继承httpServlet,复写doGet/doPost方法

interface Servlet

抽象方法, 5个

abstract class GenericServlet implements Servlet

重写了四个方法,只剩下一个抽象方法. abstract service. 如果直接使用它,则需要我们自己判断请求方式.不是那么方便.

abstract class HttpServlet, 没有抽象方法.

service方法 --> 成员方法 --> 根据不同的请求方式,给出不同的处理方法.在这些方法当中, 默认实现都抛出了异常.

强制子类去重写这些个方法,不能直接使用父类的方法 【模板方法】

service方法 --> 重写了Servlet当中的service()方法.

------------------------------------------------------------------------------------------

DispatcherServlet

doDispatcher, spring mvc核心方法.

13. redis数据类型有哪些, 列举一些redis数据类型的应用场景;

九种, 常用的是五种

String(字符串):

应用场景:这是Redis最基本的数据类型,用于存储字符串类型的数据,如用户信息、配置信息等。String类型支持修改操作,因此也可以用于实现计数器功能,如网站访问量统计。验证码啥的

Hash(哈希):

应用场景:Hash类型是一个键值对的集合,可以用于存储对象的多个字段和值。这使得Hash类型非常适用于存储用户信息、配置项等复杂数据。购物车

List(列表):

应用场景:列表是简单的字符串列表,按照插入顺序排序。因此,它适用于实现消息队列、最新消息列表等场景。例如,可以将新发布的文章ID添加到列表中,然后按照发布顺序展示给用户。

Set(集合):

应用场景:集合是无序的字符串集合,不允许重复元素。因此,它适用于需要存储唯一元素的场景,如用户ID集合、网站标签集合等。好友关注啥的.

zset, Sorted Set(有序集合):

应用场景:有序集合与集合类似,但元素是有序的,每个元素都关联一个分数。这使得有序集合非常适合用于排行榜、按分数检索等场景。例如,可以根据用户的积分或活跃度对用户进行排序。排行榜等

-----------------

Bitmap:

应用场景:Bitmap类型以bit为单位进行操作,提供了一种更细化的数据存储方式。它通常用于需要大量节省空间且数据状态只有两种(如开/关)的场景,如用户在线状态、签到记录等。

HyperLogLog:

应用场景:HyperLogLog是一种基于概率的数据结构,用于估计集合中唯一元素的数量。它非常适合用于大规模数据统计场景,如网站独立访客数统计。

Geo(地理位置):

应用场景:Geo类型用于存储地理位置信息,并支持对地理位置进行各种操作,如计算两点之间的距离、获取指定范围内的地理坐标等。因此,它适用于基于位置的应用,如附近地点搜索、用户位置统计等。

Stream(流):

应用场景:Stream是Redis 5.0版本新增的数据结构,用于实现消息队列功能。它支持消息的发布、订阅和消费,非常适合用于构建实时通信、事件驱动等应用。

14. 内部类有哪些

在Java中,内部类主要有四种类型:

普通内部类(Non-static Nested Class):

这是最常见的内部类形式。当你定义一个类在另一个类的内部,而没有在内部类前使用static关键字时,你就在创建一个普通内部类。

普通内部类可以访问其外部类的所有成员(包括私有成员)。

创建普通内部类的实例时,需要先有一个外部类的实例,因为内部类实例会隐式地持有对其外部类实例的引用。

静态内部类(Static Nested Class):

在内部类前使用static关键字定义的就是静态内部类。

静态内部类只能访问其外部类的静态成员。

创建静态内部类的实例不需要外部类的实例。

局部内部类(Local Inner Class):

定义在方法体或代码块中的内部类就是局部内部类。

局部内部类可以访问其外部类的所有成员,包括私有成员。

局部内部类还可以访问定义它的方法或代码块中的局部变量,但这些局部变量必须是final的(在Java 8及以后的版本中,这个限制已经被放宽,只要局部变量是“事实上的final”即可)。

局部内部类的作用范围仅限于其定义的方法或代码块。

匿名内部类(Anonymous Inner Class):

匿名内部类是没有名称的内部类。

它通常用于实现一个接口或继承一个类,并立即创建该类的实例。

匿名内部类常用于创建只使用一次的简单对象。

匿名内部类没有构造方法,因为不能给它命名。但它可以访问外部类的所有成员。

每种内部类都有其特定的使用场景和优势,根据具体需求选择合适的内部类类型。

public class A {

class B {}

static C {}

}

B的对象

15. synchronized和lock的区别

synchronized和Lock在Java中都是用于实现线程同步的机制,但它们之间存在一些关键的区别。以下是它们之间的一些主要差异:

实现原理:

(1)来源与灵活性:

JVM层面实现synchronized是Java语言的关键字,它是内置的语言特性,用于修饰方法或代码块,实现基本的线程同步。

Java层面实现,Lock是Java的一个接口,在java.util.concurrent.locks包下,提供了比synchronized更广泛的锁定操作。它允许更灵活的结构,比如可以尝试获取锁(tryLock()),可以中断等待锁的线程,可以实现公平锁等。

(2)等待可中断:

synchronized不可中断,除非加锁的代码抛出异常或者运行结束,否则线程将一直等待下去。

Lock接口提供了可以中断等待锁的线程的方法,如lockInterruptibly()。

(3)锁的释放:

synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;在代码执行完毕时,会自动释放线程占有的锁。

Lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。

(4)锁的状态:

对于synchronized,我们无法判断线程是否成功获取到了锁。

对于Lock,我们可以查询线程是否成功获取到了锁,例如使用tryLock()或者isLocked()等方法。

(5)性能:

在资源竞争不激烈的情况下,synchronized的性能要优于Lock,因为Lock的获取和释放操作比synchronized复杂。任务时长长

在资源竞争非常激烈的情况下,Lock的性能可能会比synchronized好,因为Lock提供了更多的自定义和灵活性,可以根据实际场景选择合适的锁策略。任务时长短暂

(6)公平锁与非公平锁:

synchronized是非公平的,即等待时间最长的线程不一定能优先获取到锁。

Lock接口可以实现公平锁,即等待时间最长的线程会优先获取到锁。

综上所述,synchronized和Lock各有其优缺点和适用场景。在选择使用哪种同步机制时,需要根据具体的业务需求和性能要求进行权衡。对于简单的同步需求,synchronized可能是一个更好的选择,因为它简单易用且性能在大多数情况下都足够好。对于更复杂的同步需求,Lock提供了更多的灵活性和控制力。

1. synchronized jvm层面实现的. Lock纯java层面实现的.【CAS + va】

2. synchronized自动释放锁, Lock不会.手动释放,一般情况将lock放到try...catch... finally

3. synchronized适用场景: 耗时比较长的任务, Lock适用任务时长短暂的场景

4. 实现原理, synchronized, 对象头【mark word】. Lock基于cas实现

16. spring mvc执行原理

DispatcherServlet类当中的doDispatcher方法当中逻辑.

Spring MVC是一个基于Java的Web框架,它使用了模型-视图-控制器(MVC)设计模式,使得Web应用的开发更加灵活和模块化。以下是Spring MVC的执行原理的详细解释:

1. 发起请求

当用户通过浏览器向服务器发送请求时,这个请求首先会被前端的Web服务器(如Tomcat)接收。

2. 前端控制器(DispatcherServlet)

在Spring MVC中,DispatcherServlet充当前端控制器的角色。它接收所有的Web请求,并根据请求的URL查找相应的处理器映射(HandlerMapping)。

3. 处理器映射(HandlerMapping)

处理器映射负责将请求的URL映射到相应的处理器(Handler),这个处理器通常是一个Controller中的方法。映射完成后,它会返回一个处理器执行链(HandlerExecutionChain),该链包含了处理器和任何相关的拦截器(Interceptor)。

4. 处理器适配器(HandlerAdapter)

处理器适配器根据返回的处理器执行链中的处理器信息,调用相应的处理器(Controller中的方法)进行业务处理。这个过程可能涉及到与后端服务的交互、数据库操作等。

5. 模型与视图

处理器处理完业务逻辑后,会返回一个ModelAndView对象。ModelAndView包含了视图需要的数据模型(Model)和视图的逻辑名称(View Name)。

6. 视图解析器(ViewResolver)

视图解析器根据ModelAndView对象中的视图逻辑名称,查找具体的视图实现类(通常是JSP、Thymeleaf等模板),并将数据模型传递给视图进行渲染。

7. 视图渲染

视图使用数据模型进行渲染,生成HTML响应。这个响应最终会发送回用户的浏览器。

8. 响应返回

浏览器接收到服务器返回的HTML响应后,进行解析和渲染,最终展示给用户。

总结

Spring MVC的执行原理可以概括为:前端控制器接收请求,通过处理器映射找到对应的处理器,处理器适配器调用处理器处理业务逻辑并返回ModelAndView,视图解析器根据ModelAndView找到并渲染视图,最终返回响应给浏览器。这个过程中,Spring MVC框架提供了丰富的组件和扩展点,使得开发者可以灵活地定制和扩展Web应用的功能。

17.redis持久化

首先,redis是一个内存数据库,当redis服务器重启或者电脑重启后,数据就会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中.

redis持久化机制:有两种RDB和AOF

RDB:默认方式,不需要进行配置,默认使用这种机制,原理是:在一定的间隔时间中,检测key的变化情况,然后持久化数据(推荐),对性能影响较小

AOF:日志记录的方式,可以记录每一条命令的操作.可以在每一次命令操作后,持久化数据,对性能影响较大

Redis 的持久化机制主要用于将数据从内存保存到磁盘中,以确保在系统崩溃或重启后数据不会丢失。Redis 提供了两种主要的持久化方式:RDB(Redis DataBase)和 AOF(Append Only File)。

1. RDB(Redis DataBase)

RDB 是 Redis 默认使用的持久化方式,它基于快照实现,将数据以二进制文件的形式保存在磁盘中。以下是 RDB 的详细介绍:

触发方式

手动触发:通过执行 SAVE 或 BGSAVE 命令来触发 RDB 持久化。其中,SAVE 命令会阻塞 Redis 服务器,直到快照保存完成;而 BGSAVE 命令则会创建一个子进程来异步保存快照,不会阻塞主进程。

自动触发:通过 Redis 配置文件中的 save 选项来设置触发条件,当满足指定的时间间隔和修改键的数量时,Redis 会自动执行 BGSAVE 命令来保存快照。

优点

高效:RDB 文件是二进制文件,体积小,恢复速度快。

紧凑:RDB 文件是压缩的,因此可以节省存储空间。

容灾性好:RDB 文件可以保存到安全的磁盘中,方便备份和恢复。

缺点

数据丢失风险:由于 RDB 是基于快照的持久化方式,如果在两次快照之间 Redis 服务器崩溃,那么上次快照之后的所有数据修改都会丢失。

fork 进程开销:在生成 RDB 文件时,Redis 需要 fork 出一个子进程来执行快照操作,这会增加一定的开销。

2. AOF(Append Only File)

AOF 持久化通过记录 Redis 服务器接收到的每一个写命令(如 SET、DEL 等),并在服务器启动时重新执行这些命令来恢复数据。以下是 AOF 的详细介绍:

写入方式

appendfsync always:每次写入命令都同步到 AOF 文件,这种方式最安全但性能最差。

appendfsync everysec:每秒同步一次 AOF 文件,这是 Redis 的默认配置,兼顾了性能和安全性。

appendfsync no:不同步 AOF 文件,由操作系统决定何时同步,这种方式性能最好但安全性最差。

优点

数据安全性高:AOF 持久化可以确保数据的完整性,即使 Redis 服务器崩溃,也可以通过 AOF 文件来恢复数据。

灵活性高:AOF 文件是文本文件,可以方便地进行查看和编辑。

缺点

恢复速度慢:由于 AOF 文件记录了所有的写命令,因此在 Redis 服务器启动时需要重新执行这些命令来恢复数据,这会导致恢复速度比 RDB 慢。

AOF 文件体积大:与 RDB 文件相比,AOF 文件的体积可能会很大,需要定期进行重写(rewrite)来减小文件体积。

3. 混合持久化(RDB-AOF)

Redis 4.0 版本开始支持混合持久化,即将 RDB 持久化的数据内容和 AOF 持久化的日志内容混合写入到一个 AOF 文件中。这种方式可以同时利用 RDB 持久化快速恢复和 AOF 持久化数据安全性的优点。在 Redis 服务器启动时,会先加载 RDB 文件的内容,然后再重放 AOF 文件中的增量命令来恢复数据。

18. 动态代理【重要】

代理模式是二十三种设计模式之一,属于结构型设计模式

三个作用:当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为.

需要给某个对象的功能进行增强的时候,可以考虑找一个代理进行增强

A对象和B对象无法直接交互时,可以使用代理对象

代理模式中有三大角色:目标对象

    代理对象

  目标对象和代理对象的公告接口

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

代理模式中的角色:

● 代理类(代理主题)

● 目标类(真实主题)

● 代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。

代理模式在代码实现上,包括两种形式:静态代理和动态代理

静态代理会产生类爆炸的问题

动态代理:

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

在内存当中动态生成类的技术常见的包括:

● JDK动态代理技术:只能代理接口。

● CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)

● Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

JDK实现动态代理:需要用到java.lang.reflect.Proxy

创建代理对象时:Object proxyObj = Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器),这个步骤首先在内存中动态的生成了一个代理类的字节码class

其次,new对象.通过内存中生成的代理类的代码,实例化了代理对象.

(类加载器,代理类要实现的接口,调用处理器)

第一个参数:类加载器(ClassLoader loader):在内存中生成的字节码也是class文件,要执行也得先加载到内存当中,加载类就需要类加载器.所以这里需要指定类加载器.并且,JDK要求,目标类的类加载器必须和代理类使用同一个.

第二个参数:Class<?>[] interfaves:代理类和目标类要实现同一个接口或同一些接口.在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的.

第三个参数:InvocationHandler l (调用处理器).是一个接口,在调用处理器中编写的代码就是增强代码.需要编写这个接口的实现类,并不会产生类爆炸的问题

在编写这个接口的实现类的时候,必须实现接口中的方法:invoke(方法),JDK在底层已经写好了调用invoke()方法的程序

当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器中的方法被调用

invoke()方法中也有三个参数

动态代理的作用主要体现在以下几个方面:

解耦:通过将业务逻辑与日志、事务等非业务逻辑分离,降低代码之间的耦合度,使代码更加清晰、易于维护。

功能增强:在不修改原始代码的情况下,为对象添加新的功能或行为。

灵活性:动态代理可以在运行时根据需要创建代理对象,适应不同的场景和需求。

可扩展性:通过动态代理,我们可以方便地扩展系统的功能,而无需修改大量现有代码。

静态代理

解释概念

动态代理

jdk

cglib

应用场景:

ao

19. spring boot自动装配原理

SpringBoot的自动装配原理是其核心特性之一,它极大地简化了Spring应用的初始化和配置过程。以下是关于SpringBoot自动装配原理的详细解释:

(1)@SpringBootApplication注解:

SpringBoot自动装配的核心起始于@SpringBootApplication注解。这个注解实际上是多个注解的组合,其中最为关键的是@EnableAutoConfiguration。

@EnableAutoConfiguration注解告诉SpringBoot根据添加的jar依赖自动配置你的Spring应用。它会从META-INF/spring.factories文件中加载所有可用的自动配置类。

(2)条件注解:

SpringBoot使用条件注解来确定是否应该自动配置某个bean。这些条件注解包括:

@ConditionalOnClass:当类路径下存在指定的类时才会生效。

@ConditionalOnMissingBean:当容器中不存在指定Bean时才会生效。

@ConditionalOnProperty:指定的属性是否有指定的值。

等等。

这些条件注解允许SpringBoot根据应用的上下文和环境进行灵活的自动配置。

(3)自动配置类:

SpringBoot提供了大量的自动配置类,这些类会根据上述的条件注解来决定是否应该创建和配置相应的bean。

这些自动配置类通常包含了大量使用@Bean方法定义的bean,这些方法同样会受到条件注解的控制。

(4)@SpringBootConfiguration和@ComponentScan:

@SpringBootApplication注解还包含了@SpringBootConfiguration和@ComponentScan。

@SpringBootConfiguration实际上是一个@Configuration注解,它告诉Spring这是一个配置类,可以定义bean。

@ComponentScan告诉Spring在哪里查找带有@Component、@Service、@Repository等注解的类,并将它们作为bean注册到Spring容器中。

(5)SPI机制:

SpringBoot使用Java的SPI(Service Provider Interface)机制来加载自动配置类。这通过META-INF/spring.factories文件实现,其中列出了所有可用的自动配置类。

总结:

当SpringBoot应用启动时,它会读取META-INF/spring.factories文件,并根据其中的配置加载自动配置类。

这些自动配置类会根据条件注解来决定是否应该创建和配置bean。

最终,SpringBoot会基于这些自动配置的bean来构建应用的Spring容器,从而实现了应用的快速启动和配置。

SpringBoot的自动装配原理大大简化了Spring应用的开发和配置过程,使得开发者能够更专注于业务逻辑的实现,而不是花费大量时间在繁琐的配置上。

20. mybatis常用注解有哪些

MyBatis 是一款优秀的 ORM(对象关系映射)框架,它提供了大量的注解来简化 Java 应用程序与关系数据库之间的交互。以下是一些 MyBatis 中的常用注解:

@Select:

用于标注查询 SQL 语句的方法。

例如:@Select("SELECT * FROM user WHERE id = #{id}")

@Insert:

用于标注插入 SQL 语句的方法。

例如:@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")

@Update:

用于标注更新 SQL 语句的方法。

例如:@Update("UPDATE user SET name = #{name} WHERE id = #{id}")

@Delete:

用于标注删除 SQL 语句的方法。

例如:@Delete("DELETE FROM user WHERE id = #{id}")

@Results 和 @Result:

@Results 用于定义结果集映射,可以包含多个 @Result。

@Result 用于定义单个字段的映射关系。

这两个注解通常用于处理复杂的查询结果,如联合查询等。

@ResultMap:

用于引用已定义的 <resultMap>。当 SQL 查询结果比较复杂时,可以在 XML 映射文件中定义 <resultMap>,然后在注解中使用 @ResultMap 引用它。

@SelectKey:

用于在插入或更新操作后获取自动生成的主键或其他字段的值。

常常与 @Insert 或 @Update 一起使用,以处理自增主键等场景。

@Options:

用于设置一些全局的选项,如是否使用缓存、是否生成自增的 key 等。

@Param:

用于命名方法参数,以便在 SQL 语句中引用它们。当方法有多个参数时,需要使用 @Param 来指定参数名。

@Mapper 和 @MapperScan:

@Mapper 注解用于标注 Mapper 接口,告诉 MyBatis 这是一个 Mapper 接口,需要为其生成实现类。

@MapperScan 用于指定扫描 Mapper 接口的包路径,MyBatis 会自动扫描这些包路径下的 Mapper 接口并为其生成实现类。

这些注解大大简化了 MyBatis 的使用,使得开发者可以更加便捷地编写 SQL 语句和处理数据库操作。

21. 原生jdbc连接步骤

原生JDBC(Java Database Connectivity)连接数据库的步骤通常包括以下七个主要步骤:

导入JDBC驱动包:首先,你需要将目标数据库的JDBC驱动包导入到你的Java项目中。这通常是通过将JAR文件添加到项目的类路径中来实现的。

加载并注册JDBC驱动:使用Class.forName()方法加载并注册JDBC驱动。这实际上是让JVM知道你要使用哪个数据库的JDBC驱动。

注意:在JDBC 4.0及更高版本中,显式加载驱动类通常是不必要的,因为DriverManager会在需要时自动加载驱动。

3. 建立数据库连接:使用DriverManager.getConnection()方法建立与数据库的连接。你需要提供数据库的URL、用户名和密码作为参数。

创建Statement或PreparedStatement对象:使用Connection对象的createStatement()或prepareStatement()方法创建Statement或PreparedStatement对象。PreparedStatement通常用于执行带有参数的SQL语句,并且可以提高性能并防止SQL注入攻击。

执行SQL语句:使用Statement或PreparedStatement对象的executeQuery()(用于查询)或executeUpdate()(用于更新、插入或删除)方法执行SQL语句。

处理结果集:如果SQL语句是查询语句,那么它将返回一个ResultSet对象。你可以使用ResultSet对象的各种方法来遍历和处理查询结果。

关闭连接和结果集:最后,使用ResultSet、Statement和Connection对象的close()方法关闭它们,以释放资源。这是一个非常重要的步骤,因为它可以确保及时关闭数据库连接,并防止潜在的资源泄漏问题。

22. redis rdb持久化原理

啥叫持久化【解释一下概念】, Redis基于内存型key-value的数据库,由于数据保存在内存当中,断电或者服务崩溃,内存数据丢失, 为了防止这种情况出现, redis提供一种机制,将内存数据按照配置写到磁盘当中.称为持久化.

rdb, redis database, 它是redis提供的持久化方案当中的一种.

当正常关闭服务, flushall, save命令的时候,会触发持久化,此种持久化方式是阻塞式.当触发save 时间 修改key个数或者bgsave的时候,此时父进程

会调用系统函数fork(), 快速创建一个子进程.此时,父进程继续对外提供读写服务, 子进程执行持久化.这里父进程、子进程都指向同一个物理内存空间, 所以不会发生内存复制.此时,如果有客户端通过父进程修改了某些key/新增key, 此时会触发copy on write(写时复制), 并不会影响到当前落盘的文件.

23.spring中常用的注解有哪些?

Spring框架中常用的注解有很多,它们为开发者提供了更加简洁、灵活的方式来编写和管理Spring应用程序。以下是一些常用的Spring注解:

组件扫描和自动装配:

@Component:用于标识一个类为Spring的组件,可以被自动扫描并注册为Bean。

@Service:用于标注业务逻辑层组件,即Service层。

@Repository:用于标注数据访问组件,即DAO层。

@Controller:用于标识一个类为控制器层(Controller)组件,通常用于处理HTTP请求。

@RestController:结合了@Controller和@ResponseBody的功能,用于标识一个类为RESTful风格的控制器,返回的数据会自动转换为JSON或XML等格式。

@Autowired:用于自动注入依赖,可以用在构造函数、成员变量、方法、方法参数上。它默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。

@Qualifier:与@Autowired配合使用,当容器中有多个相同类型的Bean时,通过该注解限定Bean的名称。

Java配置类:

@Configuration:用于标识一个类为配置类,通常与@Bean一起使用。

@Bean:注解在方法上,声明当前方法的返回值为一个Bean。

切面(AOP):

@Aspect:声明一个切面。

@After、@Before、@Around等:用于定义通知(Advice),可直接将拦截规则(切点)作为参数。

其他常用注解:

@Value:用于注入配置文件中的值或表达式的结果。

@Resource:与@Autowired类似,但它是Java自带的注解,可以指定Bean的名称。

@RequestMapping:用于处理HTTP请求,可以指定请求的URL、请求方法、请求参数等。

@PathVariable、@RequestParam等:与@RequestMapping配合使用,用于从请求URL或请求参数中绑定值到方法参数上。

使用这些注解可以大大减少配置文件的内容,提高开发效率,并使代码更加简洁、清晰。但需要注意的是,在使用注解时,必须确保已经开启了注解的支持。

24. mysql事务隔离级别及不同隔离所产生的问题

MySQL支持四种事务隔离级别,它们分别定义了事务在并发执行时如何相互影响。这四种隔离级别是:

读未提交(Read Uncommitted)

读已提交(Read Committed)

可重复读(Repeatable Read)

串行化(Serializable)

1. 读未提交(Read Uncommitted)

定义:一个事务可以读取到另一个未提交事务的修改。

问题:

脏读:一个事务读取到另一个未提交事务的修改,如果那个未提交事务回滚,那么读取到的数据就是无效的(脏的)。

不可重复读:在一个事务内,多次读取同一数据返回的结果有所不同。

幻读:一个事务在读取某些行后,另一个并发事务插入新行,然后前者再次读取同样的范围时,看到了这些新的“幻影”行。

2. 读已提交(Read Committed)

定义:一个事务只能读取到已经提交的事务的修改。

问题:

不可重复读:与读未提交相同,在一个事务内,多次读取同一数据返回的结果有所不同。

幻读:同样可能发生。

3. 可重复读(Repeatable Read)

定义:这是MySQL的默认隔离级别(对于InnoDB存储引擎)。在一个事务内,多次读取同一数据返回的结果是相同的,即使其他事务修改了那些数据。

问题:

幻读:尽管InnoDB通过多版本并发控制(MVCC)来尽量避免幻读,但在某些情况下,如使用RANGE类型的查询条件时,仍可能发生幻读。

4. 串行化(Serializable)

定义:这是最高的隔离级别,它强制事务串行执行,从而避免了上述的所有问题。

问题:

性能下降:由于事务是串行执行的,因此性能会受到严重影响。

总结

选择合适的隔离级别需要权衡并发性和数据一致性。在大多数情况下,可重复读是一个很好的选择,因为它提供了较好的并发性能,同时通过MVCC避免了大部分的数据一致性问题。但是,如果应用对数据一致性要求非常高,或者需要避免幻读,那么可能需要考虑使用串行化隔离级别。

需要注意的是,MySQL中的InnoDB存储引擎通过其特有的MVCC技术,使得在可重复读隔离级别下能够避免脏读和不可重复读问题,但幻读在某些情况下仍可能发生。因此,在设计数据库和编写SQL语句时,仍需要考虑并处理可能出现的并发问题。

25. 方法的重载与重写

方法的重载(Overloading)

方法的重载是指在同一个类中,可以有多个方法具有相同的名称,但它们的参数列表(参数的类型、数量或顺序)必须不同。这样,在调用方法时,Java会根据传递的参数来决定使用哪个方法。

重载的规则:

方法名必须相同。

参数列表必须不同(参数类型、数量或顺序)。

方法的返回类型可以相同也可以不同。

重载的方法可以有不同的访问修饰符。

方法的重写(Overriding)

方法的重写是面向对象编程中的一个核心概念,它发生在子类和父类之间。当子类想要改变从父类继承的某个方法的行为时,可以在子类中定义一个与父类方法签名完全相同的方法,这就是方法的重写。

重写的规则:

方法名、参数列表必须与父类中被重写的方法相同。

访问修饰符不能低于父类中被重写的方法的访问修饰符。

返回类型必须与父类中被重写的方法的返回类型相同或是其子类型。

子类方法抛出的异常类型必须是父类方法抛出异常类型的子集。

被重写的方法不能是final方法,因为final方法无法被改变。

被重写的方法不能是静态方法,因为静态方法和类的关联比和对象的关联更强。

26.Java基本数据类型包括以下几种:

byte:一种8位有符号整数类型,用于处理文件和网络传输等字节级别的数据。它的取值范围为-128到127,占用1个字节的空间。其对应的包装类类型是Byte。

short:一种16位有符号整数类型,可以用于节约内存空间的需求。它的取值范围为-32768到32767,占用2个字节的空间。其对应的包装类类型是Short。

int:一种32位有符号整数类型,是使用最广泛的整数类型。它的取值范围为-2147483648到2147483647,占用4个字节的空间。其对应的包装类类型是Integer。

long:一种64位有符号整数类型,用于处理需要较大值的整数计算。它的取值范围为-9223372036854774808到9223372036854774807,占用8个字节的空间。其对应的包装类类型是Long。

float:一种32位的单精度浮点数类型,用于科学计算和需要高精度计算的场景。它可以表示小数点前8位和小数点后23位的数字。其对应的包装类类型是Float。

double:一种64位的双精度浮点数类型,是使用最广泛的浮点数类型。其对应的包装类类型是Double。

char:用于表示任何字符,它是基于Unicode编码的,所以它可以表示中文字符。其对应的包装类类型是Character。

boolean:一种逻辑类型,用于条件判断和布尔运算,只有两个取值:true和false。其对应的包装类类型是Boolean。

这些基本数据类型在Java编程中非常常用,它们各自有特定的取值范围和字节大小,对应的包装类则提供了更多操作这些基本数据类型的方法。

Java中的基本数据类型各自占用的字节数如下:

byte:占用1个字节(8位)。

short:占用2个字节(16位)。

int:占用4个字节(32位)。

long:占用8个字节(64位)。

float:占用4个字节(32位),遵循IEEE 754标准。

double:占用8个字节(64位),遵循IEEE 754标准。

char:占用2个字节(16位),用于表示Unicode字符。

boolean:在Java虚拟机内部,boolean类型通常被特别处理,可能只占用特定的一个或多个位,但在Java虚拟机规范中并没有明确规定其大小。但在实际应用中,boolean类型通常会被当作一个字节来处理,因为Java虚拟机基于字节码进行操作。不过,这并不意味着boolean类型的变量实际上会占用一个字节的存储空间,这只是一个方便的实现方式。

需要注意的是,基本数据类型的大小是固定的,不会因为运行环境的改变而改变。而对应的包装类(如Byte、Short、Integer、Long、Float、Double、Character、Boolean)则提供了更多操作这些基本数据类型的方法,并且它们都是对象类型,占用的空间会比基本数据类型大。

boolean 类型: 1或者4个

27.Object类常用的方法有哪些

Object类是Java中所有类的基类,提供了许多常用的方法。以下是一些Object类常用的方法:

equals(Object obj):

该方法用于比较两个对象是否相等。默认情况下,它比较的是对象的内存地址是否相同。但在实际应用中,我们通常会根据对象的属性值来重写此方法,以实现内容的比较。

hashCode():

返回对象的哈希码值。哈希码通常用于在哈希表中存储对象,例如HashMap和HashSet等。重写equals方法时,通常也需要重写hashCode方法,以确保当两个对象相等时,它们的哈希码也相同。

getClass():

返回对象的运行时类。此方法被final修饰,不允许子类覆盖。通过getClass()方法,我们可以获取对象的实际类型,这在一些需要动态类型检查的场景中非常有用。

toString():

返回对象的字符串表示形式。默认情况下,它返回的是对象的类名、@符号和对象的哈希码的无符号十六进制表示。但通常,我们会在子类中重写此方法,以返回更具可读性的字符串。

clone():

创建并返回此对象的一个副本。但需要注意的是,此方法是protected的,且只有在对象的类实现了Cloneable接口时,才能调用此方法。否则,会抛出CloneNotSupportedException异常。另外,clone方法实现的是浅复制,如果对象中包含对其他对象的引用,那么这些引用不会被复制。

finalize():

当垃圾回收器确定对象不再被引用时,由对象的finalize()方法自动调用。但需要注意的是,finalize()方法并不能保证一定会被调用,而且其执行时间也是不确定的。因此,在Java中,我们通常不推荐使用finalize()方法来释放资源,而是应该使用try-with-resources语句或显式地关闭资源。

notify()、notifyAll()和wait():

这三个方法用于实现多线程之间的通信。wait()方法使当前线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法。notify()方法随机唤醒在此对象监视器上等待的单个线程,而notifyAll()方法则唤醒在此对象监视器上等待的所有线程。

以上就是Object类的一些常用方法。当然,除了这些方法外,Object类还提供了其他一些方法,如getProtectionDomain()等,但这些方法在一般的应用开发中并不常用。

28.集合的体系结构

在Java的集合框架中,Iterable接口扮演着非常重要的角色。以下是关于Iterable接口的一些关键点和它与其他接口/类的关系:

定义:Iterable接口位于java.lang包下,它定义了一个名为iterator()的抽象方法,该方法返回一个实现了Iterator接口的对象。这使得实现了Iterable接口的类可以使用Java中的for-each循环语法来遍历其元素。

核心地位:Iterable接口是所有集合类(包括Collection、Map等)的根接口。它定义了一种通用的迭代方式,用于遍历集合中的元素。

与Collection接口的关系:java.util.Collection接口直接继承自Iterable接口,是集合框架的核心接口之一。它代表一组对象的集合,提供了对集合元素的基本操作,如添加、删除、查询大小、是否为空、是否包含某个元素等。

迭代器的使用:通过调用Iterable接口的iterator()方法,可以获得一个Iterator对象,该对象包含了hasNext()、next()和remove()等方法,用于遍历和操作集合中的元素。

与Map接口的关系:虽然Map接口不直接继承自Iterable接口,但Map接口有一个entrySet()方法,该方法返回一个实现了Set接口的视图,该视图包含了映射中的键值对。由于Set接口继承了Collection接口,因此可以通过entrySet()方法返回的集合来使用for-each循环遍历Map中的元素。

总的来说,Iterable接口在Java集合框架中起到了桥梁的作用,它使得所有实现了该接口的集合类都可以使用统一的迭代方式进行遍历。

Java中的集合框架是一个用于表示和操作对象的集合的体系结构。这个框架主要包含了Collection接口和Map接口,以及它们各自的子接口和实现类。

1. Collection接口

Collection接口是Java集合框架中的核心接口之一,它表示一组对象的集合。Collection接口的主要特点包括:

集合中的元素不保证有序(某些子接口如List是有序的)。

集合中的元素可以重复(某些子接口如Set是不允许重复的)。

集合中的元素都是对象,不能存储基本数据类型(但可以使用基本数据类型的包装类)。

Collection接口的主要子接口包括:

List:代表有序可重复的集合,可以直接根据元素的索引来访问。常用的实现类有ArrayList、LinkedList和Vector。

ArrayList:基于数组实现,支持快速随机访问,适用于读取操作比较多的场景。

LinkedList:基于链表实现,支持快速插入和删除操作,适用于插入和删除操作比较多的场景。

Vector:与ArrayList类似,但是支持同步操作,适用于多线程环境。

Set:代表无序不重复的集合,只能根据元素本身来访问。常用的实现类有HashSet、TreeSet和LinkedHashSet。

HashSet:基于哈希表实现,元素存储位置由元素的哈希码决定,因此无序。

TreeSet:基于红黑树实现,元素自动排序,不允许插入重复元素。

LinkedHashSet:具有HashSet的查找效率,同时保持元素的插入顺序。

Queue:代表队列集合,是一种特殊的线性表,其只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。常用的实现类有LinkedList、ArrayDeque和PriorityQueue。

2. Map接口

Map接口是Java集合框架中的另一个核心接口,它表示键值对的集合。Map中的每一个元素都是一个键值对(key-value pair),键(key)是唯一的,而值(value)可以重复。Map接口的主要特点包括:

键(key)必须是唯一的,不能重复。

值(value)可以重复。

键和值都是对象,不能存储基本数据类型(但可以使用基本数据类型的包装类)。

Map接口的主要实现类包括:

HashMap:基于哈希表实现,允许null键和null值,不保证映射的顺序。

TreeMap:基于红黑树实现,能够自然排序或者通过Comparator进行定制排序,不允许null键。

LinkedHashMap:具有HashMap的查找效率,同时保持元素的插入顺序。

遍历集合

Java提供了多种遍历集合的方式,包括使用for循环、增强for循环(foreach循环)、迭代器(Iterator)以及Java 8引入的forEach方法和Stream API等。这些遍历方式各有特点,可以根据具体的需求和场景选择合适的遍历方式。

总之,Java的集合框架为Java程序提供了丰富的数据结构支持,使得Java程序能够更加方便地处理和操作对象集合。

29.mysql多表查询的方式

内连接 (INNER JOIN)

隐式内连接:

隐式内连接不使用JOIN关键字,而是通过在WHERE子句中指定连接条件来实现。

显式内连接:

显式内连接使用INNER JOIN关键字来指定连接条件和参与连接的表。

外连接 (OUTER JOIN)

左外连接 (LEFT OUTER JOIN):

左外连接从左表中选择所有的行,即使右表中没有匹配的行。如果没有匹配的行,则结果集中的右表字段将为NULL。

右外连接 (RIGHT OUTER JOIN):

右外连接与左外连接相反,从右表中选择所有的行,即使左表中没有匹配的行。

子查询 (Subquery)

单行单列子查询:

子查询返回单一的值,通常用于比较操作,如=、<、>等。

多行单列子查询:

子查询返回多行但仅一列的结果集,通常与IN、ANY、ALL等操作符一起使用。

多行多列子查询:

多行多列子查询通常与JOIN一起使用,将子查询的结果作为虚拟表。但在某些情况下,您可能想从子查询中选择多列数据并在外部查询中引用它们。

30.java基本数据类型及对应的包装类类型

Java中有八种基本数据类型(primitive data types),每种基本数据类型都对应一个包装类(wrapper class),这些包装类属于Java的标准库java.lang包。下面是基本数据类型及其对应的包装类的列表:

byte - Byte

byte 数据类型是 8 位、有符号的二进制整数。它的取值范围是 -128 到 127(包括)。

short - Short

short 数据类型是 16 位、有符号的二进制整数。它的取值范围是 -32,768 到 32,767(包括)。

int - Integer

int 数据类型是 32 位、有符号的二进制整数。它的取值范围大约是 -2^31 到 2^31 - 1。

long - Long

long 数据类型是 64 位、有符号的二进制整数。它的取值范围大约是 -2^63 到 2^63 - 1。

float - Float

float 数据类型是单精度、32位IEEE 754浮点数。

double - Double

double 数据类型是双精度、64位IEEE 754浮点数。

char - Character

char 数据类型是 16 位 Unicode 字符。它的取值范围是从 '\u0000'(即0)到 '\uffff'(即65,535)。

boolean - Boolean

boolean 数据类型有两个可能的值:true 和 false。

包装类的主要用途是在需要对象的地方使用基本数据类型。例如,当你需要将基本数据类型作为对象在集合类(如ArrayList)中使用时,或者当你需要为基本数据类型提供额外的功能(如方法)时,就可以使用包装类。

此外,包装类还提供了很多有用的方法和常量,如Integer.MAX_VALUE、Double.parseDouble()等。

31.有哪几种方式创建对象

使用new关键字创建对象:

这是最常见和基本的对象创建方式。通过调用类的构造方法(可以是无参或有参构造方法)来实例化对象。使用new关键字可以在堆内存中为对象分配内存,并调用构造方法对对象进行初始化。

使用反射机制创建对象:

反射机制允许我们在运行时获取类的信息并操作类的属性、方法等。通过反射,我们可以通过类的全限定名获取Class对象,并使用该对象的newInstance()方法(或getDeclaredConstructor().newInstance()方法,如果有参数)创建对象。

使用克隆的clone()方法创建对象:

通过实现Cloneable接口并重写clone()方法,我们可以使用clone()方法创建对象的副本。需要注意的是,不是所有的对象都可以被克隆,只有实现了Cloneable接口并且正确重写了clone()方法的对象才可以被克隆。

使用序列化和反序列化创建对象:

序列化是将对象转换为字节流的过程,而反序列化则是将字节流转换回对象的过程。这种方式可以在网络传输或者持久化对象时使用。要支持序列化和反序列化,类需要实现Serializable接口。

32.redis作为缓存有哪些问题?对应的解决方案有哪些 ?

Redis作为缓存时,可能会遇到以下几个问题,以及相应的解决方案:

缓存穿透:

问题描述:当查询一个不存在的数据时,由于缓存中没有该数据,每次查询都会直接访问数据库,导致数据库压力过大。

解决方案:

缓存空对象:当数据库查询结果为空时,将空结果也缓存起来,并设置一个较短的过期时间。这样,后续请求相同的数据时,可以直接从缓存中获取空结果,而不会再次访问数据库。

布隆过滤器:布隆过滤器是一种空间效率极高的概率型数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。在缓存之前,使用布隆过滤器判断数据是否存在,如果不存在则直接返回,避免访问数据库。

缓存击穿:

问题描述:当某个热点数据(被频繁访问的数据)的缓存失效时,大量的请求会同时打到数据库上,导致数据库压力骤增。

解决方案:

设置失效期为永不失效:对于热点数据,可以将其缓存的失效时间设置为一个较长的值,甚至永不失效。

增加互斥锁:在缓存失效时,只允许一个线程去查询数据库,其他线程等待结果即可。这样可以避免多个线程同时查询数据库。

设置二级缓存(本地缓存):在应用程序中设置本地缓存,当Redis缓存失效时,可以从本地缓存中获取数据,减少对数据库的访问。

缓存雪崩:

问题描述:在同一时间点,大量的缓存数据过期或失效,导致所有请求都直接打到数据库上,造成数据库压力过大。

解决方案:

为预计的热key设置过期时间时增加一个随机时间,防止同时过期。

设置为永久不过期:对于重要的热点数据,可以将其缓存的失效时间设置为永久不过期。

保证Redis服务的高可用:使用Redis集群、主从复制等方式,确保Redis服务的高可用性,避免单点故障。

使用互斥锁逐步重新创建所有缓存:在缓存失效时,使用互斥锁确保只有一个线程去重新创建缓存,其他线程等待结果即可。

异步任务全量重新创建缓存:使用异步任务定期全量重新创建缓存,避免缓存集中失效的问题。

数据一致性问题:

问题描述:当使用Redis作为缓存时,由于缓存与数据库的数据可能存在不一致的情况,导致数据不一致问题。

解决方案:

使用双写一致性方案:在更新数据时,同时更新数据库和Redis缓存,确保数据的一致性。但需要注意双写时可能存在的并发问题。

延迟双删策略:在更新数据时,先删除Redis缓存中的旧数据,然后更新数据库;在更新数据库后,再异步删除Redis缓存中的旧数据。这样可以避免在更新过程中其他线程读取到旧数据的问题。

监听数据库变更:使用数据库的变更监听功能(如MySQL的binlog),当数据库数据发生变化时,自动更新Redis缓存中的数据。这样可以确保Redis缓存中的数据与数据库中的数据保持一致。

33. 获取Class对象有几种方式

使用.class语法:

这是最常用的方法,直接在类名后面加上.class。

使用对象的getClass()方法:

如果你已经有一个类的实例,可以使用getClass()方法获取其Class对象。注意,getClass()方法返回的是具体的类类型(即String.class),而不是父类或接口。

使用Class.forName()方法:

你可以使用Class.forName()方法通过类的全限定名来获取Class对象。此方法常用于在运行时动态加载类。注意,如果类不存在于类路径中,Class.forName()会抛出ClassNotFoundException。

使用类的加载器(ClassLoader):

你可以使用ClassLoader的loadClass()方法来加载类并获取其Class对象。这通常在你需要更精细地控制类的加载过程时使用。这里的ClassLoader可以是系统类加载器、自定义类加载器等。

使用Java的内置类型包装类:

对于Java的原始数据类型(如int, double等),它们的包装类(如Integer, Double等)都有一个静态的TYPE字段,该字段是一个Class对象,代表该原始数据类型的类。

注意,这与使用.class语法(如int.class)是不同的,因为原始数据类型没有.class属性。但是,你可以使用它们的包装类的.TYPE字段来获取对应的Class对象。

使用数组的getClass()方法(对于数组类型):

对于数组类型,你可以使用数组的getClass()方法来获取其Class对象。但请注意,这将返回数组的Class对象,而不是数组元素类型的Class对象。要获取数组元素类型的Class对象,你可以使用getComponentType()方法。

34.反射有哪些重要的操作,什么是反射?

在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时检查和修改类、接口、字段和方法的行为。以下是Java反射中一些重要的操作:

获取Class对象:

使用.class语法。

使用对象的getClass()方法。

使用Class.forName()方法通过类的全限定名获取。

创建对象:

通过Class对象的newInstance()方法(对于无参构造函数)。

使用Class对象的getDeclaredConstructor()方法获取Constructor对象,然后通过Constructor对象的newInstance()方法创建对象(对于有参构造函数)。

获取类成员变量:

使用Class对象的getDeclaredFields()方法获取所有声明的字段(包括私有字段)。

使用Class对象的getFields()方法获取所有公共字段。

使用Field对象的getName()方法获取字段名。

获取和设置成员变量的值:

对于公共字段,可以直接使用Field对象的get()和set()方法。

对于私有字段,需要先调用Field对象的setAccessible(true)方法取消访问检查,然后再使用get()和set()方法。

获取类的方法:

使用Class对象的getDeclaredMethods()方法获取所有声明的方法(包括私有方法)。

使用Class对象的getMethods()方法获取所有公共方法(包括从父类和接口继承的方法)。

使用Method对象的invoke()方法调用方法。

获取类的构造方法:

使用Class对象的getDeclaredConstructors()方法获取所有声明的构造方法。

使用Class对象的getConstructors()方法获取所有公共构造方法。

访问私有成员:

通过setAccessible(true)方法可以取消Java语言的访问检查,允许访问类的私有成员。

动态代理:

使用java.lang.reflect.Proxy类和InvocationHandler接口可以创建动态代理对象,在运行时动态地实现接口或代理类。

类加载器:

Java的类加载器负责在运行时加载类。通过类加载器,可以在运行时动态地加载和卸载类。

这些反射操作使得Java程序在运行时具有更高的灵活性和动态性,但也需要注意其性能开销和安全性问题。

35.Java中sleep和wait的区别

在Java中,sleep和wait都是用于线程控制的方法,但它们之间存在显著的差异。以下是sleep和wait的主要区别:

所属类和方法签名:

sleep是Thread类的一个静态方法,用于使当前线程暂停执行一段时间。其方法签名是public static void sleep(long millis)和public static void sleep(long millis, int nanos)。

wait是Object类的一个方法,用于使当前线程等待,直到其他线程调用此对象的notify()或notifyAll()方法,或者超过指定的时间量。其方法签名是public final void wait(), public final void wait(long timeout), 和 public final void wait(long timeout, int nanos)。

使用上下文:

sleep可以在任何情况下使用,它仅仅是让当前线程暂停执行一段时间。

wait只能在同步方法或同步代码块中使用,因为它依赖于对象的监视器锁(monitor lock)。

释放锁:

当线程调用sleep方法时,它不会释放持有的任何锁。这意味着如果线程在持有某个对象的锁时调用sleep,其他线程仍然无法访问该对象,直到原始线程醒来并释放锁。

当线程调用wait方法时,它会释放当前持有的对象的监视器锁。这允许其他线程进入同步方法或同步代码块并访问该对象。

唤醒机制:

sleep方法在指定的时间过去后会自动唤醒。

wait方法需要其他线程调用notify()或notifyAll()方法来唤醒,或者等待超时。

异常处理:

两者都会抛出InterruptedException,当线程在等待、睡眠或进行其他可以中断的阻塞状态时,如果其他线程中断了当前线程,那么会抛出这个异常。

用途:

sleep通常用于模拟等待、暂停线程执行等场景。

wait通常用于线程间的通信和协调,如生产者-消费者问题中的等待-通知机制。

返回值:

sleep没有返回值。

wait在方法被唤醒或抛出InterruptedException之前,会一直阻塞。如果调用了带参数的wait方法,并且超时了,那么wait方法会返回,但不会抛出异常。

在实际应用中,wait和notify/notifyAll通常用于实现更复杂的线程间通信和同步,而sleep则用于简单的线程暂停。

36. java中this关键字作用, super关键字作用

this关键字的作用

引用当前对象的成员变量:当成员变量和局部变量重名时,可以使用this来区分它们。

调用当前对象的成员方法:虽然在大多数情况下,我们可以直接调用成员方法,但在某些情况下,使用this可以使代码更清晰。

作为方法的返回值:在某些情况下,你可能希望方法返回当前对象本身,这时可以使用this。

在构造函数中调用另一个构造函数(自Java 8起):可以使用this(...)来调用当前类的另一个构造函数。

super关键字的作用

引用父类的成员变量:当子类隐藏了父类的成员变量时(即子类和父类有同名的成员变量),可以使用super来引用父类的成员变量。

调用父类的成员方法:当子类重写了父类的成员方法时,可以使用super来调用父类的成员方法。

调用父类的构造函数:在子类的构造函数中,可以使用super(...)来调用父类的构造函数。这是子类构造函数必须做的第一件事情(除非父类有无参构造函数且子类构造函数没有显式调用其他构造函数)。

在匿名内部类中使用外部类的成员:在匿名内部类中,super引用的是外部类的父类(如果有的话),而不是外部类本身。但在大多数情况下,我们会在匿名内部类中使用外部类的成员,这时直接使用外部类的成员名即可。但在某些需要区分的情况下,可以使用OuterClass.this来引用外部类的实例。

37.==和equals区别

基本类型与对象类型:

对于基本数据类型(如int, char, boolean等),== 用于比较它们的值是否相等。

对于对象引用类型,== 用于比较两个引用是否指向内存中的同一个对象(即比较它们的内存地址是否相同)。

equals() 方法:

equals() 方法是Object类的一个方法,用于比较两个对象的内容是否相等。但是,Object类的equals()方法默认实现的是和==相同的比较(即比较引用是否相同)。

通常,Java中的类(如String, Integer, 自定义类等)会重写equals()方法,以提供更有意义的比较逻辑(例如,比较两个String对象的内容是否相同)。

null值处理:

使用==比较时,如果其中一个引用是null,那么结果就会是false(因为null不指向任何对象)。

在使用equals()方法时,如果调用它的对象是null,那么将会抛出NullPointerException。因此,在使用equals()方法之前,通常需要检查对象是否为null。

对称性和一致性:

equals()方法必须满足对称性(即a.equals(b)和b.equals(a)的结果应该相同)和一致性(即多次调用a.equals(b)的结果应该相同,前提是a和b都没有被修改)。

38.列举一些常用软件或框架的端口号

8080:这是Tomcat默认的HTTP端口。当然,它也可以被配置为使用其他端口。

3306:MySQL数据库的默认端口号,用于数据库连接和数据传输。

MongoDB:默认端口为27017,这是一个流行的NoSQL文档数据库。

Elasticsearch:这是一个基于Lucene的搜索和分析引擎,默认HTTP端口是9200。

Redis:默认端口为6379,它是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息代理。

Nginx:虽然Nginx本身不是Java软件,但它经常与Java应用一起使用作为反向代理。Nginx的默认HTTP端口是80,HTTPS默认端口是443。

1521:Oracle数据库的默认端口号,用于数据库连接和数据传输。

SQL Server:默认端口为1433,这是Microsoft的关系型数据库管理系统。

rabbitmq 5672

39.rabbitmq 模式有哪些?特点都是啥

RabbitMQ支持多种消息传递模式,每种模式都有其特定的用途和特点。以下是RabbitMQ的一些常见模式及其特点:

简单模式(Simple):

特点:一个生产者将消息放入队列,一个消费者从队列中获取并消费消息。消息一旦被消费者拿走,就会自动从队列中删除。

应用场景:简单的消息队列场景,如聊天应用(一个服务器和多个客户端之间的通信)。

工作模式(Work):

特点:也称为“能者多劳队列”,一个生产者将消息放入队列,多个消费者同时监听同一个队列,消息被消费者消费后自动从队列中删除。消费者之间通过竞争关系来消费消息。

应用场景:需要并行处理大量消息的场景,如订单处理、日志处理等。

发布/订阅模式(Publish/Subscribe):

特点:也称为fanout模式,生产者将消息发送到交换机(Exchange),交换机将消息广播到所有与之绑定的队列中,每个队列中的消费者都可以消费消息。

应用场景:需要向多个消费者发送相同消息的场景,如实时新闻推送、公告发布等。

路由模式(Routing):

特点:生产者将消息发送到交换机,交换机根据路由规则将消息发送到与规则匹配的队列中。消费者从对应的队列中获取并消费消息。

应用场景:需要根据不同条件将消息发送到不同队列的场景,如订单处理系统(根据订单类型发送到不同的处理队列)。

主题模式(Topics):

特点:生产者将消息发送到交换机,交换机使用模糊匹配规则将消息发送到与规则匹配的队列中。消费者从对应的队列中获取并消费消息。

应用场景:需要更复杂的路由规则的场景,如日志处理系统(根据日志级别和来源发送到不同的处理队列)。

RPC模式(Remote Procedure Call):

特点:客户端发送带有请求ID和回复队列的RPC请求到RabbitMQ,服务器处理请求后将结果发送到指定的回复队列中,客户端从回复队列中获取结果。

应用场景:需要实现远程过程调用的场景,如分布式系统中的服务调用。

RabbitMQ的主要特点包括:

可靠性:基于AMQP协议,提供了持久化、可靠的消息传递机制,确保消息在发送和接收之间进行可靠传输,即使在出现故障的情况下也能保证消息的安全性。

灵活性:支持多种消息传递模式,允许开发人员根据应用程序的需求来选择合适的消息模式,实现灵活的消息传递。同时支持水平扩展,可以在需要时添加更多的节点来处理更多的消息。

应答模式:支持消息应答机制,确保消息在处理过程中不会丢失。如果一个消费者在处理消息时挂掉,RabbitMQ会将该消息重新分配给其他消费者进行处理。

以上是关于RabbitMQ的常见模式及其特点的简要介绍。如需更详细的信息,请参考RabbitMQ的官方文档或相关教程。

40.rabbitmq如何实现消息的可靠性

三方面:发送者,rabbitMQ,消费者

发送者:两种方式:

1.发送者重连:有的时候由于网络波动,可能会出现发送者连接MQ失败的情况,通过配置我们可以开启连接失败后的重连机制

spring:

  rabbitmq:

    connection-timeout: 1s  # 设置MQ连接超时时间

    template:

      retry:

        enabled: true #开启超时重试机制

        initial-interval: 1000ms  #失败后的初始等待时间

        multipliter: 1  # 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multipliter

        max-attempts: 3 #最大重试次数

当网络不稳定的时候,利用重试机制可以有效提高消息发送的成功率.不过,SpringAMQP提供的重试机制是阻塞式的重试,也就是说多次重试等待的过程中,当前线程是被阻塞的,会影响业务性能.

如果对于业务性能有要求,建议禁用重试机制.如果一定要使用,请合理配置等待时长和重试次数,当然也可以考虑使用异步线程来执行发送消息的代码.

2.发送者确认

41.Java中的访问修饰符有哪些,各自的范围会什么?

Java中的访问修饰符用于控制类、变量、方法和构造器的访问权限。Java提供了四种访问修饰符:private、default(也称为包级私有)、protected和public。下面是它们各自的访问范围:

private(私有)

访问范围:只在定义该成员的类内部可见。

成员变量和成员方法都可以被声明为private。

private是最严格的访问级别。

default(默认,也称为包级私有)

访问范围:在同一个包(package)内的类可见。

如果一个类、变量、方法或构造器没有指定访问修饰符,则它默认使用default访问级别。

default访问级别仅允许同包内的类访问。

protected(受保护)

访问范围:在同一个包内的类可见,以及在其他包中的子类也可见。

protected可以修饰成员变量、成员方法、构造器以及内部类。

protected提供了比default更宽松的访问权限,允许子类访问父类的protected成员。

public(公共)

访问范围:对所有类可见,无论它们是否在同一个包中。

public可以修饰类、变量、方法、构造器和接口。

public访问级别是最宽松的,允许任何类访问。

注意:

类只能被声明为public或default(默认,没有修饰符)。

接口默认是public的,但也可以显式地声明为public。

成员变量、成员方法和构造器都可以被声明为private、default、protected或public。

顶层类(即不是内部类的类)不能是private或protected的,它们只能是public或default的。

枚举常量默认是public static final的,因此它们总是可以被访问的。

局部变量(在方法或代码块中定义的变量)不能有访问修饰符,因为它们的可见性由它们所在的作用域决定。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容