【八股总结】至今为止遇到的八股(下半)

这是总结的下半部分,上半在这:https://www.cnblogs.com/DAYceng/p/18037696
可以见到,下半出现了一些Java相关的东西,懂的都懂,唉

Java基础

Java静态类型有哪些

在Java中,静态类型(Static Type)是指在编译时就确定了变量的类型,并且在运行时保持不变的类型。也就是说,每个变量都有一个静态类型,该类型在编译时就已经确定,并且在程序运行期间不能改变。

主要包括以下几种:

1、基本数据类型:byte、short、int、long、float、double、char和boolean

2、引用类型:类(Class)、接口(Interface)、数组(Array)等

Java数据类型的长度?按字节说

常见数据类型的长度(按字节计算):

  • byte:1字节
  • short:2字节
  • int:4字节
  • long:8字节
  • float:4字节
  • double:8字节
  • char:2字节
  • boolean:1字节(但实际上通常不会只占用1字节)

需要注意的是,这些长度是基于大多数Java虚拟机实现的默认情况。

此外,还有一些引用类型的长度也是固定的:

  • 对象引用:4字节(32位系统)或8字节(64位系统)
  • 数组:根据数组元素类型和数组长度来计算

异常处理的常用场景

调试代码的时候,在适当的地方加入异常处理逻辑可以有效捕获并定位代码的问题,防止程序崩溃;

对于用户来说,异常捕获可以在程序发生错误的时候返回提示,使用户更加容易理解和解决问题。

此外,程序发生异常的时候通常来不及处理获取的资源,这有可能会导致更严重的问题,因此异常处理中我们还可以对资源进行释放操作。

string和stringbuffer

在Java中,StringStringBuffer都是用于处理字符串的类

简单来说:

  • String是一个不可变的类,创建之后就不能修改。当进行字符串拼接或修改操作时会创建一个新的String对象,原有的不变
  • StringBuffer是一个可变的类,进行字符串的修改操作不会创建新的对象,因此需要考虑线程安全问题,多线程环境下需要进行同步操作
  • 如果需要频繁进行字符串拼接、修改或者使用在多线程环境下,建议使用StringBuffer
  • 如果字符串不需要被修改,或者只进行少量的字符串操作,可以使用String
  1. String(字符串):
    • String是一个不可变(immutable)的类,一旦创建,其值就不能被修改。
    • 字符串拼接或修改操作会创建新的String对象,而原始的String对象保持不变。
    • 由于不可变性,String在多线程环境下是线程安全的。
    • String提供了丰富的方法用于字符串操作,如获取子串、拼接、替换等。
    • 由于不可变性,频繁的字符串拼接操作会导致内存开销和性能问题。
  2. StringBuffer(字符串缓冲区):
    • StringBuffer是一个可变(mutable)的类,可以进行字符串的修改和拼接操作。
    • 字符串的修改操作不会创建新的对象,而是直接在原始的StringBuffer对象上进行修改。
    • 由于可变性,StringBuffer在多线程环境下使用时需要进行同步操作,否则可能出现线程安全问题。
    • StringBuffer提供了丰富的方法用于字符串的增删改查操作,如追加、插入、删除、替换等。
    • 由于可变性,适合频繁进行字符串拼接和修改的场景,避免了频繁创建新对象的开销。

hashmap和hashtable的区别,扩容机制

HashMap和Hashtable都是Java中的映射容器,用于存储键值对。
【防盗链提醒:爬虫是吧?原贴在:https://www.cnblogs.com/DAYceng】
主要区别如下:

  1. 线程安全性:Hashtable是线程安全的,而HashMap则不是。如果需要在多线程环境下使用HashMap,可以使用ConcurrentHashMap。
  2. 空键/值:Hashtable不允许空键或空值,而HashMap则允许一个空键和多个空值
  3. 继承关系:Hashtable是Dictionary类的子类,而HashMap是AbstractMap类的子类。
  4. 性能:由于Hashtable是线程安全的,因此在单线程环境下,HashMap的性能通常比Hashtable更好

关于扩容机制,

当HashMap或Hashtable中的元素数量超过了其容量的75%时,就会进行扩容操作

HashMap的扩容机制是将容量增加到原来的两倍,并将所有元素重新分配到新的桶中。

Hashtable的扩容机制是将容量增加到原来的两倍加一,并将所有元素重新分配到新的桶中。

在扩容期间,HashMap或Hashtable可能会暂停对外部的操作,以便将元素重新分配到新的桶中。这可能会导致一些操作的延迟和性能下降。因此,在设计应用程序时,需要考虑到HashMap或Hashtable的扩容机制对性能的影响。

Java文件编译过程

Java文件的编译过程可以分为几个主要步骤。

首先,编译器会对源代码进行词法分析和语法分析。词法分析将源代码分解为标记,例如关键字、标识符和运算符等。语法分析阶段会检查这些标记是否按照正确的语法结构组织。

接下来,编译器会生成一个抽象语法树(AST),它以源代码的语法结构为基础,用于表示程序的抽象语法结构。AST可以帮助编译器理解源代码的结构和含义。

在语义分析阶段,编译器会对AST进行进一步的分析,检查语义错误和类型匹配等问题。如果发现错误,编译器会生成相应的错误信息。同时,编译器还会生成中间代码,例如Java字节码。

然后,编译器将中间代码转换为字节码,字节码是一种与特定平台无关的二进制格式,可以在Java虚拟机(JVM)上运行。此外,编译器还会进行一些优化操作,以提升程序的性能和效率。

最后,生成的字节码可以被Java虚拟机加载和执行。虚拟机会负责将字节码解释或编译成特定平台的机器码,并执行程序的逻辑。

JVM

Jvm工作流程

JVM是Java程序的运行环境,它负责将Java字节码转换为特定平台的机器码,并执行程序的逻辑

首先,JVM会加载字节码文件。字节码文件通常是由Java编译器生成的,其中包含了程序的指令和数据。

JVM会逐行读取字节码文件,并将其转换为内部表示形式。

接下来,JVM会对字节码进行验证。

验证过程主要包括三个方面:文件格式验证、元数据验证和字节码验证。

验证通过后,JVM会将字节码解释或编译成机器码。

解释执行是逐条解释字节码指令并执行相应操作,而编译执行是将字节码转换为本地机器码,以提高执行效率。JVM通常会使用即时编译器(Just-In-Time Compiler,JIT)来进行动态编译,将热点代码(经常执行的代码)编译成机器码。

在执行过程中,JVM会提供内存管理和垃圾回收功能。JVM会将内存划分为不同的区域,如堆、栈和方法区。

栈用于存储方法调用和局部变量,方法区用于存储类信息和静态变量。

谈谈什么是反射

反射(Reflection)是Java语言的一个特性,它允许程序在运行时动态地获取类的信息并操作类或对象。

有点类似于c++中的运行时类型信息这么一个特性

通过反射,我们可以获取到类的信息,还可以通过newInstance()方法动态的创建对象并调用类方法

抽象类和接口的区别

抽象类和接口是面向对象编程中的两个重要概念。首先,抽象类是一个类,可以包含抽象方法和非抽象方法,而接口是一种完全抽象的类,只包含抽象方法和常量的定义。

抽象类可以被继承,一个子类只能继承一个抽象类。子类需要实现抽象类中的抽象方法,并可以覆盖非抽象方法。而接口可以被实现,一个类可以实现多个接口。实现接口的类需要提供接口中定义的所有方法的具体实现。

抽象类可以有构造函数,用于初始化抽象类的成员变量和执行其他必要的操作。但是接口不能有构造函数,因为接口只是一个行为和结构的定义,没有具体的实例化对象。

==和equal有什么区别?

在Java中,使用"=="和"equals"方法来比较两个String对象是有区别的。

""用于比较两个对象的引用是否相等,也就是两个对象是否指向同一块内存地址。如果两个String对象的引用相同,则""返回true;否则返回false。例如:

String str1 = "hello";
String str2 = "hello";

System.out.println(str1 == str2); // true

在这个例子中,str1和str2都是指向相同的字符串常量池中的"hello"对象,因此"=="运算符返回true。

然而,在下面的例子中,尽管s1和s2的内容相同,但它们不是指向同一个对象,因此"=="返回false:

String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1 == s2); // false

"equals"方法用于比较两个对象的内容是否相等。当两个String对象的内容相同时,"equals"方法返回true。例如:

String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1.equals(s2)); // true

总之,"=="用于比较两个对象的引用是否相等,而"equals"方法用于比较两个对象的内容是否相等。在比较两个字符串时,通常应该使用"equals"方法。

有了解什么中间件吗?

什么是中间件

中间件是指在客户端和服务器端之间,或者在不同的应用程序之间起到连接、沟通、协调作用的软件。它可以协助多个应用程序之间进行数据传输、消息传递、负载均衡、流程控制等操作,提高服务器的可用性、性能、可伸缩性和安全性。

中间件根据功能和设计理念可以分为多种类型,包括消息队列中间件、缓存中间件、Web应用服务器、集群中间件、RPC中间件等。每种中间件都有着特定的应用场景和功能特点,开发人员需要根据实际需求选择合适的中间件来解决问题。

举例来说,消息队列中间件如ActiveMQ、RabbitMQ可以支持异步通信、解耦系统;缓存中间件如Memcached、Redis可以提高数据读取性能;Web应用服务器如Apache、Nginx可以解决Web应用程序之间的负载均衡、静态和动态内容分离等问题;而集群中间件如HAProxy、Keepalived可以提高服务器的可用性和性能。

在实际应用中,中间件的使用需要结合具体业务需求和技术特点来选择,并且要了解中间件的正确使用方法和注意事项,以确保其有效运行。中间件的合理使用可以提高查询效率、降低IO负载,增加服务器性能和可靠性,促进数据共享和流程协同,但同时也要注意避免过度使用,以免造成不必要的负担和浪费。

Kafka

Redis

RabbitMQ

spring

什么是spring?

一般说的spring是指spring framework,是一种轻量级框架,其中集合了很多的模块以方便开发。

例如:Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现I0C和DI的基础,AOP
组件用来实现面向切面编程。

什么是Bean?

在Spring框架中,Bean是一个被实例化、组装并通过Spring容器进行管理的对象

这个对象可以是任何Java对象,例如POJO(Plain Old Java Object)、JavaBean、数据访问对象(DAO)、服务类等等。

在Spring框架中,开发者可以通过声明式配置或基于注解的方式来定义Bean。

通过配置文件或注解,可以指定Bean的作用域、依赖关系、初始化和销毁方法等属性。Spring容器会根据这些配置信息在需要的时候自动创建Bean,并将它们注入到其他对象中。

Bean与容器的关系:

  1. Bean是Spring框架中由容器管理的对象,它可以是任何普通的Java对象,由Spring容器负责实例化、装配和管理。
  2. Spring容器负责创建、配置和管理Bean对象,它通过BeanDefinition来描述Bean的配置元数据,包括Bean的类名、属性值、作用域等信息。
  3. 容器根据BeanDefinition来实例化Bean对象,并将它们装配成完整的应用程序,开发者可以通过配置文件或注解来描述Bean的定义和依赖关系。
  4. 容器还提供了对Bean的生命周期管理、依赖注入、AOP等功能,使得开发者可以更加方便地开发和维护应用程序。

Bean的生命周期

ps:对于prototype类型的bean,Spring在创建好交给使用者使用之后,就不在管理其后续的生命周期了

首先是容器启动阶段

BeanDefinitionReader通过XML文件读取到每个Bean的配置信息之后,使用BeanDefinition表示。在Bean实例化之前,Spring框架提供了一个重要接口BeanFactoryPostProcessor用于对Bean进行一些额外处理。

​ BeanFactoryPostProcessor主要有两个常见的使用场景:

​ (1)在容器初始化阶段对Bean的定义进行修改;

​ (2)注册新的Bean定义,在容器实例化Bean之前就可以将新的Bean定义加入到容器中;

假设我们有一个基于 Spring 的应用程序,并且在应用程序中有一些需要加密处理的属性,比如数据库的用户名和密码。这时候,我们希望在应用程序启动的时候自动对这些敏感信息进行加密,而不是在每个 Bean 中手动编写加密逻辑。

编写一个自定义的 BeanFactoryPostProcessor,它可以在容器实例化 Bean 之前拦截 BeanDefinition,并对其中需要加密的属性进行加密处理。

具体的实现可以通过扫描 BeanDefinition 中的属性,判断是否需要加密,如果需要加密,则对属性值进行加密处理,然后更新 BeanDefinition。

接下来到了实例化Bean的阶段

​ 在实例化阶段,容器通过反射机制创建 Bean 的实例。这通常是通过调用类的构造方法来实现的。容器为 Bean 的实例设置属性值,包括基本类型、引用类型以及其他 Bean 的引用。

​ 如果 Bean 实现了一些 Aware 接口,容器会通过回调的方式将特定的资源或接口注入到 Bean 中,比如ApplicationContextAware 可以获得容器上下文。

容器还提供了一个扩展点(BeanPostProcessor接口),开发者可以通过实现该接口,在 Bean 初始化前后添加自定义的逻辑。

上述步骤完成后就算创建好一个Bean了,使用完成后还要进行销毁

销毁Bean

Spring容器关闭时,它会对所有的 Bean 进行销毁(当然也可以手动销毁Bean但是不建议)

关闭有以下步骤:

  • 关闭前进行回调,如果 Bean 实现了 DisposableBean 接口,则容器会在执行销毁动作前调用 destroy() 方法。

    Spring也提供了拓展接口在 Bean 销毁前后添加自定义的逻辑。

  • 最后,容器会调用 Bean 的 finalize() 方法进行销毁操作,释放占用的资源。

流程总结

在 Spring 中,Bean 的生命周期是由 Spring 容器来管理的。

当我们启动 Spring 容器时,它会读取 Bean 的配置信息,将其转化为 BeanDefinition 对象并注册到 BeanDefinitionRegistry 中。在创建 Bean 实例时,Spring 容器会先通过反射或者 CGLIB 等方式创建 Bean 的实例,然后根据 BeanDefinition 中的属性信息对 Bean 进行初始化和装配。在这个过程中,Spring 容器会检测 Bean 是否实现了一些特定的接口,如 BeanFactoryAware、ApplicationContextAware 等,从而将一些必要的资源注入到 Bean 中。

在 Bean 初始化的过程中,Spring 容器还提供了两个拓展点:BeanFactoryPostProcessor 和 BeanPostProcessor。BeanFactoryPostProcessor 允许我们在 Bean 实例化之前修改 BeanDefinition 的信息,而 BeanPostProcessor 则允许我们在 Bean 实例化完成之后对 Bean 进行增强处理。对于 BeanPostProcessor 接口的实现,它主要用于 AOP 的实现。

在 Bean 初始化完成后,如果 Bean 实现了 InitializingBean 接口,则会执行其 afterPropertiesSet() 方法,如果 Bean 在 XML 中配置了 init-method,则会执行对应的自定义初始化方法。在 Spring 容器关闭时,会触发对所有单例 Bean 的销毁操作,包括调用实现了 DisposableBean 接口的 destroy() 方法和 XML 中配置的自定义销毁方法。

总的来说,Spring 容器通过管理 Bean 的生命周期,帮助开发者实现了 Bean 的自动化装配和管理,减少了代码的冗余性和复杂度。同时,通过拓展点的机制,也允许开发者对 Spring 容器进行扩展和定制,以满足更多的业务需求。

如何定义Bean的范围?

在Spring中定义一个时,我们也可以为Bean声明一个范围,它可以通过Bean定义中的scope属性定义。

例如,当Spring每次需要生成一个新的Bean实例时,bean'sscope属性就是原型。

另一方面,当每次需要Spring都必须返回相同的Bean实例时,Bean scope属性必须设置为singleton。

Bean的作用域

  • singleton:唯一Bean实例,Spring中的Bean默认都是单例的。
  • prototype: 每次请求都会创建一个新的Bean实例。
  • request:每一次HTTP请求都会产生一个新的Bean,该bean仅在当前HTTP request内有效
  • session:每一次HTTP请求都会产生一个新的Bean,该bean仅在当前HTTP session内有效。
  • global-session: 全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码 (例如HTML) 片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。

将一个类声明为Spring的bean的注解有哪些?

我们一般使用@Autowired注解去自动装配bean。而想要把一个类标识为可以用@Autowired注解自动装配的bean,可以采用以下的注解实现:
1.@Component注解。通用的注解,可标注任意类为Spring组件。如果一个Bean不知道属于哪一个层,可以使用@Component注解标注

​ 2.@Repository注解。对应持久层,即DAO层,主要用于数据库相关操作

​ 3.@Service注解。对应服务层,即Service层,主要涉及一些复杂的逻辑,需要用到DAO层(注入)。

​ 4.@Controller注解。对应Spring MVC的控制层,即Controller层,主要用于接受用户请求并调用Service层的方法返回数据给前端页面。

@Component和@Bean的区别是什么?

@Component 和 @Bean 都是在Spring框架中用来注册Bean的注解,它们的主要区别在于作用对象和作用范围。

@Component注解适用于标识任意类型的Bean,由Spring自动扫描并注册;而@Bean注解适用于手动配置Bean的创建过程,通常用于定制化的Bean创建场景,例如第三方库的Bean、不易改动的类的Bean等。

​ 1.作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。

​ 2.@Component注解通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径)。@Bean注解通常是在标有该注解的方法中定义产生这个bean,告诉Spring这是某个类的实例,当我需要用它的时候还给我。

​ 3.@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册bean。比如当引用第三方库的类需要装配到Spring容器的时候,就只能通过@Bean注解来实现。

什么是IOC?如何实现?

I0C (lnversion OfControll,控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交给I0C容器来管理,并由IOC容器完成对象的注入。

意思就是将创建对象的控制权从自己硬编码new的一个对象反转到了第三方身上

I0C的主要实现方式是依赖注入

Spring中的依赖注入方式有: 构造方法注入、settter注入、接口注入

目的: 帮助我们接耦各种有依赖关系的业务对象之间的绑定关系

I0C容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。

IOC-Provider

虽然不需要我们自己来做绑定关系,但是这部分的工作还是需要有人来实现的,所以IOC Provider就担任了这个角色,同时IOC Provider的职责也不仅仅这些,其基础职责如下:

1、业务对象的构建管理:

​ IOC中,业务对象不需要关心所依赖的对象如何构建如何获取,这部分任务交由IOC Provider

2、业务对象之间的依赖绑定:

​ 通过结合之前构建和管理的所有业务对象,以及各个业务对象之间可识别的依赖关系,将这些对象所依赖的对象注入绑定。从而保证每个业务对象在使用的时候,可以处于就绪状态。

Spring的IOC容器

担任了IOC Provider的职责,同时在此基础上,还增加了对Bean生命周期的管理、AOP支持内容。

从整体来看Spring的IOC容器的作用,共分为两部分:

1、容器启动阶段:

​ 以某种方式将配置的Bean信息 (XML、注解、Java编码) 加载如整个Spring应用

2、Bean实例化阶段:

​ 将加载的Bean配置信息组装成应用需要的业务对象。在此基础上,还充分运用了这两个阶段不同的特点,都预留了拓展钩子,供我们根据业务场景进行自定义拓展。

一些核心的接口、类

Resource

用于解决IOC容器中的内容从哪里来的问题,也就是 配置文件从哪里读取、配置文件如何读取 的问题

BeanDefinition

用于解决 Bean 的具体定义问题,包括 Bean 的名字是什么、它的类型是什么,它的属性赋予了哪些值或者引用。

也就是 如何在IOC容器中定义一个 Bean,使得IOC容器可以根据这个定义来生成实例 的问题。

BeanFactoy

用于解决IOC容器在 已经获取 Bean 的定义的情况下,如何装配、获取 Bean 实例 的问题。

ApplcationContext

对上述的内容进行了功能的封装,解决 根据地址获取 IOC 容器并使用的问题。

什么是AOP? 有哪些AOP的概念?

AOP (Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。

可以理解为utils

spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用IDK动态代理去创建代理对象,而对于没有实现接口的对象,就无法使用IDK动态代理,转而使用CGib动态代理生成一个被代理对象的子类来作为代理。

什么tm的是动态代理?后面讲

AOP包含的几个概念

Jointpoint (连接点)

​ 具体的切面点的抽象概念,可以是在字段、方法上,Spring中具体表现形式是PointCut (切入点),仅作用在方法上

Advice(通知)

​ 在连接点进行的具体操作,如何进行增强处理的,分为前置、后置、异常、最终、环绕五种情况。

目标对象

​ 被AOP框架进行增强处理的对象,也被称为被增强的对象。

AOP代理

​ AOP框架创建的对象,简单的说,代理就是对目标对象的加强。Spring中的AOP代理可以是IDK动态代理,也可以是CGLIB代理。

Weaving(织入)

​ 将增强处理添加到目标对象中,创建一个被增强的对象的过程

【防盗链提醒:爬虫是吧?原贴在:https://www.cnblogs.com/DAYceng】

总结为一句话就是:

​ 在目标对象 (target object)的某些方法 (jintpoint)添加不同种类的操作(通知、增强操处理),最后通过某些方法 (weaving、织入操作)实现一个新的代理目标对象。

AOP的应用场景

  • 记录日志(调用方法后记录日志)
  • 监控性能(统计方法运行时间)
  • 权限控制(调用方法前校验是否有权限)
  • 事务管理(调用方法前开启事务,调用方法后提交关闭事务)
  • 缓存优化(第一次调用查询数据库,将查询结果放入内存对象,第二次调用,直接从内存对象返回,不需要查数据库)

AOP Advice通知类型有哪些?

拦截器(Interceptor)是在AOP中用于拦截和处理JoinPoint的组件。它可以在JoinPoint的周围执行一系列的操作,比如添加额外的逻辑、修改参数、记录日志等。拦截器可以在目标方法执行前、执行后或抛出异常时进行拦截,并根据需要进行相应的处理。

在Spring AOP中,拦截器被称为Advice(通知),它是实现了特定接口的类或者使用注解进行标记的方法。Spring AOP提供了多种类型的Advice,包括:

  1. 前置通知(Before Advice):在目标方法执行之前执行的通知。可以通过前置通知在目标方法执行前添加额外的逻辑。
  2. 后置通知(After Advice):在目标方法执行之后执行的通知。无论目标方法是否抛出异常,后置通知都会被执行。
  3. 返回通知(After Returning Advice):在目标方法成功执行并返回结果后执行的通知。可以获取目标方法的返回值,并在需要时进行处理。
  4. 异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。可以捕获目标方法抛出的异常,并根据需要进行处理。
  5. 环绕通知(Around Advice):在目标方法执行前后都执行的通知。可以控制目标方法的执行,包括是否执行目标方法、修改参数、处理返回值等。

在Spring AOP中,这些Advice可以根据需要组合成拦截器链,按照指定的顺序依次执行。拦截器链中的每个拦截器都有机会在JoinPoint周围执行自己的逻辑,从而实现对目标方法的拦截和处理。

AOP的实现方式

主要分类两类:静态代理动态代理

静态代理

指使用AOP框架提供的命令进行编译,在编译阶段就可以生成AOP代理类(也叫编译时增强

动态代理

运行时在内存中"临时"生成AOP动态代理类(也叫运行时增强

JDK动态代理

JDK Proxy是Java语言自带的功能,无需通过加载第三方类实现。

JDK Proxy是通过拦截器+反射的方式实现的,只能代理实现接口的类

CGLIB动态代理

CGLIB是第三方工具,基于ASM实现,性能比较好;

ASM(全称为"直接操作字节码的框架",英文为"Bytecode Manipulation Framework")是Java字节码操纵框架,它允许以程序方式动态修改已编译的Java类文件的字节码。ASM提供了一组API和工具,使开发者能够在不加载类文件的情况下直接对字节码进行操作,包括添加、修改、删除字节码指令等。

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库。CGLIB通过继承目标类来创建代理类,因此可以代理没有实现接口的类。

CGLIB无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。

对CGLIB的理解

Spring AOP和AspectJ AOP有什么区别?

Spring AOP是属于运行时增强,而Aspect AOP是编译时增强。

Spring AOP基于代理(Proxying),而Aspect AOP基于字节码操作 (Bytecode Manipulation)

Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。

SpringMVC

说说SpringMVC

MVC是一种设计模式,而SpringMVC是MVC的一个实现。

Spring MVC下我们⼀般把后端项⽬分为Service层(处理业务)、DAO层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台⻚⾯)

image-20231126153154303

SpringMVC的原理

总的来说,整个流程就是客户端发送请求到DispatcherServlet,DispatcherServlet通过HandlerMapping找到对应的Controller,再由HandlerAdapter调用处理器执行业务逻辑,最后经过ViewResolver和View的处理,将渲染后的View返回给客户端。

当客户端(比如浏览器)发送请求时,请求会直接到达DispatcherServlet,它是Spring MVC中的核心控制器。

DispatcherServlet会根据请求信息调用HandlerMapping,这个过程就是为了找到请求对应的Handler,也就是我们平常说的Controller控制器。

一旦找到了对应的Controller,HandlerAdapter就会根据这个Controller来调用真正的处理器来处理请求并执行相应的业务逻辑。

当处理器完成业务逻辑后,会返回一个包含数据对象和逻辑View的ModelAndView对象。其中,Model包含了返回的数据对象,View则是逻辑上的View。

接着,ViewResolver会根据逻辑View去查找实际的View,并将Model传递给View进行渲染。

最后,DispatcherServlet会把渲染后的View返回给请求者,也就是浏览器。

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

举几个例子

工厂设计模式

​ Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象

代理设计模式

​ SpringAOP功能的实现。

单例设计模式

​ Spring中的bean默认都是单例的。

模板方法模式

​ Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。

包装器设计模式

​ 如果项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

观察者模式

​ Spring事件驱动模型就是观察者模式很经典的一个应用

适配器模式

​ Spring AOP的增强或通知 (Advice) 使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。

springboot

spring和springboot是什么,区别又是什么?

Spring和Spring Boot是两个常用的Java开发框架,主要用于在开发Java应用过程中简化步骤,提高效率

Spring框架是面向切面编程(AOP)的,提供了很多现成的模块和功能,可以在开发过程中直接使用

Spring Boot则是一个基于Spring框架的快速开发框架,旨在简化Spring应用程序的配置和部署,使得开发者可以更专注于业务逻辑的实现。

使用Spring时,我们需要手动配置和集成各个组件,而使用Spring Boot时,我们可以通过起步依赖和自动配置来简化开发过程。

Spring适用于复杂的应用程序开发,而Spring Boot适用于快速构建简单和微服务应用。

SpringBoot自动配置的原理?

在Spring程序main方法中,添加 @SpringBootApplication 或者 @EnableAutoConfiguration 会自动去maven中读取每个starter中的 spring.factories 文件,该文件里配置了所有需要被创建的 Spring 容器中的Bean

Spring Boot的核心注解是哪些? 主要哪几个注解组成的?

启动类上面的注解是@SpringBootApplication,他也是SpringBoot的核心注解,主要组合包含了以下3个注解:

  • @SpringBootConfiguration: 组合了@Configuration注解,实现配置文件的功能;
  • @EnableAutoConfiguration: 打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置的功能:
  • @SpringBootApplication(exclude={DataSourceAutoConfiguration.class));
  • @ComponentScan: Spring组件扫描

SpringBoot的核心配置文件有哪几个? 他们的区别是什么?

SpringBoot的核心配置文件是:applicationbootstrap两个配置文件

application配置文件这个容易理解,主要用于Spring Boot项目的自动化配置

bootstrap配置文件有以下几个应用场景:

  • 使用Spring CloudConfig配置中心时,这时需要在bootstrap配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
  • 一些固定的不能被覆盖的属性;
  • 一些加密/解密的场景;

什么是Spring Boot Starter?有哪些常用的?

Spring Boot Starter是一种用于简化Spring Boot应用程序依赖管理的机制。它们是预配置的依赖项集合,可以通过引入特定的Starter来轻松地添加所需的功能和模块到Spring Boot项目中,从而减少了手动配置的工作量。

spring-boot-starter-web-services - SOAP Web Services

spring-boot-starter-web-用于构建Web应用程序的Starter,包含了Spring MVC、Tomcat等相关依赖。

spring-boot-starter-test-用于编写测试代码的Starter,包含了JUnit、Mockito等测试相关的依赖。

spring-boot-starter-jdbc-传统的JDBC

spring-boot-starter-hateoas-为服务添加 HATEOAS功能

spring-boot-starter-security-使用 SpringSecurity 进行身份验证和授权s

pring-boot-starter-data-jpa-用于支持使用Spring Data JPA进行数据持久化的Starter,包含了JPA相关依赖以及数据库驱动。

spring-boot-starter-data-rest-使用Spring Data REST 公布简单的 REST 服务

至今为止遇到的手撕

单例模式

class Singleton{ //懒汉式
private:
    static Singleton* instance;
    Singelton(){}
public:
	//提供一个获取实例的方法供外界调用
    static Singleton* getInstace(){
        if(instance == null){//判断一下当前是否存在实例
            instance = new Singleton();//在getInstance中创建实例
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;//实例空指针,还没创建,当调用get方法时才真正创建
int main(){
    Singleton* singletonObj = Singleton::getInstance();//创建一个单例类对象并调用getInstance获取一个实例
    return 0
}
class Singleton{ //饿汉式
private://私有成员属性:声明实例、构造函数
    static Singleton* instance;
    Singleton(){}
public://对外暴露:getInstance
    static Singleton* getInstace(){
        return instance;//直接就返回实例
    }
};
Singleton* Singleton::instance = new Singleton();//在初始化静态成员时直接创建一个实例

int main(){
    Singleton* singletonObject = Singleton::getInstance();//
    return 0;
}

删除链表重复节点

#include <iostream>
using namespace std;
struct ListNode{
	int val;
    ListNode* next;
    ListNode(int x): val(int), next(nullptr){}
};

ListNode* deleteNode(ListNode* head){
    ListNode* cur = head;
    while(cur->next){
        if(cur->val = cur->next->val){
            cur->next = cur->next->next;
        }else{
            cur = cur->next;
        }
    }
    return head;
}
int main(){
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(4);
    head->next->next->next = new ListNode(4);
    head->next->next->next->next = new ListNode(5);
    ListNode* res = deleteNode(haed);
    
    while(res){
        cout << res->val << endl;
        res = res->next;
    }  
    return 0;
}

合并两个有序数组

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int k = nums1.size() - 1;
        int j = nums2.size() - 1;
        int i = nums1.size() - nums2.size() - 1;
        while(j >= 0){
            if(i < 0 || nums2[j] > nums1[i]){
                swap(nums1[k], nums2[j]);
                j--;
                k--;
            }else{
                swap(nums1[k], nums1[i]);
                i--;
                k--;
            }
        }
    }
};

【防盗链提醒:爬虫是吧?原贴在:https://www.cnblogs.com/DAYceng】

合并两个无序数组

#include <iostream>
#include <vector>

using namespace std;

vector<int> mergeArrays(vector<int>& arr1, vector<int>& arr2) {
    vector<int> res;
    int i = 0, j = 0;//设置两个指针分别指向两个数组

    while (i < arr1.size() || j < arr2.size()) {//当两个指针都小于各自的数组长度时
        //将小的数不断添加到结果数组中
        if (arr1[i] < arr2[j]) {
            result.push_back(arr1[i]);
            i++;
        } else {
            result.push_back(arr2[j]);
            j++;
        }
    }
	//当一个数组遍历完成,剩下的另一个数组中的元素一定都是比目前结果数组中的元素大的
    //按顺序加入结果数组即可
    while (i < arr1.size()) {
        result.push_back(arr1[i]);
        i++;
    }
    while (j < arr2.size()) {
        result.push_back(arr2[j]);
        j++;
    }

    return res;
}
int main() {
    int n, m;
    cin >> n >> m; // 输入两个数组的长度
    vector<int> arr1(n);
    vector<int> arr2(m);
    for (int i = 0; i < n; i++) cin >> arr1[i]; // 输入第一个数组的元素
    for (int i = 0; i < m; i++) cin >> arr2[i]; // 输入第二个数组的元素

    vector<int> merged = mergeArrays(arr1, arr2);
    for (int num : merged) {
        cout << num << " ";
    }
    return 0;
}

LRU

class LRUCache {
    struct ListNode{//定义节点结构体
        int key;
        int val;
        ListNode* next;
        ListNode* pre;
    };
    ListNode* dummy;
    int maxSize;//最大缓存数量
    int nodeNums;//当前缓存中的节点数量
    //定义哈希表,key是int,val是节点
    unordered_map<int, ListNode*> hash;
    
public:
    LRUCache(int capacity): maxSize(capacity), dummy(new ListNode){//不用参数列表也行
        nodeNums = 0;
        //dummy的 next 和 prev 指针都指向自身,这样当缓存为空时,dummy既是头节点也是尾节点
        dummy->next = dummy;
        dummy->pre = dummy;
    }
    
    int get(int key) {// 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
        if(hash.find(key) != hash.end()){
            //找到对应节点,取出
            ListNode* node = hash[key];
            //将node从当前位置移除
            node->pre->next = node->next;
            node->next->pre = node->pre;
            //把node插到dummy的后面,也就是链表头部
            node->next = dummy->next;
            node->pre = dummy;
            dummy->next->pre = node;//令dummy后面节点的前面节点为node
            dummy->next = node;//令dummy的后面节点为node
            return node->val;      
        }
        return -1;//没找到对应节点返回-1
    }
    //如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 
    void put(int key, int value) {
        //检查是否存在对应键值
        if(hash.find(key) != hash.end()){//存在
            hash[key]->val = value;//键已经存在于哈希表中,那么需要更新这个键对应的节点的值
            get(key);//调用 get(key) 函数,将这个节点移动到链表头部,表示最近访问过它
        }else{//不存在,添加进链表
            if(nodeNums < maxSize){//缓存没满
                nodeNums++;//缓存中当前节点数增加
                //创建新节点
                ListNode* node = new ListNode;
                node->key = key;
                node->val = value;
                //哈希表对应位置进行记录
                hash[key] = node;
                //将新节点插到dummy后面,也就是链表头部
                node->next = dummy->next;
                node->pre = dummy;
                dummy->next->pre = node;
                dummy->next = node;
            }else{//缓存满了,删除此时链表末尾的节点
                //取链表最后一个节点,即dummy的pre指针指向的节点
                ListNode* node = dummy->pre;
                hash.erase(node->key);//在哈希表中删除对应节点
                hash[key] = node;//在哈希表中添加新的键值对,其中 key 是缓存节点的键,node 则是新的节点。
				node->key=key;//更新 node 节点的键值为新的 key。	
				node->val=value;
                get(key);
            }
        }      
    }
};

lc78子集

class Solution {
private:
    vector<int> path;
    vector<vector<int>> res;
    void backtracking(vector<int>& nums, int beginIndex){
        res.push_back(path);
        if(path.size() == nums.size()) return;

        for(int i = beginIndex; i < nums.size(); ++i){
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
        return;
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        backtracking(nums, 0);
        return res;
    }
};

按规则翻转字符串

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

void reverseWords(string &s) {
    // 翻转整个字符串
    reverse(s.begin(), s.end());

    // 翻转每个单词
    int start = 0;
    for (int end = 0; end < s.length(); end++) {
        if (s[end] == ' ') {
            reverse(s.begin() + start, s.begin() + end);
            start = end + 1;
        }
    }
    reverse(s.begin() + start, s.end());
}

int main() {
    string str = "how are you";
    reverseWords(str);
    cout << str << endl;

    return 0;
}
posted @ 2024-02-28 09:57  dayceng  阅读(1277)  评论(7编辑  收藏  举报