select * from A where id in(select id from B)
有两点区别:
(1) 使用上的区别:exists中放一个子查询有记录返回true,无记录返回false(NULL也算有记录),in中查询结果集只能有一个字段
(2) 性能上的区别:in要把缓存到内存中,exists不需要缓存结果
in()适合B表比A表数据小的情况
exists()适合B表比A表数据大的情况
当A表数据与B表数据一样大时,in与exists效率差不多,可任选一个使用.
UNION
和 UNION ALL
是用于合并两个或多个 SELECT 语句的结果集的 SQL 操作符,它们之间有一些重要的区别:
UNION:
UNION
用于合并两个或多个 SELECT 语句的结果集,并且会自动去除重复的行。UNION
操作中有重复的行,只会返回一次。UNION
执行过程中会对结果集进行排序和去重,因此相比 UNION ALL
,它可能会更消耗资源,性能可能会稍低。UNION ALL:
UNION ALL
同样用于合并两个或多个 SELECT 语句的结果集,但不会去除重复的行。UNION ALL
操作中有重复的行,会返回多次。UNION ALL
不会对结果集进行排序和去重,因此相比 UNION
,它的性能可能更高。主要区别:
UNION
去除重复行,而 UNION ALL
不去除重复行。UNION
需要对结果集进行排序和去重,而 UNION ALL
不需要。在选择使用 UNION
还是 UNION ALL
时,需要根据实际需求来决定是否需要去除重复行,以及对性能的要求。如果确实需要去除重复行,并且对性能影响可以接受,可以选择使用 UNION
,否则可以考虑使用 UNION ALL
。
jdbcTemplate
在Java中自定义启动器通常涉及到创建自定义的启动类,该启动类将负责初始化和启动应用程序。以下是一种常见的方式来创建自定义的启动器:
public class MyAppLauncher {
public static void main(String[] args) {
// 执行应用程序初始化操作
initialize();
// 启动应用程序主逻辑
startApplication();
}
private static void initialize() {
// 进行应用程序初始化,例如加载配置、初始化数据库连接等
}
private static void startApplication() {
// 启动应用程序的主逻辑,例如启动服务器、加载UI界面等
}
}
构建启动器: 接下来,您可以将这个启动类打包成一个可执行的JAR文件。您可以使用Maven或Gradle等构建工具来构建和管理项目。
运行应用程序: 最后,您可以通过命令行或其他方式来运行这个自定义启动器的JAR文件。例如:
java -jar MyAppLauncher.jar
或者您也可以在IDE中直接运行启动类的main方法。
通过这种方式,您可以实现一个自定义的Java应用程序启动器,该启动器可以执行一些初始化操作并启动应用程序的主逻辑。您可以根据实际需求来扩展和定制启动器的功能。
在Java中,您可以使用自定义注解来为类、方法、字段等元素添加元数据信息,以及定义和使用特定的标记。以下是创建和使用自定义注解的一般步骤:
@interface
关键字来声明。注解接口中可以定义方法,这些方法可以被用来指定注解的属性。import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // 指定注解保留策略
@Target(ElementType.METHOD) // 指定注解可以应用的目标元素类型
public @interface MyAnnotation {
String value() default ""; // 定义一个属性,默认值为空字符串
}
public class MyClass {
@MyAnnotation("SomeValue") // 使用自定义注解,并指定属性值
public void myMethod() {
// 方法实现
}
}
import java.lang.reflect.Method;
public class AnnotationProcessor {
public void processAnnotations(Class<?> clazz) {
Method[] methods = clazz.getDeclaredMethods(); // 获取类中声明的所有方法
for (Method method : methods) {
if (method.isAnnotationPresent(MyAnnotation.class)) { // 判断方法是否使用了指定的注解
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); // 获取注解实例
String value = annotation.value(); // 获取注解属性值
System.out.println("Value from annotation: " + value);
}
}
}
}
这样,您就可以定义、使用和读取自定义注解了。自定义注解可以用于各种场景,例如在测试框架中标记测试方法、在AOP中标记切点等。通过注解,您可以为代码添加更多的语义信息和标记,从而提高代码的可读性和可维护性。
线程池是一种用于处理并发任务的软件设计模式,它的核心思想是利用池化技术来实现资源的高效复用,从而避免频繁地创建和销毁线程所带来的性能开销。在Java中,通常是通过ThreadPoolExecutor
类来创建线程池。线程池的主要功能包括:
提升性能:线程池能够独立负责线程的创建和管理,允许将任务提交给线程池并让其调度执行,这样可以最大化地使用空闲线程来执行异步任务,从而显著提升性能。1
线程管理:线程池会维护一些基本线程统计信息,以便有效地管理和调度接收到的异步任务。
统一管理:线程池可以对线程进行统一的分配、调优和监控,提高了线程的可管理性。2
减少资源消耗:通过复用已存在的线程,线程池可以减少线程创建和销毁造成的资源消耗。
提高响应速度:当有任务到达时,线程池可以直接从池中获取线程来执行,而无需等待新线程的创建,这样可以快速响应用户请求。
线程池的工作流程通常是这样的:任务提交者将任务提交给线程池,线程池会根据配置决定是否创建一个新的线程来执行这个任务。如果线程池中有足够的空闲线程,它会使用这些线程来执行任务;如果没有足够线程,则会创建新的线程。一旦任务完成,线程会被放回线程池中,而不是被销毁,以便将来可以被再次使用。这种策略可以有效防止线程的过度创建和销毁,从而减少资源浪费和提高系统性能
线程池的拒绝策略是指当线程池无法接受新任务时,如何处理这些新任务的一种策略。Java中的线程池框架 ThreadPoolExecutor
提供了几种内置的拒绝策略,以便开发人员根据实际情况选择适合的策略。下面是几种常见的拒绝策略:
AbortPolicy(默认策略):
RejectedExecutionException
异常,阻止任务的提交。CallerRunsPolicy:
DiscardPolicy:
DiscardOldestPolicy:
自定义拒绝策略:
RejectedExecutionHandler
接口来定义自己的拒绝策略,根据实际需求进行处理。通过实现该接口,可以定义拒绝策略的具体行为,例如记录日志、持久化、等待一段时间再重新尝试等。在使用线程池时,选择合适的拒绝策略非常重要,它可以影响到任务的执行和系统的稳定性。根据实际情况,选择合适的拒绝策略可以更好地处理任务的拒绝情况。
在Java中,线程池的任务队列是用来保存等待执行的任务的数据结构。Java中的线程池框架 ThreadPoolExecutor
提供了几种常见的任务队列实现,下面是其中几种:
直接提交队列(Direct handoff):
无界队列(Unbounded queue):
有界队列(Bounded queue):
优先级队列(Priority queue):
PriorityBlockingQueue
类来实现优先级队列,它是一个线程安全的无界优先级队列。链表阻塞队列(Linked blocking queue):
以上是几种常见的线程池任务队列实现。根据实际需求和性能考量,选择合适的队列实现可以更好地提高系统的性能和稳定性。
分布式锁是一种用于在分布式系统中控制共享资源访问的机制,它可以确保在多个节点上的不同线程或进程之间对共享资源的访问是互斥的。在实现分布式锁时,需要考虑到分布式系统中的网络通信延迟、节点故障等因素,以确保分布式锁的正确性和可靠性。下面是几种常见的分布式锁实现方式:
基于数据库的分布式锁:
基于缓存的分布式锁:
基于ZooKeeper的分布式锁:
基于分布式锁框架:
在选择分布式锁实现方式时,需要根据实际情况综合考虑系统的复杂性、性能需求、一致性要求等因素。
<foreach item="item" index="index" collection="allIdList" open="(" close=")" separator=",">
#{item}
</foreach>
第一种方式:
如果您不想使用CONCAT
函数来拼接字符串,还可以通过#{}
占位符来实现。以下是一个示例,在Mapper XML文件中使用占位符实现模糊查询:
<select id="findUsersByName" resultType="User">
SELECT * FROM User
<where>
<if test="name != null and name != ''">
AND name LIKE #{ '%' + name + '%' }
if>
where>
select>
在这个示例中,我们直接使用#{}
占位符,并通过字符串拼接来构建模糊查询的条件。#{ '%' + name + '%' }
将会被解析为'%'+name+'%'
,即在传入的name参数两端加上%
,实现模糊查询。
第二种方式:
如果您想在MyBatis中使用动态SQL语句来实现模糊查询,您可以使用MyBatis提供的
标签来动态生成查询条件。以下是一个示例:
在Mapper XML文件中:
<select id="findUsersByName" resultType="User">
SELECT * FROM User
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
if>
where>
select>
在这个示例中,
标签用于检查传入的参数name是否为null或空字符串,如果不是,则在查询中添加模糊查询的条件。
这样就完成了使用MyBatis动态SQL语句进行模糊查询的操作。
串口通信(Serial Communication)和并口通信(Parallel Communication)都是计算机用于与外部设备通信的方式,但它们之间存在一些重要的区别:
数据传输方式:
线数和引脚:
传输速度:
应用领域:
总之,串口通信和并口通信都是用于计算机与外部设备之间的数据传输,但它们在传输方式、线数、速度和应用领域等方面有明显的区别。通常情况下,选择通信方式取决于外部设备的类型、通信需求和可用的硬件接口。在现代计算机中,串口通信和并口通信已经不如以前常见,USB等通用接口更加普及。
UsernamePasswordAuthenticationToken
构造方法里面创建实例对象,再将该实例对象传到AuthenticationManager
的authenticate()
方法里,传递调用UserDetailsService
接口,重写他的loadUserByUsername
方法,在该方法里,通过用户名在数据库中查询到对应的密码,将用户名和密码查询到封装起来,返给框架,框架去校验传参的密码和数据库的密码是否一致,如果不一致提示用户,如果一致,使用jwt生成token,返回给用户OncePerRequestFilter
接口并且重写doFilterInternal()
方法,在该方法里面拿到token,处理解析token,解析出来的数据没有问题就存到框架里SecurityContextHolder.getContext().setAuthentication()
WebSecurityConfigurerAdapter
这个抽象类并且重写configure()
方法,在该方法里配置jwt过滤器,比如 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//配置jwt过滤器,放在UsernamePasswordAuthenticationFilter前面 http.exceptionHandling()
Spring Security 是一个广泛用于认证和授权的框架,因此在面试中可能会涉及到与 Spring Security 相关的问题。以下是一些常见的 Spring Security 面试题和答案:
什么是 Spring Security?
Spring Security 的主要特性是什么?
Spring Security 如何处理身份验证?
Spring Security 支持哪些身份验证提供者?
如何配置基于用户名和密码的身份验证?
可以通过配置 UserDetailsService 和密码编码器(PasswordEncoder)来实现基于用户名和密码的身份验证
什么是权限(Authority)和角色(Role)?
如何配置基于角色的访问控制?
@PreAuthorize
或 @Secured
注解以及方法级别的安全性配置来实现基于角色的访问控制。什么是 CSRF 攻击?Spring Security 如何防御 CSRF 攻击?
什么是单点登录(SSO)?Spring Security 支持单点登录吗?
Spring Security 和 OAuth 2.0 有何关系?
如何自定义登录页面和注销行为?
loginPage()
和 logoutUrl()
来自定义登录页面和注销行为。Spring Security 中的会话管理是什么?如何配置会话管理?
什么是JWT(JSON Web Token)?Spring Security 支持 JWT 吗?
如何在Spring Boot中配置Spring Security?
spring-boot-starter-security
依赖来快速配置Spring Security。可以通过 application.properties
或 application.yml
文件进行配置。如何处理跨域请求(CORS)?Spring Security 如何支持CORS?
CorsFilter
或使用 Spring Security 提供的 @CrossOrigin
注解来处理跨域请求。这些问题涵盖了 Spring Security 的基本概念、功能和配置,但根据具体的职位和项目要求,面试官可能会提出更深入和复杂的问题,因此建议您深入学习 Spring Security 并准备好深入的理解和实践。
Spring Security 使用一种称为身份验证提供者(Authentication Provider)的机制来验证用户名和密码是否正确。身份验证提供者是 Spring Security 的核心组件之一,负责验证用户的身份,并提供认证后的用户信息。
下面是 Spring Security 验证用户名和密码的一般过程:
用户登录:用户在登录页面上输入用户名和密码,然后提交登录请求。
拦截登录请求:Spring Security 拦截登录请求,并将其路由到相应的身份验证过滤器。
身份验证过滤器处理:身份验证过滤器会提取用户提供的用户名和密码,并封装成一个凭据对象(Credentials)。通常,这个凭据对象是 UsernamePasswordAuthenticationToken
的实例。
选择身份验证提供者:Spring Security 根据配置选择合适的身份验证提供者来验证用户的凭据。通常,身份验证提供者是 DaoAuthenticationProvider
,它使用 UserDetailsService
来获取用户信息。
UserDetailsService 获取用户信息:UserDetailsService
是一个接口,用于获取用户的详细信息。它从用户存储位置(如数据库)中检索用户的信息,包括用户名、密码和角色等。
验证用户名和密码:身份验证提供者使用 UserDetailsService
获取的用户信息与用户提供的用户名和密码进行比较。如果密码匹配,认证成功。
认证成功或失败:如果认证成功,Spring Security 会创建一个包含用户详细信息和授权信息的 Authentication
对象,并将其存储在安全上下文中。如果认证失败,将抛出异常。
重定向或授权:认证成功后,用户通常会被重定向到受保护的资源或授权页面,根据其角色和权限进行访问控制。
需要注意的是,身份验证提供者和用户详细信息的来源可以根据实际需求进行配置。通常情况下,UserDetailsService
从数据库或其他用户存储中获取用户信息。密码的存储和验证通常也由 Spring Security 处理,可以使用密码编码器(PasswordEncoder)来确保密码的安全性。
总之,Spring Security 提供了一个灵活且高度可配置的机制来验证用户名和密码,并支持多种身份验证提供者和用户详细信息源。这使得开发人员可以根据应用程序的需求选择合适的配置和实现方式。
Spring框架使用了多种设计模式,这些模式帮助Spring实现了各种功能和特性。以下是一些Spring框架中常用的设计模式:
单例模式(Singleton Pattern):Spring容器中的Bean默认是单例的,确保一个类只有一个实例。
工厂模式(Factory Pattern):Spring使用工厂模式来创建和管理Bean。BeanFactory和ApplicationContext负责创建和管理Bean实例。
依赖注入模式(Dependency Injection Pattern):Spring框架是依赖注入(DI)的鼻祖,它通过将依赖注入到Bean中来实现松耦合的组件之间的通信。
观察者模式(Observer Pattern):Spring的事件机制基于观察者模式,它允许Bean监听并响应应用程序中的事件。
模板方法模式(Template Method Pattern):Spring的JdbcTemplate和HibernateTemplate等模板类使用了模板方法模式,将通用的任务封装在模板方法中,子类可以覆盖特定的部分。
策略模式(Strategy Pattern):Spring中的AOP(面向切面编程)和事务管理使用了策略模式,允许开发人员定义横切关注点和切面。
装饰者模式(Decorator Pattern):Spring的AOP代理就是一种装饰者模式的应用,它在不改变原始类的情况下增加了额外的功能。
适配器模式(Adapter Pattern):Spring的适配器模式用于将不同类型的组件集成到Spring容器中,例如将非Spring Bean整合到Spring中。
建造者模式(Builder Pattern):Spring框架中使用建造者模式来配置和构建复杂的Bean定义,如Spring的BeanDefinitionBuilder
。
代理模式(Proxy Pattern):Spring的AOP代理使用了JDK动态代理和CGLIB代理等代理模式来增强Bean的功能。
这些设计模式有助于Spring框架实现松耦合、可维护性、可扩展性和可重用性等关键特性,使Spring成为一个强大的企业级应用程序开发框架。它们帮助开发人员更容易地编写可维护和可测试的代码,提供了各种功能,如依赖注入、AOP、事务管理等,从而简化了应用程序的开发过程。
JDK动态代理和CGLIB代理是两种常用的Java代理机制,它们在实现方式和适用场景上有一些区别:
实现方式:
JDK动态代理:JDK动态代理是基于Java反射机制实现的。它要求被代理的类必须实现一个接口,代理类通过实现同样的接口并在运行时生成代理对象。在代理对象的方法被调用时,会通过反射调用被代理对象的方法。
CGLIB代理:CGLIB(Code Generation Library)代理是通过字节码生成库实现的。它可以代理那些没有实现接口的类,通过继承被代理类,并覆盖其中的方法来创建代理对象。
适用场景:
JDK动态代理:适用于代理接口的情况。如果被代理的类实现了一个或多个接口,可以使用JDK动态代理。常见的应用场景包括AOP(面向切面编程)和代理事务管理。
CGLIB代理:适用于代理普通的Java类,包括没有实现接口的类。CGLIB可以代理具体的类,而不仅仅是接口。它常用于扩展现有类的功能,例如生成Bean的子类来实现延迟加载。
性能:
JDK动态代理:通常来说,JDK动态代理在性能上比CGLIB代理稍微快一些,因为它使用了Java原生的反射机制,但它要求被代理的类必须实现接口。
CGLIB代理:CGLIB代理通常比JDK动态代理慢一些,因为它需要生成和加载字节码,但它可以代理没有实现接口的类。
总之,选择JDK动态代理还是CGLIB代理取决于具体的需求和场景。如果被代理的类已经实现了接口,而且性能要求较高,可以考虑使用JDK动态代理。如果被代理的类没有实现接口,或者需要对类的方法进行扩展而不修改源代码,可以考虑使用CGLIB代理。在实际应用中,有时候也会结合两者的优点来使用。
创建型,结构型,行为行
常用的设计模式有很多,它们是经过反复验证和广泛应用的软件设计原则,可以帮助开发人员解决各种常见的设计问题。以下是一些常用的设计模式:
单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。属于创建型设计模式
工厂模式(Factory Pattern):用于创建对象,但将对象的实际创建过程封装在工厂类中。属于创建型设计模式
抽象工厂模式(Abstract Factory Pattern):提供一组相关或依赖对象的接口,而不指定其具体类。属于创建型设计模式
建造者模式(Builder Pattern):用于创建复杂对象,将创建过程分解为一系列步骤。
原型模式(Prototype Pattern):通过复制现有对象来创建新对象,而不是从头开始构建。
适配器模式(Adapter Pattern):允许接口不兼容的类一起工作,提供一个中间适配器来转换接口。
装饰者模式(Decorator Pattern):允许动态地给对象添加新的行为,不需要修改其源代码。
代理模式(Proxy Pattern):提供一个代理对象来控制对其他对象的访问。属于结构型设计模式
观察者模式(Observer Pattern):定义对象间的一对多依赖关系,使得当一个对象改变状态时,所有依赖它的对象都会得到通知并更新。
策略模式(Strategy Pattern):定义一系列算法,将每个算法封装起来,并使它们可以相互替换。
命令模式(Command Pattern):将请求封装为一个对象,从而允许使用不同的请求、队列或者日志来参数化客户端。
状态模式(State Pattern):允许对象在内部状态改变时改变它的行为,看起来好像修改了它的类。
备忘录模式(Memento Pattern):捕获一个对象的内部状态,并在对象之外保存这个状态,以后可以将对象恢复到这个状态。
迭代器模式(Iterator Pattern):提供一种方法来访问一个聚合对象中的各个元素,而不需要暴露其内部表示。
组合模式(Composite Pattern):将对象组合成树形结构以表示“部分-整体”的层次结构。
模板方法模式(Template Method Pattern):定义一个算法的骨架,将一些步骤推迟到子类中实现。
访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,可以让你在不改变这些元素的类的前提下定义作用于这些元素的新操作。
解释器模式(Interpreter Pattern):给定一个语言,定义它的文法的一种表示,并定义一个解释器,用来解释语言中的句子。
这些设计模式可以帮助提高代码的可维护性、可读性和可扩展性,使得软件更容易维护和扩展。在实际开发中,根据具体的问题和需求选择合适的设计模式非常重要。
七层网络协议模型是一种用于描述计算机网络协议的框架,也称为 OSI 模型(Open Systems Interconnection Model)。它将网络协议分为七个不同的层次,每个层次负责特定的功能,从物理传输到应用层面。以下是七层网络协议模型的每个层次:
物理层(Physical Layer): 这是最底层的层次,负责定义物理介质和电信号的传输方式,如电缆、光纤、电压等。它关注的是如何在物理层面传输比特流。例如串口通信属于这一层
数据链路层(Data Link Layer): 数据链路层负责将比特流组织成帧(Frame),并提供物理层的错误检测和纠正。它还处理局域网中的数据帧传输,以及物理地址(MAC 地址)的管理。
网络层(Network Layer): 网络层负责路由选择和数据包的转发。它定义了不同设备之间的路径选择算法,以及 IP 地址的管理。
传输层(Transport Layer): 传输层提供端到端的通信和数据传输服务。它包括了 TCP(传输控制协议)和 UDP(用户数据报协议)等协议,用于确保数据的可靠性和顺序传输。
会话层(Session Layer): 会话层负责建立、管理和终止通信会话。它处理了会话的开始、暂停、恢复和结束等操作。
表示层(Presentation Layer): 表示层处理数据的编码、解码和加密解密等操作,以确保数据在不同系统之间的兼容性。
应用层(Application Layer): 应用层包含了各种应用程序和协议,如HTTP、FTP、SMTP等,用于实现各种网络应用,例如网页浏览、电子邮件和文件传输。
这种七层模型的设计使不同的网络协议和功能可以被划分到不同的层次中,从而实现了协议的模块化和分层设计。这有助于不同厂商和组织之间的互操作性,并使网络协议的开发和维护更加可管理。虽然 OSI 模型在理论上非常有用,但实际上,互联网的协议体系更多地采用了 TCP/IP 模型,该模型包含了较少的层次,并将某些功能合并到了更少的层次中,但仍然保留了许多 OSI 模型的概念。
在网络编程中,“握手”(Handshake)通常指的是建立连接的过程。TCP/IP 协议中的握手过程是为了确保通信双方都准备好进行数据传输。最常见的握手过程是 TCP 的三次握手,用于建立 TCP 连接。
以下是 TCP 的三次握手过程:
第一次握手(SYN): 客户端向服务器发送一个特殊的 TCP 报文,包含一个标志位 SYN(同步),表示客户端希望建立连接。客户端选择一个随机的初始序列号(ISN)作为起始值,并发送给服务器。
第二次握手(SYN-ACK): 服务器接收到客户端的请求后,会向客户端回复一个包含 SYN 和 ACK(确认)标志位的 TCP 报文,表示服务器同意建立连接。服务器也会选择一个随机的初始序列号,并将其发送给客户端。同时,服务器会确认客户端的序列号,将其加一作为确认号。
第三次握手(ACK): 客户端收到服务器的确认后,也会发送一个确认报文,其中包含 ACK 标志位。这个报文的序列号会加一,表示客户端已经确认了服务器的响应。
在完成这个三次握手过程后,双方就建立了连接,可以开始进行数据传输。这个握手过程确保了双方都准备好进行通信,同时也能够防止无效的连接请求。
需要注意的是,UDP 协议不涉及握手过程,因为它是一种无连接协议,数据包可以直接发送给目标,而不需要建立连接。握手通常在可靠性和有序性对数据传输要求较高的情况下使用,而 UDP 则用于那些可以容忍一些数据包丢失或乱序的应用。
LinkedList(链表)是一种常见的线性数据结构,它由一系列的节点组成,每个节点包含数据元素和指向下一个节点的引用。LinkedList的底层实现通常基于这种节点结构,具体取决于编程语言和标准库的实现。
在Java中,LinkedList的底层实现是双向链表(Doubly Linked List)。双向链表中的每个节点包含三部分信息:
这种双向链表的结构使得在链表中进行前后遍历和节点插入/删除等操作更加高效,因为它允许直接访问前一个节点。
具体来说,Java的java.util.LinkedList
类使用双向链表来实现,它包含一个头节点和一个尾节点,以及指向链表中的前一个节点和后一个节点的引用。这使得在头部和尾部进行元素的添加和删除操作非常高效,但在随机位置插入和删除元素的效率较低。
下面是一个简化的示意图,表示双向链表的结构:
null <-> [Node 1] <-> [Node 2] <-> [Node 3] <-> ... <-> [Node N] <-> null
在这个示意图中,null
表示链表的起始和结束。每个节点包含数据和前后节点的引用。
需要注意的是,虽然Java的LinkedList
是双向链表的实现,但在某些编程语言中,也可以使用单向链表、循环链表等不同类型的链表来实现LinkedList。因此,具体的底层实现可以因编程语言和库的不同而异。
String拼接字符串效率慢主要是因为String对象在Java中是不可变的(immutable),这意味着一旦创建了一个String对象,就不能再修改它的内容。因此,每次对String进行拼接、修改或连接操作时,都会创建一个新的String对象,而不是在原有的String上直接进行修改,这会导致性能问题,特别是在大量拼接字符串的情况下。
具体来说,以下是导致String拼接效率慢的主要原因:
创建新对象: 每次进行字符串拼接时,都会创建一个新的String对象,这会导致内存分配和垃圾回收的开销,特别是在循环中进行大量拼接操作时。
字符串不可变性: 由于String对象是不可变的,修改字符串内容必须创建一个新的String对象,将原有的内容复制到新对象中,这会导致额外的内存和时间开销。
性能退化: 随着字符串的拼接次数增加,性能会逐渐下降。这是因为每次拼接都需要复制已有字符串的内容,导致每次操作都比前一次操作慢。
为了提高字符串拼接的性能,通常可以采用以下方式:
使用StringBuilder或StringBuffer: 这两个类是可变的字符串缓冲区,它们允许在同一个对象上进行多次拼接操作,而不会创建新的String对象。StringBuilder是非线程安全的,适用于单线程环境,而StringBuffer是线程安全的,适用于多线程环境。
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString();
使用字符串格式化: 使用字符串格式化工具(如String.format
或MessageFormat
)可以将变量插入到字符串中,而无需手动拼接。
String name = "Alice";
int age = 30;
String message = String.format("Name: %s, Age: %d", name, age);
避免在循环中频繁拼接: 如果需要在循环中拼接大量字符串,可以考虑将拼接结果存储在集合中,然后在循环结束后将它们连接成一个字符串,以减少对象创建次数。
总之,String拼接效率慢是因为String的不可变性,因此在处理大量字符串拼接时,最好使用StringBuilder或StringBuffer来提高性能。避免在循环中频繁拼接字符串,使用字符串格式化工具,都有助于优化字符串拼接操作。
Socket(套接字)是计算机网络编程中的一种通信机制,用于在不同计算机之间进行数据传输。Socket的原理基于网络协议栈和操作系统提供的接口,它允许程序通过网络连接进行数据的发送和接收。
Socket的工作原理包括以下关键步骤:
创建Socket: 在编程中,首先需要创建一个Socket对象,以便与其他计算机建立连接。Socket对象包含了通信所需的信息,如目标IP地址、端口号等。
建立连接: 一方作为服务器(Server Socket),另一方作为客户端(Client Socket)。服务器Socket在指定的IP地址和端口上监听客户端的连接请求。当客户端Socket发起连接请求时,服务器Socket接受连接,建立双向通信通道。
数据传输: 一旦连接建立,双方可以通过Socket进行数据传输。数据可以以字节流或字符流的形式在Socket之间传递。发送方将数据写入输出流,接收方从输入流中读取数据。
关闭连接: 数据传输完成后,可以关闭Socket连接。关闭连接会释放资源并终止连接,使Socket不再可用。
Socket的原理基于底层的网络协议栈,通常是TCP/IP协议栈。TCP/IP协议栈负责将数据分成小块(数据包),并确保它们以正确的顺序到达目标。Socket在应用层与传输层之间提供了一个接口,使开发人员能够轻松地进行网络通信。
在Java中,可以使用java.net
包中的Socket类来创建和管理Socket连接。这允许开发人员编写网络应用程序,实现客户端和服务器之间的通信。Socket编程是构建网络应用程序的基础,它可以用于各种网络通信需求,如Web服务器、聊天应用、文件传输等。
synchronized
是一种非公平锁(不保证公平性)。
在 Java 中,synchronized
关键字用于创建互斥锁,但它并不保证线程获取锁的顺序是公平的。也就是说,当多个线程竞争一个 synchronized
块或方法的锁时,JVM 并不会按照严格的顺序来分配锁,而是根据操作系统和线程调度的情况来分配。这意味着有可能某个线程一直获取到锁,而其他线程一直处于等待状态,导致不公平的竞争。
如果需要公平性,即按照等待时间的顺序分配锁,Java 提供了 ReentrantLock
类的构造函数可以选择创建公平锁。例如:
ReentrantLock lock = new ReentrantLock(true); // 创建公平锁
使用公平锁可以确保等待时间最长的线程优先获取锁,但需要注意,公平锁可能会降低锁的性能,因为它需要更多的线程切换开销。
总之,synchronized
不是公平锁,但可以通过其他手段实现公平锁的效果,例如使用 ReentrantLock
的公平锁构造函数。
在 Java 中,synchronized
关键字可以应用于不同级别的锁,主要有以下几种级别的锁:
对象级别锁(对象锁): 这是最常见的锁级别,它锁定的是一个对象实例。当一个线程获取了某个对象的锁,其他线程必须等待该线程释放锁才能获得锁。例如:
public synchronized void synchronizedMethod() {
// 这是对象级别锁,锁定的是当前对象
}
类级别锁(类锁): 类级别锁是在整个类上的锁,而不是某个对象实例上的锁。它适用于静态方法或静态代码块。当一个线程获取了类锁时,其他线程必须等待该线程释放锁才能获得锁。例如:
public static synchronized void synchronizedStaticMethod() {
// 这是类级别锁,锁定的是类本身
}
块级别锁(局部锁): 这是一种更细粒度的锁,它锁定了某个特定的代码块,而不是整个方法或类。多个线程可以同时访问不同的代码块,只有在同一代码块内的线程之间会有竞争。例如:
public void someMethod() {
// 这是块级别锁,锁定的是特定代码块
synchronized (lockObject) {
// 代码块的内容
}
}
这些不同级别的锁提供了不同的粒度,允许你在多线程环境中对代码进行更细粒度的同步控制。选择哪种锁取决于你的需求和设计,通常应根据需要确保线程安全性,同时尽量减小锁的范围,以提高性能。
ApplicationContext
和 BeanFactory
是 Spring 容器的两个重要接口,它们在应用程序中有一些异同点:
相同点:
Bean 的管理: 无论是 ApplicationContext
还是 BeanFactory
,它们都负责管理和控制应用程序中的 Bean 对象。它们可以根据配置文件或注解来创建、配置和管理 Bean。
依赖注入: 无论是 ApplicationContext
还是 BeanFactory
,它们都支持依赖注入,可以将依赖项自动注入到 Bean 中,实现松耦合的组件之间的协作。
作用域管理: 无论是 ApplicationContext
还是 BeanFactory
,它们都支持管理 Bean 的作用域,如单例、原型等。
生命周期管理: 无论是 ApplicationContext
还是 BeanFactory
,它们都可以管理 Bean 的生命周期,包括初始化和销毁阶段。
不同点:
初始化时机: 主要的不同点在于初始化时机。BeanFactory
是懒加载的,它在第一次访问 Bean 时才进行初始化。而 ApplicationContext
是在容器启动时就立即初始化所有的单例 Bean。
扩展性: ApplicationContext
接口是 BeanFactory
的扩展,它提供了更多的功能,如国际化支持、事件传播、资源加载、AOP 功能等。ApplicationContext
是 BeanFactory
的超集,因此通常更常用。
资源管理: ApplicationContext
提供了更丰富的资源管理功能,能够加载和管理不同类型的资源,如文件、类路径、URL 等。BeanFactory
主要关注 Bean 的管理,对资源管理的支持较少。
性能: 由于 ApplicationContext
在启动时会初始化所有单例 Bean,因此可能会占用更多的内存和时间。相比之下,BeanFactory
是懒加载的,只有在需要时才初始化 Bean,因此可能更节省资源。
总结:
ApplicationContext
和 BeanFactory
都是 Spring 容器的接口,它们在 Bean 的管理和依赖注入方面有很多相似之处,但在初始化时机、扩展性、资源管理和性能等方面存在一些不同。通常情况下,开发者更倾向于使用 ApplicationContext
,因为它提供了更多的功能和便利性。但如果对性能要求非常高,可以考虑使用 BeanFactory
。
BeanFactory
和 FactoryBean
是 Spring Framework 中两个不同的概念,它们用于不同的目的,有不同的作用。
BeanFactory:
BeanFactory
是 Spring 的核心容器接口之一,它负责管理和控制应用程序中的所有 Bean 对象。BeanFactory
提供了一种机制来实例化、配置和管理 Bean,并提供了 Bean 的生命周期管理、依赖注入等功能。BeanFactory
是 Spring 容器的基础接口,它定义了获取和管理 Bean 的标准方法,包括 getBean()
方法来获取 Bean 实例。BeanFactory
是一个接口,它有多种实现,其中最常用的是 ApplicationContext
接口,它是 BeanFactory
的子接口,提供了更多的功能和扩展,如国际化支持、事件处理等。FactoryBean:
FactoryBean
是 Spring 中的一个特殊接口,用于创建复杂的 Bean 对象或充当自定义 Bean 工厂。它允许开发者自定义创建和配置 Bean 的逻辑,从而可以生成复杂的 Bean 实例。FactoryBean
接口要求实现一个 getObject()
方法,该方法返回一个 Bean 实例。通常,FactoryBean
的实现类会被注册到 Spring 容器中,然后通过容器的 getBean()
方法获取 FactoryBean
的实例,然后调用 getObject()
方法来获取实际的 Bean。FactoryBean
可以用于延迟初始化、条件化创建 Bean,以及执行一些特定的逻辑来生成 Bean 实例。总结:
BeanFactory
是 Spring 容器的核心接口,用于管理和控制 Bean 实例,而 FactoryBean
是一个接口,用于创建自定义的 Bean 工厂,允许开发者实现自定义的 Bean 创建逻辑。FactoryBean
的实现类可以生成特定类型的 Bean 实例,这些 Bean 实例可以是复杂的,也可以是延迟初始化的。
Spring Boot 的自动装载是通过 Spring 框架的 @Configuration
、@ComponentScan
、@EnableAutoConfiguration
等注解和条件注解来实现的。这一机制允许 Spring Boot 自动配置应用程序的依赖项,从而简化了应用程序的配置。
下面是 Spring Boot 自动装载的关键组件和原理:
@EnableAutoConfiguration: Spring Boot 应用程序的入口类通常会使用 @SpringBootApplication
注解,这个注解包含了 @EnableAutoConfiguration
。@EnableAutoConfiguration
注解会启用自动配置机制,它会尝试根据项目的依赖和配置来自动配置应用程序的各种功能。Spring Boot 包含了大量的自动配置类,它们会根据需要添加到 Spring 应用程序上下文中。
条件注解: Spring Boot 使用条件注解来决定是否应该自动配置特定的组件。条件注解基于一组条件来判断是否满足自动配置的条件。例如,@ConditionalOnClass
表示只有在类路径上存在某个特定类时才会应用自动配置。
自动配置类: Spring Boot 的自动配置是通过一组自动配置类实现的,这些类位于 org.springframework.boot.autoconfigure
包中。这些自动配置类使用条件注解和 @Configuration
注解来配置应用程序的不同部分,例如数据库连接、Web服务器、消息队列等。
Spring Boot Starter: Spring Boot Starter 是一组预定义的依赖包,它们包含了常见的功能和组件,例如 Web、数据访问、安全等。当你添加一个 Starter 依赖时,Spring Boot 会自动为你配置这些功能的相关依赖和自动配置类。
application.properties/application.yml: Spring Boot 提供了配置文件,你可以在这些文件中定义自定义配置属性,以覆盖自动配置的默认值。通过这种方式,你可以定制应用程序的行为。
总结:
Spring Boot 的自动装载机制依赖于条件注解、自动配置类和应用程序的配置文件。它简化了应用程序的配置和依赖管理,使得开发者可以更轻松地构建和部署应用程序。这种自动装载机制是 Spring Boot 强大的特性之一,减少了繁琐的手动配置工作。
@SpringBootApplication
注解是 Spring Boot 中的一个复合注解,它包含了多个其他注解,用于简化 Spring Boot 应用程序的配置和启动。具体来说,@SpringBootApplication
注解包含以下三个重要的注解:
@Configuration
: 这个注解标记一个类作为配置类,它通常包含应用程序的配置信息。@SpringBootApplication
注解会将这个类注册为 Spring 上下文的一个 Bean。
@ComponentScan
: 这个注解指示 Spring 扫描并加载应用程序中的组件,包括控制器、服务、仓库等。它默认扫描与配置类相同的包及其子包下的组件。@SpringBootApplication
注解会自动配置包扫描,通常放置在主应用程序类上。
@EnableAutoConfiguration
: 这个注解启用了 Spring Boot 的自动配置功能。它会根据应用程序的类路径和依赖来自动配置 Spring 应用程序,包括数据源、Web服务器、消息队列等。@SpringBootApplication
注解会自动启用自动配置。
这三个注解的组合使得 @SpringBootApplication
成为一个强大的注解,它简化了 Spring Boot 应用程序的配置和启动过程,允许开发者快速搭建一个完整的 Spring Boot 应用程序。通过这个注解,开发者可以更专注于应用程序的业务逻辑而不必手动配置许多基础设施和组件。
Redis(Remote Dictionary Server)是一种开源的内存数据库,也可以被称为缓存服务器,它提供了高性能的数据存储和检索功能。Redis 是一个基于键值对存储的数据结构服务器,具有以下特点:
内存数据库: Redis 将数据存储在内存中,这使得它具有极高的读写速度。数据也可以异步地持久化到磁盘上,以便在重启后恢复数据。
键值对存储: Redis 使用键值对(key-value)的方式存储数据,这种简单的数据模型非常适合缓存和快速检索。
多种数据结构支持: Redis 不仅支持简单的字符串,还支持多种数据结构,如哈希表、列表、集合、有序集合、位图等。这些数据结构可以让你更灵活地存储和操作数据。
持久化支持: Redis 可以将数据异步地持久化到磁盘上,以防止数据丢失。它支持两种持久化方式:快照(snapshot)和日志(append-only file)。
分布式支持: Redis 提供了集群模式,允许将数据分布到多个节点上,以实现高可用性和横向扩展。
发布-订阅: Redis 支持发布-订阅模式,允许客户端订阅消息通知,并在消息发布时接收通知。
事务支持: Redis 支持事务,可以一次性执行多个命令,保证这些命令的原子性。
Lua 脚本支持: Redis 支持使用 Lua 脚本执行复杂的操作,这可以减少网络开销并提高性能。
丰富的客户端库: Redis 提供了多种编程语言的客户端库,使得它可以轻松集成到各种应用程序中。
社区活跃: Redis 拥有庞大的开源社区,因此可以获取大量的文档、教程和支持。
Redis 主要用途包括:
总之,Redis 是一个多功能的、高性能的内存数据库,适用于各种不同的应用场景,尤其适合需要快速数据存储和检索的应用。
Redis 是一个开源的高性能键值存储数据库,它支持多种不同的数据结构,每种数据结构都有不同的用途。以下是 Redis 支持的主要数据结构:
字符串(String):
哈希(Hash):
列表(List):
集合(Set):
有序集合(Sorted Set):
位图(Bitmap):
HyperLogLog:
地理空间数据(Geospatial Data):
流(Stream):
这些不同的数据结构使Redis成为了一个多用途、灵活的数据存储系统,可以满足各种应用程序的需求。开发人员可以根据应用程序的需要选择合适的数据结构来存储和操作数据。
在Spring中,Bean的生命周期包括多个阶段,每个阶段都可以由开发人员干预和定制。下面是Spring中Bean的详细生命周期:
实例化(Instantiation): 首先,Spring容器会创建Bean的实例。这通常涉及使用类的构造函数来创建一个新的对象。如果Bean配置中有工厂方法定义,那么这个工厂方法也可以用于创建Bean。
属性设置(Populating Properties): 一旦Bean实例被创建,Spring容器会注入或设置Bean的属性。这些属性可以是基本数据类型,其他Bean的引用,或集合属性。这是通过依赖注入(Dependency Injection)来完成的,Spring容器会根据Bean定义中的配置信息来自动完成属性注入。
Bean的初始化回调(Initialization Callbacks): 在属性设置之后,Spring容器会调用Bean的初始化回调方法。你可以通过以下方式定义初始化回调:
@PostConstruct
注解标记一个方法,这个方法会在依赖注入完成后立即执行。InitializingBean
接口并覆盖afterPropertiesSet()
方法,这个方法会在属性设置后被调用。init-method
属性指定初始化方法的名称。初始化回调通常用于执行一些预处理操作,例如建立数据库连接、打开文件、初始化资源等。
Bean的使用(Bean in Use): 此时,Bean实例已经可以在应用程序的其他部分使用了。它们处于活动状态,执行业务逻辑。
Bean的销毁回调(Destruction Callbacks): 当Bean的生命周期结束时(通常在容器关闭时),Spring容器会调用Bean的销毁回调方法。你可以通过以下方式定义销毁回调:
@PreDestroy
注解标记一个方法,这个方法会在Bean被销毁前调用。DisposableBean
接口并覆盖destroy()
方法,这个方法会在Bean销毁前被调用。destroy-method
属性指定销毁方法的名称。销毁回调通常用于释放资源、关闭数据库连接、清理临时文件等。
Bean的销毁(Destruction): 最后,Bean的实例被销毁,释放内存和资源。这发生在容器关闭时或当Bean的作用域结束时(例如单例Bean的生命周期随着容器的关闭而结束)。
总结:
Spring容器负责管理Bean的完整生命周期,开发人员可以通过初始化回调和销毁回调来插入自定义逻辑。这种生命周期管理有助于确保Bean在创建、使用和销毁过程中的一致性和可控性,同时减少了内存泄漏和资源泄漏的风险。
JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境
具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。
类是对现实生活中一类具有共同属性和行为的事物的抽象
类的特点:
•类是对象的数据类型
•类是具有相同属性和行为的一组对家的集合
3.类和对象的关系
类加载器是加载class文件到内存中,供其他class文件调用。
加载过程是:加载->链接(验证、准备、解析)->初始化->使用->卸载
ClassLoad(类加载器)是Java虚拟机(JVM)的一部分,它负责将Java字节码(编译后的Java源代码)加载到内存中并转换成可执行的Java类。类加载器是Java的一个重要概念,它允许应用程序在运行时动态加载类,这是Java语言的一个重要特性之一,也是实现Java的动态扩展和模块化的基础。
类加载器的原理和过程:
java.lang.Class
对象。static
块)和静态字段初始化。这是类加载过程的最后一步,只有在初始化完成后,类才会真正准备好使用。JVM (Java Virtual Machine) 是 Java 编程语言的关键组成部分之一,它是一个虚拟机,用于执行 Java 程序。JVM 的主要任务是将 Java 源代码编译成字节码(Bytecode),然后在运行时执行这些字节码。以下是 JVM 的一些重要特点和功能:
字节码执行:JVM 通过解释或即时编译(Just-In-Time Compilation,JIT)方式执行 Java 字节码。即时编译将字节码转换成本地机器代码,以提高执行速度。
平台无关性:Java 程序编写一次,可以在支持 JVM 的任何平台上运行,只要有相应的 JVM 实现。这个特性使得 Java 成为跨平台的编程语言。
内存管理:JVM 负责内存管理,包括分配和回收内存。它具有垃圾回收机制,可自动释放不再使用的内存,以减少内存泄漏的风险。
安全性:JVM 提供了一系列安全性功能,如字节码验证,以确保代码的合法性和防止恶意代码执行。
多线程支持:JVM 具有内置的多线程支持,可以轻松创建和管理多线程应用程序。
性能优化:现代的 JVM 实现包括各种性能优化技术,以提高 Java 程序的执行速度。
异常处理:JVM 提供了强大的异常处理机制,使开发人员能够有效地处理错误和异常情况。
类加载器:JVM 使用类加载器(Class Loader)来加载类和资源文件,它们可以动态地从不同的来源加载类,如本地文件系统、网络等。
总的来说,JVM 允许开发人员编写一次 Java 代码,并在不同的平台上运行,同时提供了许多强大的功能,包括内存管理、多线程支持和安全性,以简化和增强 Java 应用程序的开发和执行。不同的 Java 实现(例如Oracle HotSpot、OpenJ9、GraalVM等)提供了不同的性能特性和优化,以满足不同应用程序的需求。
JVM的生命周期:
1.启动。启动一个java程序的时候就产生了一个jvm实例
2.运行。Main方法是程序的入口,任何其他线程均有它启动
3.消亡。当程序中所有非守护线程都终止时,JVM才退出。若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出程序。
JVM的组成:
JVM是由类加载器,字节码执行引擎,运行时数据区(堆,栈,本地方法栈,方法区,程序计数器)组成的
JVM的优点:
一次编写跨平台运行
提供自动内存管理机制
以下是一些常用的 Git 命令:
初始化仓库:
git init
: 在当前目录创建一个新的 Git 仓库。克隆仓库:
git clone <仓库URL>
: 从远程仓库克隆一个副本到本地。添加和提交文件:
git add <文件名>
: 将文件添加到暂存区。git add .
: 添加所有修改的文件到暂存区。git commit -m "提交消息"
: 提交暂存区的文件到本地仓库。查看和比较变更:
git status
: 查看当前工作目录的状态。git diff
: 查看未暂存的变更。git diff --cached
: 查看已暂存的变更。git log
: 查看提交日志。分支操作:
git branch
: 查看本地分支列表。git branch <分支名>
: 创建新分支。git checkout <分支名>
: 切换到指定分支。git merge <分支名>
: 合并指定分支到当前分支。git pull
: 拉取远程分支的更新。远程仓库操作:
git remote -v
: 查看远程仓库列表。git remote add <远程名> <仓库URL>
: 添加远程仓库。git push <远程名> <分支名>
: 推送本地分支到远程仓库。git pull <远程名> <分支名>
: 拉取远程分支的更新。撤销和重置:
git reset HEAD <文件名>
: 从暂存区中取消暂存文件。git checkout -- <文件名>
: 撤销对文件的修改(谨慎使用,会丢失未提交的修改)。标签操作:
git tag
: 查看标签列表。git tag <标签名>
: 创建标签。git tag -a <标签名> -m "标签描述"
: 创建带有描述的标签。git push origin <标签名>
: 推送标签到远程仓库。其他操作:
gitignore
文件:定义忽略的文件和目录。git stash
: 暂存当前工作目录的变更,以便切换到其他分支。git stash pop
: 恢复之前暂存的变更。这些是 Git 的一些常用命令,可以帮助你进行版本控制、协作开发和管理代码仓库。根据你的具体需求,还有更多高级的 Git 命令和操作可以掌握。你可以使用 git --help
命令来查看 Git 的帮助文档,或查阅 Git 官方文档以获取更多信息。
以下是一些常用的 Linux 命令和基本操作:
登录和注销:
ssh
命令连接到远程服务器,例如:ssh username@hostname
。logout
或 exit
命令退出当前用户会话。文件和目录操作:
pwd
。ls
,例如:ls -l
以长格式显示。cd
,例如:cd /path/to/directory
。mkdir
,例如:mkdir new_directory
。cp
,例如:cp file1 file2
或 cp -r directory1 directory2
。mv
,例如:mv oldname newname
。rm
,例如:rm file
或 rm -r directory
。文件查看和编辑:
cat
、more
、less
或 head
、tail
。vi
或 nano
进行文本编辑。vi filename
进入 vi 编辑器,按 i
进入编辑模式,完成后按 Esc
,再输入 :wq
保存并退出。文件权限和所有权:
ls -l
。chmod
,例如:chmod 755 filename
。chown
和 chgrp
。压缩和解压缩文件:
tar
,例如:tar -czvf archive.tar.gz file1 file2
。tar
,例如:tar -xzvf archive.tar.gz
。查找文件和目录:
find
,例如:find /path/to/search -name filename
。grep
,例如:grep "pattern" file
。进程管理:
ps
,例如:ps aux
。kill
,例如:kill PID
,PID 是进程 ID。系统信息:
uname -a
。top
或 htop
。lscpu
、lsblk
、lshw
。用户和权限管理:
useradd
。passwd
。usermod
。su
或 sudo
。网络配置:
ifconfig
或 ip addr
。ifconfig
、ip
、route
。ping
。这些是一些常用的 Linux 命令和基本操作,可以帮助你在 Linux 系统上进行常见的文件操作、系统管理和网络配置。根据你的需求和具体情况,可能会有更多命令和操作需要学习。
MyBatis:
所有SQL语句全部自己写
手动解析实体关系映射转换为MyBatis内部对象注入容器
不支持Lambda形式调用
Mybatis Plus:
强大的条件构造器,满足各类使用需求
内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作
支持Lambda形式调用
提供了基本的CRUD功能,连SQL语句都不需要编写
自动解析实体关系映射转换为MyBatis内部对象注入容器
借助数组进行分页
借助Sql语句进行分页
拦截器分页:MybatisPlusInterceptor
RowBounds实现分页
从磁盘读到内存,从内存写到磁盘
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
公平锁―是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁
**IoC(Inversion of Control)和DI(Dependency Injection)**都是软件设计和开发中的重要概念,用于管理组件之间的关系和依赖,但它们有一些区别:
IoC(Inversion of Control):
DI(Dependency Injection):
总结:
在现代软件开发中,特别是在使用框架和容器的情况下,IoC和DI已成为常见的设计和开发原则,它们有助于提高代码的可维护性、可测试性和松耦合性。
第一个是面向接口,第二个是面向对象
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FilterEvenNumbersInList {
public static void main(String[] args) {
// 创建一个包含数组的 List
List<Integer[]> list = new ArrayList<>();
list.add(new Integer[]{1, 2, 3, 4, 5});
list.add(new Integer[]{6, 7, 8, 9, 10});
list.add(new Integer[]{11, 12, 13, 14, 15});
// 使用 Java Stream 进行过滤
List<Integer[]> filteredList = list.stream()
.map(arr -> Arrays.stream(arr)
.filter(num -> num % 2 == 0)
.toArray(Integer[]::new))
.collect(Collectors.toList());
// 打印结果
filteredList.forEach(arr -> System.out.println(Arrays.toString(arr)));
}
}
iMap,Collection两大类
Map分为hashMap和TreeMap
Collection分为Set和List
同步IO是指,读写IO时代码必须等待数据返回后才继续执行后续代码,它的优点是代码编写简单,缺点是CPU执行效率低。
而异步IO是指,读写IO时仅发出请求,然后立刻执行后续代码,它的优点是CPU执行效率高,缺点是代码编写复杂。
&
和 &&
都是在Java中用于执行逻辑与操作的运算符,但它们之间有一些重要的区别:
&
(按位与):
&
是一个按位运算符,用于对两个整数值的二进制位进行逐位与操作。&
执行逻辑与操作,但不会进行短路求值。false
,&
仍然会计算并评估第二个操作数。示例:
int a = 5; // 二进制:0101
int b = 3; // 二进制:0011
int result = a & b; // 结果是1,二进制:0001
System.out.println("false & false 结果是"+(false & false));//结果是false
System.out.println("a | b 结果是"+(a | b));//有1是1 0111
System.out.println("a & b 结果是"+(a & b));//双1是1 0001
打印结果
false & false 结果是false
a | b 结果是7
a & b 结果是1
&&
(逻辑与):
&&
是一个逻辑运算符,用于在布尔表达式中执行逻辑与操作。false
,则不会继续评估第二个操作数,因为无论如何结果都将是false
。示例:
boolean x = true;
boolean y = false;
boolean result = x && y; // 结果是false,不会评估y
总结:
&
是按位与运算符,可以用于整数运算和逻辑运算,不会短路求值。&&
是逻辑与运算符,仅用于逻辑运算,会进行短路求值,提高了程序的效率和安全性。Integer f1 = 100,f2 = 100,f3 = 150,f4 = 150;
System.out.println(f1 == f2);//true
System.out.println(f3 == f4);//false
如果字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1f2的结果是true,而f3f4的结果是false。
System.out.println(2 >> 32)
是多少是2
HashMap
和HashSet
是Java集合框架中的两种不同的数据结构,它们有一些重要的区别:
数据结构:
HashMap
:是一种键值对(key-value)映射表,用于存储一组键值对,每个键都唯一,可以用于快速查找和访问值。HashSet
:是一种集合,用于存储一组唯一的元素,不允许重复值。元素类型:
HashMap
:包含键值对,其中键和值可以是任意类型的对象。HashSet
:包含唯一的元素,通常是对象,但也可以包含基本数据类型的值,因为它们会被自动装箱。存储方式:
HashMap
:使用哈希表来存储键值对,通过键的哈希码来定位值的存储位置,允许通过键来查找值。HashSet
:也使用哈希表来存储唯一元素,通过元素的哈希码来确定存储位置,允许通过元素来查找。操作:
HashMap
:提供了put(key, value)
来插入键值对,get(key)
来根据键获取值,以及其他与键相关的操作。HashSet
:提供了add(element)
来添加元素,contains(element)
来检查是否包含特定元素,以及其他与元素相关的操作。重复值:
HashMap
:允许不同键对应相同的值,但不允许重复的键。HashSet
:不允许重复的元素,如果试图添加重复的元素,它将被忽略。迭代顺序:
HashMap
:不保证键值对的顺序,可以通过LinkedHashMap
来保持插入顺序。HashSet
:不保证元素的顺序,可以通过LinkedHashSet
来保持插入顺序。线程安全性:
HashMap
和 HashSet
都不是线程安全的。如果在多线程环境中使用它们,需要采取额外的同步措施或使用线程安全的集合类。总之,HashMap
用于存储键值对,而HashSet
用于存储唯一元素。它们有不同的用途和适用场景,因此在选择使用哪个取决于您的需求。如果需要键值对映射,HashMap
更合适;如果需要存储唯一元素的集合,HashSet
更适合。
synchronized
关键字用于实现多线程并发控制,可以用于锁定静态方法和非静态方法,但它们之间有一些关键区别:
锁定对象:
synchronized
锁定的是类级别的锁,也称为类锁。这意味着不论多少个实例对象,它们都共享同一个类级别的锁,只有一个线程能够进入这个静态方法。synchronized
锁定的是实例级别的锁,每个实例对象都有自己的锁,因此多个实例对象可以并发执行各自的实例方法,不会互斥。锁定范围:
synchronized
影响的是整个类,即无论调用哪个静态方法,都会互斥。synchronized
仅影响当前实例对象,不会阻止其他实例对象的方法调用。下面是一个示例来说明这两者之间的不同:
public class MyClass {
public synchronized void synchronizedMethod() {
// 这是一个实例方法,锁定的是当前实例对象
}
public static synchronized void synchronizedStaticMethod() {
// 这是一个静态方法,锁定的是整个类
}
}
使用 synchronized
静态方法可以控制对类级别资源的并发访问,而使用 synchronized
实例方法则可以控制对实例级别资源的并发访问。根据你的需求和具体情况,选择适合的锁定方式。需要注意的是,过多的锁定会导致性能问题,因此在设计并发程序时需要谨慎选择锁定的粒度。
Spring框架是一个广泛用于构建Java企业级应用程序的框架,其中Spring MVC(Model-View-Controller)是Spring框架的一部分,用于构建Web应用程序。以下是Spring MVC的请求处理流程:
客户端发起请求:
http://example.com/myapp/somePage
。前端控制器(DispatcherServlet)接收请求:
DispatcherServlet
的前端控制器充当所有请求的入口点。DispatcherServlet
会拦截到所有请求,然后根据配置将它们路由到适当的处理程序(Controller)。处理程序映射:
DispatcherServlet
使用处理程序映射器(Handler Mapping)来确定请求应该由哪个处理程序(Controller)来处理。处理程序映射器将请求URL映射到一个具体的处理程序。处理程序执行:
DispatcherServlet
调用相应的处理程序方法来执行业务逻辑。处理程序方法通常会访问模型(Model)来存储和获取数据,并返回视图(View)名称。业务逻辑执行:
模型更新:
视图解析:
DispatcherServlet
使用视图解析器(View Resolver)来解析处理程序返回的视图名称,以确定要使用哪个视图模板来呈现响应。视图呈现:
响应发送:
DispatcherServlet
将视图生成的响应发送回客户端,通常是浏览器。这是通过HTTP响应完成的。请求生命周期结束:
Spring MVC提供了丰富的配置选项,允许您自定义处理程序、拦截器、视图解析器等,以适应不同的应用程序需求。这个流程提供了一个清晰的结构,使得构建和维护Web应用程序变得更加容易。
byte c = 2;
c += 1;//合法 相当于c = (byte)(c + 1);
byte a = 1;
a = a + 1;//不合法 这里结果是int类型
int g = a + 1;//合法
在MySQL中,有几种常见的索引类型,每种类型都有其特定的用途和性能特点。以下是MySQL中常见的索引类型:
B-tree 索引:B-tree(平衡树)索引是MySQL中最常用的索引类型。它适用于各种查询,包括等值查询、范围查询和排序。B-tree索引可以用于所有存储引擎,包括InnoDB和MyISAM。
哈希索引:哈希索引适用于等值查询,但不适用于范围查询或排序。它们通常在内存表上使用,例如MEMORY存储引擎。InnoDB存储引擎也支持自动创建哈希索引以加速特定类型的查询。
全文索引:全文索引用于在文本数据上执行全文搜索。它们主要用于MyISAM存储引擎,并且支持全文搜索功能,以查找匹配的文本。
空间索引:空间索引用于处理空间数据类型,例如地理信息系统(GIS)数据。MySQL支持用于空间索引的特定类型,如R-tree。
前缀索引:前缀索引允许您只索引列值的一部分。这可以用于减小索引的大小,但也会降低查询性能。前缀索引通常用于处理大文本列。
复合索引:复合索引是包含多个列的索引。它们用于优化需要多个列的查询,例如多列的等值查询或范围查询。复合索引的列顺序非常重要,因为它们影响查询性能。
唯一索引:唯一索引确保索引列的值是唯一的,不允许重复值。这种索引用于实现唯一性约束,以确保表中的数据不包含重复记录。
主键索引:主键索引是一种特殊的唯一索引,它唯一标识表中的每一行,并且每个表只能有一个主键索引。主键索引通常与主键列关联,用于快速查找和引用表中的特定行。
外键索引:外键索引是用于实现外键关系的索引。它们用于维护表之间的引用完整性,确保在关联表中的数据一致性。
全列索引:全列索引是包含表中所有列的索引。通常,它们用于覆盖索引,以避免回表操作,从而提高性能。
每种索引类型都有其适用场景和性能考虑因素,根据您的查询需求和数据模型来选择适当的索引类型非常重要。不正确的索引策略可能会导致性能问题。
B-tree(平衡树)索引是MySQL中最常用的索引类型之一,用于加速各种查询操作,包括等值查询、范围查询和排序。下面是如何使用B-tree索引的一些示例:
创建B-tree索引:
您可以在表的列上创建B-tree索引,以加速对该列的查询。例如,假设您有一个名为users
的表,并且要在username
列上创建索引:
CREATE INDEX idx_username ON users(username);
这将在username
列上创建一个B-tree索引,以加速与username
相关的查询。
等值查询:
B-tree索引特别适用于等值查询,例如:
SELECT * FROM users WHERE username = 'john_doe';
在这种情况下,MySQL可以使用B-tree索引快速查找username
等于’john_doe’的行。
范围查询:
B-tree索引还可以用于范围查询,例如:
SELECT * FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-12-31';
在这种情况下,MySQL可以使用B-tree索引快速查找order_date
在指定范围内的订单。
排序:
B-tree索引也有助于加速排序操作,因为它们存储了索引列的数据的有序副本。例如,如果您需要按user_id
列对用户表进行升序排序:
SELECT * FROM users ORDER BY user_id ASC;
B-tree索引可以加速这个排序操作。
覆盖索引:
如果您的查询只需要从索引中获取数据,而不需要回到表中检索其他列的值,那么可以使用覆盖索引来提高性能。例如:
SELECT username FROM users WHERE user_id = 123;
如果在user_id
列上有一个B-tree索引,并且只需获取username
列的值,MySQL可以完全使用索引,而不需要回到表中查找。
请注意,创建索引会增加插入、更新和删除操作的开销,因此需要根据查询需求和数据库的使用模式仔细考虑索引的创建。同时,还要定期维护索引以确保其性能。使用EXPLAIN
语句可以帮助您分析查询的执行计划,以确定是否有效地使用了B-tree索引。
B-tree(B树)是一种常用于数据库和文件系统中的索引结构,它的底层结构包括以下重要组成部分:
节点(Node): B-tree是由一系列节点组成的树结构。每个节点通常表示一个范围或键值范围,并包含了与这个范围相关的数据条目。在数据库中,通常是索引键和指向数据行的指针。
根节点(Root Node): B-tree的顶层节点称为根节点。根节点包含了对整个B-tree的引用。根节点通常是一个特殊的节点,它可以有多个子节点,用于分割数据范围。
内部节点(Internal Node): 除了根节点外,B-tree还包括内部节点。内部节点通常包含一组键值范围,用于确定从该节点分支到哪个子节点。内部节点不包含实际的数据,而是用于导航到数据的叶子节点。
叶子节点(Leaf Node): 叶子节点是B-tree的最底层节点,它们包含了实际的数据条目。在数据库中,叶子节点通常包含索引键和指向数据行的指针。叶子节点之间可以按照键值排序,以支持范围查询。
平衡: B-tree通常是平衡的,这意味着从根节点到叶子节点的最长路径和最短路径的长度之差是有限的。这种平衡性使得B-tree的查询、插入和删除操作都具有良好的性能。
分支因子(Branching Factor): B-tree的每个内部节点通常有多个子节点,这个子节点的数量称为分支因子。分支因子决定了B-tree的层数和容量。
B-tree索引的主要优点之一是它的高效性能,特别适用于范围查询和区间查询。由于B-tree的平衡性和分支因子的设计,查询和维护操作的时间复杂度通常是O(log n),其中n是B-tree中的节点数量。
B-tree索引广泛用于关系型数据库管理系统(RDBMS)中,例如在MySQL、PostgreSQL、Oracle等数据库中。这些数据库使用B-tree索引来加速查询操作,提高数据检索性能。此外,B-tree的变种,如B+树和B*树,也被广泛使用,它们在某些场景下进一步优化了性能和存储效率。
使用MySQL索引是优化数据库性能的重要步骤之一,但需要注意一些重要事项,以确保索引的正确性和有效性。以下是使用MySQL索引时需要注意的事项:
选择合适的列: 索引应该选择那些经常用于检索数据的列,通常是WHERE
子句中使用的列、JOIN
操作中连接的列、ORDER BY
子句中用于排序的列和GROUP BY
子句中用于分组的列。
避免不必要的索引: 不要为每个列都创建索引,只创建必要的索引。过多的索引会增加数据维护的开销,降低插入、更新和删除操作的性能。
使用索引覆盖查询: 如果查询只需要从索引中获取数据,而不需要访问实际的数据行,这种情况下称为索引覆盖查询,可以提高查询性能。
考虑复合索引: 复合索引包括多个列,可以提高特定查询的性能。但要注意不要创建过于复杂的复合索引,以避免降低插入和更新操作的性能。
使用前缀索引: 对于长文本列,可以考虑使用前缀索引,只索引文本的一部分字符,以减小索引的大小。
定期维护和优化索引: 定期分析和重建索引可以提高索引的性能。MySQL提供了ANALYZE TABLE
和OPTIMIZE TABLE
等命令用于这些操作。
避免使用通配符开头的LIKE
查询: 使用通配符开头的LIKE
查询(例如LIKE '%abc'
)无法使用索引,因为无法利用索引的前缀匹配优化。如果需要模糊查询,尽量避免通配符在开头。
小心使用索引提示: MySQL允许使用索引提示来强制查询使用特定的索引,但这应该谨慎使用。不正确的索引提示可能导致性能下降。
注意表结构的设计: 良好的表结构设计可以减少索引的需求。遵循范式规范,避免过度冗余数据,可以改善查询性能。
监控索引性能: 使用MySQL的性能监控工具来监视索引的使用情况,以及执行计划是否符合预期。
考虑存储引擎: 不同的MySQL存储引擎(例如InnoDB、MyISAM、Memory等)对索引的处理方式不同。选择存储引擎时要考虑索引的性能需求。
备份和恢复索引: 在备份和恢复数据库时,确保索引也得到了适当的处理,以防止数据丢失。
综上所述,使用MySQL索引需要谨慎考虑,结合具体的业务需求和查询模式来选择合适的索引策略。了解索引的工作原理以及监控索引性能是优化数据库性能的关键步骤。同时,定期维护和优化索引也是保持数据库高性能的关键因素之一。
使用MySQL的查询执行计划解释工具(EXPLAIN)可以帮助你了解查询是如何执行的,以便进行进一步的优化
索引不会包含有null值的列
只要列中包含有null值都将不会被包含在索引中,复合索引中只要有一列含有null值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为null。
使用短索引
对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
索引列排序
查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
like语句操作
一般情况下不推荐使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。
不要在列上进行运算
这将导致索引失效而进行全表扫描,例如
SELECT * FROM table_name WHERE YEAR(column_name)<2017;
不使用not in和<>操作
可以的
public interface Book {
String abc = "open";
Shop getShopName(Shop shop);
}
public class TestMain {
public static void main(String[] args) {
System.out.println(Book.abc);
}
}
结果是:open
请注意,尽管在接口中定义字段是合法的,但这种做法通常不被推荐,因为接口的主要目的是定义方法签名,而不是存储数据。通常情况下,应该使用常量类或枚举来定义常量,而不是将常量放在接口中。
在Java中,一个接口可以继承多个其他接口,这种多继承的机制是通过使用关键字 extends
来实现的。当一个接口继承多个其他接口时,它可以继承这些接口的方法定义,从而具备了多个接口的功能。
下面是一个示例,演示了如何在Java中实现接口之间的多继承:
interface A {
void methodA();
}
interface B {
void methodB();
}
// 接口C继承了接口A和接口B
interface C extends A, B {
void methodC();
}
class MyClass implements C {
@Override
public void methodA() {
System.out.println("Method A");
}
@Override
public void methodB() {
System.out.println("Method B");
}
@Override
public void methodC() {
System.out.println("Method C");
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.methodA();
obj.methodB();
obj.methodC();
}
}
在上述示例中,接口 C
继承了接口 A
和接口 B
,因此它包含了这两个接口的方法定义。类 MyClass
实现了接口 C
,因此它需要实现接口 C
中的所有方法,以及接口 A
和接口 B
中的方法。
总结起来,Java中的接口之间可以多继承,使用 extends
关键字来实现多继承关系。这种方式允许你将多个接口的功能组合到一个接口中,从而提供更灵活的接口设计和实现。
不能,反之是可以的,抽象类可以实现抽象方法
在Java中,接口(interface)和抽象类(abstract class)是两种不同的概念,它们有不同的用途和特性。
接口(Interface):接口是一种抽象类型,它可以包含抽象方法的声明,但不能包含方法的实现。接口通常用于定义一组方法签名,而不提供具体的实现。类可以实现(implements)一个或多个接口,实现接口的类必须提供接口中声明的所有方法的实现。
抽象类(Abstract Class):抽象类也是一种抽象类型,但它可以包含抽象方法的声明以及方法的实现。抽象类用于定义具有一些通用实现的类,但允许派生类覆盖(override)其中的方法,也可以包含非抽象的方法。
接口不能直接实现抽象类,因为它们是不同的概念。一个类可以同时实现一个接口并扩展一个抽象类,这是允许的。例如:
// 接口
interface MyInterface {
void methodA();
}
// 抽象类
abstract class MyAbstractClass {
abstract void methodB();
}
// 实现接口并扩展抽象类的类
class MyClass extends MyAbstractClass implements MyInterface {
@Override
void methodB() {
// 实现抽象方法
}
@Override
public void methodA() {
// 实现接口方法
}
}
在上述示例中,MyClass
类同时实现了MyInterface
接口和扩展了MyAbstractClass
抽象类。它必须提供抽象类中的抽象方法和接口中的方法的实现。
总之,接口和抽象类都有其用途,它们可以在Java中用于不同的情景和需求。您可以根据具体的设计需求来选择使用哪个或同时使用两者。
不能表示
汉字可以用char类型或者String类型表示
可以表示
char letter = 'A'; // 字母字符 'A'
int intValue = (int) letter; // 将字母字符 'A' 转换为 int 值
System.out.println(intValue); // 输出 65
在Java中,基本数据类型的大小是规定的,但具体的大小可能因不同的JVM实现和平台而有所不同。通常情况下,Java中的基本数据类型的大小如下:
需要注意的是,这些基本数据类型的大小是Java语言规范中定义的标准大小。然而,不同的Java虚拟机(JVM)实现和不同的平台可能会有微小的差异,但这些差异通常很小,不会对大多数Java程序产生显著影响。
此外,Java的基本数据类型在内存中的布局也可以受到虚拟机的实现和平台的影响,例如,字节对齐等。因此,如果需要严格控制内存布局,可以考虑使用Java的ByteBuffer
等机制。
初始化项目
git init
将当前目录下所有需要上传的文件代码等资源添加到缓存区
添加一个或多个文件到暂存区:
git add [file1] [file2]
添加指定目录到暂存区,包括子目录:
git add [dir]
添加当前目录下的所有文件到暂存区:
git add .
提交缓存区里面的主要内容到本地仓库
git commit -m “提交说明”
添加一个远程仓库的地址
git remote add origin https://gitee.com/xxxxxxx(远程仓库的地址)
将远程仓库进行下拉,获取同步
git pull --rebase origin master
提交本地仓库到远程仓库
git push -u origin master
因为第一次提交 所以要加一个 -u
表锁,页锁,行锁
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低,使用表级锁定的有MyISAM,MEMORY,CSV等一些非事务性存储引擎;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高,使用行级锁定的主要是InnoDB存储引擎;
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般,使用页级锁定的主要是BerkeleyDB存储引擎。
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。
共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE
说两个维度:
共享锁(简称S锁)和排他锁(简称X锁)
读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁和行锁两种。
表锁和行锁
表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。
行锁又可以分为乐观锁和悲观锁
悲观锁可以通过for update实现
乐观锁则通过版本号实现。
MySQL的缓存是用于提高数据库性能的重要组成部分,主要包括以下几种类型的缓存:
查询缓存(Query Cache): 查询缓存是MySQL中最基本的缓存类型,它可以缓存查询的结果集。当一个查询被执行后,MySQL会检查查询缓存,如果之前执行过相同的查询且缓存仍有效,就会直接返回缓存中的结果,而不需要再次执行查询。但要注意,查询缓存在MySQL 5.7版本及以后已经被弃用,不再推荐使用,因为它存在一些性能问题和限制。
缓冲池(Buffer Pool): 缓冲池是用于缓存数据页的内存区域。MySQL将磁盘上的数据分成数据页,当查询需要访问数据时,MySQL会首先查看缓冲池中是否已经加载了该数据页,如果存在就直接从内存中读取,而不需要访问磁盘。缓冲池的大小可以配置,较大的缓冲池可以提高查询性能。
键值缓存(Key-Value Cache): MySQL支持使用外部缓存系统,如Memcached或Redis,来缓存查询结果或数据。这些缓存系统通过将热门数据存储在内存中,可以显著提高读取操作的性能。MySQL可以与这些缓存系统集成,以支持缓存查询或数据。
表缓存(Table Cache): 表缓存用于存储表的元数据信息,例如表的结构、列的数据类型等。这些信息在执行查询和优化查询时经常被使用,因此缓存它们可以提高性能。
日志缓存(Log Cache): 日志缓存用于缓存二进制日志(binary logs)的内容,这对于主从复制等数据库复制操作非常重要。它可以加速数据同步过程。
查询结果缓存(Result Cache): 查询结果缓存是一种缓存查询的结果,而不是查询语句本身。与查询缓存不同,查询结果缓存通常更灵活,允许缓存不同参数的查询结果,但需要在应用程序中显式管理缓存。
存储过程和函数缓存: MySQL还可以缓存存储过程和函数的执行计划,以减少重复执行相同存储过程和函数的开销。
要有效地使用MySQL缓存,需要根据具体的应用和工作负载来进行适当的配置和调整。缓存的大小、清除策略、缓存命中率等都需要根据实际情况进行优化,以确保提高数据库性能。同时,还要注意监控缓存的使用情况,以及定期清理和维护缓存,以防止缓存过期或过度膨胀。
线程池是一种用于管理和复用线程的机制,它可以预先创建一组线程,然后将任务分配给这些线程执行。通过使用线程池,可以避免频繁地创建和销毁线程,从而降低线程开销,提高系统性能和资源利用率。
在Java中,你可以使用Executor
框架来创建线程池,以管理和执行多个任务。线程池可以帮助你更有效地使用系统资源,并提高多线程应用程序的性能。以下是创建线程池的一些常见方式:
使用Executors
工厂方法创建线程池:
java.util.concurrent.Executors
类提供了一些工厂方法,用于创建不同类型的线程池。以下是一些示例:
创建一个固定大小的线程池:
ExecutorService executor = Executors.newFixedThreadPool(5); // 创建一个包含5个线程的固定大小线程池
创建一个单线程的线程池:
ExecutorService executor = Executors.newSingleThreadExecutor(); // 创建一个单线程的线程池
创建一个缓存线程池:
ExecutorService executor = Executors.newCachedThreadPool(); // 创建一个根据需求自动调整线程数的线程池
手动创建线程池:
你也可以手动创建一个ThreadPoolExecutor
,以更精细地控制线程池的属性,如核心线程数、最大线程数、线程空闲时间等。以下是一个示例:
int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 5000; // 5秒
TimeUnit unit = TimeUnit.MILLISECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
ExecutorService executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);
执行任务:
无论你选择哪种方式创建线程池,一旦创建完成,你可以使用execute()
方法提交任务供线程池执行,或者使用submit()
方法提交任务并获取Future
对象以便跟踪任务的执行进度和结果。
executor.execute(() -> {
// 在这里执行你的任务逻辑
});
关闭线程池:
最后,不要忘记在程序结束时关闭线程池,以释放资源。
executor.shutdown(); // 关闭线程池
以上是创建和使用线程池的基本步骤。你可以根据自己的需求和项目的复杂性来选择不同类型的线程池,并配置线程池的属性以优化性能。确保在使用完线程池后关闭它,以避免资源泄漏。
在Spring Boot中创建定时任务有多种写法,其中最常用的是使用@Scheduled
注解和实现SchedulingConfigurer
接口。以下是几种创建定时任务的写法:
使用@Scheduled注解:
使用@Scheduled
注解是最简单的方式之一,它可以直接标注在需要定时执行的方法上。您可以在方法上设置定时任务的触发时间、周期、延迟等属性。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTasks {
@Scheduled(fixedRate = 5000) // 每5秒执行一次
public void myTask() {
// 定时任务的逻辑
}
}
使用Cron表达式:
使用Cron表达式可以更灵活地定义定时任务的执行时间,例如每天凌晨执行、每周某一天的某个时间执行等。
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTasks {
@Scheduled(cron = "0 0 0 * * ?") // 每天凌晨执行
public void myTask() {
// 定时任务的逻辑
}
}
实现SchedulingConfigurer接口:
通过实现SchedulingConfigurer
接口,您可以更灵活地配置定时任务。这种方式适用于需要动态配置定时任务的情况。
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@Configuration
@EnableScheduling
public class MySchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addCronTask(() -> {
// 定时任务的逻辑
}, "0 0 0 * * ?"); // 每天凌晨执行
}
}
4.使用Quartz Scheduler:
使用 Quartz Scheduler 实现定时任务需要以下步骤:
引入 Quartz 依赖: 首先需要在项目中引入 Quartz 的依赖,以便使用 Quartz 的 API。可以通过 Maven、Gradle 或手动下载 jar 包的方式引入。
创建 Job 类: 创建一个类来实现定时任务的逻辑,该类需要实现 org.quartz.Job
接口,并实现 execute()
方法。
配置 JobDetail: 配置 JobDetail
对象,指定要执行的 Job 类以及其它属性。
配置 Trigger: 配置触发器 Trigger
,指定定时任务的触发规则,如执行时间间隔、执行次数等。
配置 Scheduler: 创建 Scheduler
对象,并将 JobDetail
和 Trigger
关联起来。
启动 Scheduler: 启动 Scheduler
,定时任务将会按照配置的触发规则执行。
下面是一个简单的示例,演示了如何使用 Quartz Scheduler 实现一个定时任务:
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class QuartzSchedulerExample {
public static void main(String[] args) throws SchedulerException {
// 创建 Scheduler 实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
// 创建 JobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();
// 创建触发器 Trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10) // 每隔10秒执行一次
.repeatForever()) // 一直重复执行
.build();
// 将 JobDetail 和 Trigger 关联起来
scheduler.scheduleJob(jobDetail, trigger);
}
// 自定义 Job 类
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("定时任务执行:" + System.currentTimeMillis());
}
}
}
在这个示例中,我们创建了一个 Scheduler
实例并启动了它。然后,我们创建了一个 JobDetail
对象,指定要执行的 MyJob
类,并创建了一个触发器 Trigger
,指定了触发规则(每隔10秒执行一次)。最后,我们将 JobDetail
和 Trigger
关联起来,并将其加入到 Scheduler
中。
这样,定时任务就会按照指定的触发规则执行。
5.线程池
另一种创建线程池的写法是使用 ScheduledThreadPoolExecutor
类,它是 ScheduledExecutorService
接口的实现类,可以更灵活地配置线程池的参数。下面是使用 ScheduledThreadPoolExecutor
的示例代码:
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(() -> {
System.out.println("定时任务执行:" + System.currentTimeMillis());
}, 0, 1, TimeUnit.SECONDS); // 每隔 1 秒执行一次
try {
Thread.sleep(5000); // 让程序运行 5 秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
在这个示例中,我们使用 ScheduledThreadPoolExecutor
的构造函数来创建一个大小为 1 的线程池,然后使用 scheduleAtFixedRate()
方法安排了一个定时任务,使其每隔 1 秒执行一次。与 Executors.newScheduledThreadPool()
方法相比,ScheduledThreadPoolExecutor
允许更灵活地配置线程池的参数,如核心线程数、最大线程数、线程空闲时间等。
InnoDB存储引擎支持多个事务隔离级别,这些隔离级别控制了事务之间的可见性和并发行为。不同的隔离级别提供不同的事务隔离程度,以满足不同的应用需求。以下是InnoDB支持的事务隔离级别:
READ UNCOMMITTED(读取未提交): 在这个最低级别的隔离中,一个事务可以读取另一个事务尚未提交的数据。这意味着事务之间没有任何隔离,可能会导致脏读(读取到未提交的数据)、不可重复读(同一查询在事务执行期间返回不同的结果)和幻影读(同一查询在事务执行期间返回不同数量的行)等问题。通常情况下,不推荐使用这个隔离级别。
READ COMMITTED(读取已提交): 这是大多数数据库系统的默认隔离级别,也是MySQL的默认隔离级别。在这个级别中,一个事务只能读取已经提交的数据,不会读取到未提交的数据。这解决了脏读问题,但仍然可能出现不可重复读和幻影读。
REPEATABLE READ(可重复读): 在这个隔离级别中,一个事务可以读取和锁定其他事务正在使用的数据,但不会读取未提交的数据。这可以防止脏读和不可重复读,但仍然可能发生幻影读。这是Innodb的默认隔离级别。
SERIALIZABLE(串行化): 这是最高的事务隔离级别,它确保了最高的隔离性。在这个级别中,事务是串行执行的,不会出现任何并发问题,包括幻影读。虽然它提供了最高的隔离性,但通常会导致性能下降,因为它限制了并发性。
你可以在MySQL中使用SET TRANSACTION ISOLATION LEVEL
语句来设置事务的隔离级别,例如:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
不同的应用场景可能需要不同的事务隔离级别。你需要根据应用的需求和性能要求来选择合适的隔离级别。通常情况下,READ COMMITTED或REPEATABLE READ是较常用的隔离级别。如果需要更高的隔离性,可以选择SERIALIZABLE,但要注意可能的性能影响。
Atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
@Autowired注解由Spring提供,只按照byType注入;@resource注解由J2EE提供,默认按照byName自动注入。
@Autowired功能虽说非常强大,但是也有些不足之处。比如:比如它跟spring强耦合了,如果换成了JFinal等其他框架,功能就会失效。而@Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持。
除此之外,有些场景使用@Autowired无法满足的要求,改成@Resource却能解决问题。接下来,我们重点看看@Autowired和@Resource的区别。
@Autowired默认按byType自动装配,而@Resource默认byName自动装配。
@Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。
@Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。
@Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。
Redis是一种支持key-value等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。
读写性能优异
Redis能读的速度是110000次/s,写的速度是81000次/s (测试条件见下一节)。
数据类型丰富
Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子性
Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
丰富的特性
Redis支持 publish/subscribe, 通知, key 过期等特性。
持久化
Redis支持RDB, AOF等持久化方式
发布订阅
Redis支持发布/订阅模式
分布式
Redis Cluster
Set方法注入
构造器注入
静态工厂注入
实例工厂注入
在使用上述配置文件时,可以直接@Value(“${user.userName}”)等等。
可以注入基本类型
@TableField(exist = false):表示该属性不为数据库表字段,但又是必须使用的。 @TableField(exist = true):表示该属性为数据库表字段。
@SpringBootApplication
这是 Spring Boot 最最最核心的注解,用在 Spring Boot 主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。
三个注解的组合,也可以用这三个注解来代替 @SpringBootApplication 注解。
@SpringBootConfiguration:@SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类。
@EnableAutoConfiguration:启用Spring的自动加载配置。
@ComponentScan:该注解定义了Spring将自动扫描包主类所在包及其子包下的bean
如果你项目中所有的类都定义在主类包及其子包下,那你不需要做任何事,如果有别的类不在,需要注解后面配置当前包位置,和主类包位置
定义:
@Component
@Bean
@Controller
@Service
@Repository
装备:
@Autowire:只按照类型
@Resources:默认按照名称找,找不到按照类型找
阿里巴巴(Alibaba Group)有一套广泛使用的编程规范,被称为阿里巴巴Java开发手册。这个规范是基于阿里巴巴多年的软件开发经验和最佳实践建立的,旨在提高代码的质量、可维护性和可读性。以下是一些常见的阿里巴巴Java编程规范的要点:
命名规范:
.
分隔(例如:com.alibaba.package)。代码格式:
注释:
异常处理:
Exception
。避免使用魔法数值和魔法字符串,应该使用常量或枚举来代替。
类设计:
包管理:
import java.util.*
)。并发编程:
性能优化:
+
操作符。测试:
这些是阿里巴巴Java开发手册的一些要点,该手册还包含更多详细的规范和实践建议,以确保Java代码的质量和一致性。请注意,这些规范可能随着时间而变化,建议查阅阿里巴巴的官方文档以获取最新的信息和指导。
一个方法不能超过50行
复杂功能要写注释
格式化对其
抽取公共方法
数据库连接的表不能超过五个
首先,@ComponentScan是组件扫描注解,用来扫描@Controller @Service @Repository这类,主要就是定义扫描的路径从中找出标志了需要装配的类到Spring容器中
其次,@MapperScan 是扫描mapper类的注解,配置包的路径,就不用在每个mapper类上加@Mapper
在Java中,常用的反射方式主要包括以下几种:
获取Class对象: 获取Class对象是反射的起点,可以通过以下方式获取:
.getClass()
方法。.class
字面常量。Class.forName("完整类名")
方法。下面是示例代码:
// 使用对象的 getClass() 方法
MyClass obj = new MyClass();
Class<?> clazz1 = obj.getClass();
// 使用 .class 字面常量
Class<?> clazz2 = MyClass.class;
// 使用 Class.forName() 方法
Class<?> clazz3 = Class.forName("包名.MyClass");
获取构造函数并创建对象: 通过Class对象可以获取类的构造函数,进而创建对象。示例代码如下:
Class<?> clazz = MyClass.class;
Constructor<?> constructor = clazz.getConstructor();
MyClass myObject = (MyClass) constructor.newInstance();
获取方法并调用: 使用Class对象可以获取类的方法,并通过反射调用方法。示例代码如下:
Class<?> clazz = MyClass.class;
Method method = clazz.getMethod("methodName", parameterTypes);
Object result = method.invoke(instance, arguments);
获取字段并设置/获取值: 通过反射可以获取类的字段,并设置/获取字段的值。示例代码如下:
Class<?> clazz = MyClass.class;
Field field = clazz.getDeclaredField("fieldName");
field.setAccessible(true); // 如果字段是私有的,需要设置为可访问
Object value = field.get(instance); // 获取字段的值
field.set(instance, newValue); // 设置字段的值
操作注解: 反射可以用于操作类、方法、字段上的注解信息。示例代码如下:
Class<?> clazz = MyClass.class;
// 获取类上的注解
MyAnnotation classAnnotation = clazz.getAnnotation(MyAnnotation.class);
// 获取方法上的注解
Method method = clazz.getMethod("methodName", parameterTypes);
MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);
// 获取字段上的注解
Field field = clazz.getDeclaredField("fieldName");
MyAnnotation fieldAnnotation = field.getAnnotation(MyAnnotation.class);
获取泛型类型信息: 反射可以用于获取类、方法、字段的泛型类型信息。示例代码如下:
Class<?> clazz = MyClass.class;
// 获取类的泛型信息
Type[] genericTypes = clazz.getGenericInterfaces();
// 获取方法的泛型参数类型
Method method = clazz.getMethod("methodName", parameterTypes);
Type[] parameterTypes = method.getGenericParameterTypes();
// 获取字段的泛型类型
Field field = clazz.getDeclaredField("fieldName");
Type fieldType = field.getGenericType();
需要注意的是,反射是一项强大但复杂的特性,应谨慎使用,因为它可能绕过编译时的类型检查,导致运行时异常。在使用反射时,应确保有充分的理由和必要性,并遵循最佳实践,同时保持代码的可读性和维护性。
72.并行和并发有什么区别?
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
73.线程和进程的区别?
进程是资源分配的最小单位,线程是CPU调度的最小单位。一个进程包含多个线程,一个线程只能属于一个进程。进程在执行过程中拥有内存单元。进程比线程消耗更多的资源。
74.暂定守护线程是什么?
守护线程(即daemon thread),准确地来说就是服务其他的线程。
在Java中,有多种方式可以创建线程,以下是常见的几种方式:
继承Thread类: 这是一种创建线程的最基本方式,通过继承Thread
类并重写run
方法来定义线程的执行逻辑。然后创建线程对象并调用start
方法启动线程。
class MyThread extends Thread {
public void run() {
// 线程执行的逻辑
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
实现Runnable接口: 这是一种更灵活的创建线程的方式,通过实现Runnable
接口并将其作为参数传递给Thread
类来创建线程。这种方式支持多重继承,因为Java不支持多重继承。
class MyRunnable implements Runnable {
public void run() {
// 线程执行的逻辑
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 启动线程
}
}
使用匿名内部类: 可以使用匿名内部类来创建线程,特别适用于定义线程逻辑比较简单的情况。
Thread thread = new Thread(new Runnable() {
public void run() {
// 线程执行的逻辑
}
});
thread.start(); // 启动线程
使用Java 8的Lambda表达式: 在Java 8及更高版本中,可以使用Lambda表达式来简化线程的创建。
Thread thread = new Thread(() -> {
// 线程执行的逻辑
});
thread.start(); // 启动线程
使用线程池: Java提供了Executor
框架和线程池来管理线程的创建和执行。线程池可以重复使用线程,减少线程的创建和销毁开销。
ExecutorService executor = Executors.newFixedThreadPool(5); // 创建固定大小的线程池
executor.execute(() -> {
// 线程执行的逻辑
});
executor.shutdown(); // 关闭线程池
不同的线程创建方式适用于不同的场景。通常情况下,推荐使用实现Runnable
接口或Lambda表达式的方式,因为它们更灵活,并且可以避免继承的局限性。线程池也是一种管理线程的好方法,可以提高线程的复用性和效率。选择合适的线程创建方式取决于你的需求和代码设计。
通过Callable和Future创建线程
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象的target创建并启动新线程。
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
线程池有5种状态:Running、ShutDown、Stop、Tidying、Terminated。
在Java中,线程可以处于不同的状态,主要有以下几种线程状态:
New(新建): 当线程对象被创建但尚未调用其start()
方法时,线程处于新建状态。在这个状态下,线程还没有被启动执行。
Runnable(可运行): 当线程对象调用了start()
方法后,线程进入可运行状态。在这个状态下,线程已经被启动,但可能还未获得CPU的执行时间,因此它准备好了可以执行。
Blocked(阻塞): 线程在运行过程中可能会被阻塞,进入阻塞状态。这种情况通常是因为线程在等待某些条件满足,比如等待某个锁的释放。一旦条件满足,线程会从阻塞状态转移到可运行状态。
Waiting(等待): 线程进入等待状态,通常是因为调用了wait()
方法,或者等待某个条件的发生。在等待状态下,线程不会消耗CPU时间,需要其他线程唤醒它。
Timed Waiting(计时等待): 与等待状态类似,但是等待有一定的时间限制。线程可以在等待一段时间后自动恢复到可运行状态,例如调用了sleep()
方法或者join()
方法时。
Terminated(终止): 线程在执行完毕或者异常终止后进入终止状态。一旦线程处于终止状态,就不能再回到其他状态。当线程的run()
方法执行结束时,或者线程抛出了未捕获的异常,线程会进入终止状态。
sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
execute():只能执行 Runnable 类型的任务。
submit():可以执行 Runnable 和 Callable 类型的任务。
Callable 类型的任务可以获取执行的返回值,而 Runnable 执行无返回值。
线程安全在三个方面体现:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗,引入了 “偏向锁” 和 “轻量级锁”:锁一共有 4 种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。
偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中记录存储锁偏向的线程ID,以后该线程在进入同步块时先判断对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果存在就直接获取锁。
轻量级锁:当其他线程尝试竞争偏向锁时,锁升级为轻量级锁。线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,标识其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
重量级锁:锁在原地循环等待的时候,是会消耗CPU资源的。所以自旋必须要有一定的条件控制,否则如果一个线程执行同步代码块的时间很长,那么等待锁的线程会不断的循环反而会消耗CPU资源。默认情况下锁自旋的次数是10 次,可以使用-XX:PreBlockSpin参数来设置自旋锁等待的次数。10次后如果还没获取锁,则升级为重量级锁。
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁的四个必要条件:
互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
环路等待条件:是指进程发生死锁后,若干进程之间形成一种头尾相接的循环等待资源关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不满足,就不会发生死锁。
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和 解除死锁。
所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。
此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
synchronized 用的锁是存在 Java 对象头里的。在 JVM 中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。
同步方法通过ACC_SYNCHRONIZED 关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。
同步代码块通过monitorenter和monitorexit执行来进行加锁。当线程执行到monitorenter的时候要先获得锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。每个对象自身维护着一个被加锁次数的计数器,当计数器不为0时,只有获得锁的线程才能再次获得锁。
Java中的每个对象都可以作为锁,锁的是当前实例对象。具体表现为以下三种方式:
对于普通方法,锁的是当前对象
对于静态同步方法,锁的是当前类的class对象
对于同步方法块,锁住的是synchronized里括号里配置的内容
synchronized关键字可以保证并发编程的三大特性:原子性、可见性、有序性,而volatile关键字只能保证可见性和有序性,不能保证原子性,也称为是轻量级的synchronized。
原子性:一个或多个操作全部执行成功或者全部执行失败。synchronized关键字可以保证只有一个线程拿到锁,访问共享资源。
可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到。
执行synchronized时,会对应执行lock、unlock原子操作,保证可见性。
有序性:程序的执行顺序会按照代码的先后顺序执行。
悲观锁:synchronized关键字实现的是悲观锁,每次访问共享资源时都会上锁。
非公平锁:synchronized关键字实现的是非公平锁,即线程获取锁的顺序并不一定是按照线程阻塞的顺序。
可重入锁:synchronized关键字实现的是可重入锁,即已经获取锁的线程可以再次获取锁。
独占锁或者排他锁:synchronized关键字实现的是独占锁,即该锁只能被一个线程所持有,其他线程均被阻塞。
Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为 “重量级锁”。
1,作用的位置不同
synchronized是修饰方法,代码块。
volatile是修饰变量。
2,作用不同
synchronized,可以保证变量修改的可见性及原子性,可能会造成线程的阻塞;synchronized在锁释放的时候会将数据写入主内存,保证可见性;
volatile仅能实现变量修改的可见性,但无法保证原子性,不会造成线程的阻塞;volatile修饰变量后,每次读取都是去主内存进行读取,保证可见性
synchronized是修饰方法,代码块。
volatile是修饰变量。
首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
Synchronized是关键字,ReentrantLock是类
ReentrantLock可以替代synchronized进行同步;
ReentrantLock获取锁更安全,不会无限等待,造成死锁;
必须先获取到锁,再进入try {…}代码块,最后使用finally(unlock())保证释放锁;
Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
Atomic系列的类中的核心方法都会调用unsafe类中的几个本地方法。我们需要先知道一个东西就是Unsafe类,全名为:sun.misc.Unsafe,这个类包含了大量的对C代码的操作,包括很多直接内存分配以及原子操作的调用,而它之所以标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,需要小心使用,否则会导致严重的后果,例如在通过unsafe分配内存的时候,如果自己指定某些区域可能会导致一些类似C++一样的指针越界到其他进程的问题。
docker是一个用Go语言实现的开源项目,可以让我们方便的创建和使用容器,docker将程序以及程序所有的依赖都打包到docker container,这样你的程序可以在任何环境都会有一致的表现,这里程序运行的依赖也就是容器就好比集装箱,容器所处的操作系统环境就好比货船或港口,程序的表现只和集装箱有关系(容器),和集装箱放在哪个货船或者哪个港口(操作系统)没有关系。docker可以屏蔽环境差异,也就是说,只要你的程序打包到了docker中,那么无论运行在什么环境下程序的行为都是一致的
以下是一些常用的 Docker 命令,用于管理和操作 Docker 容器和镜像:
容器生命周期管理:
docker run <选项> <镜像>
: 创建并运行一个容器。docker start <容器ID或名称>
: 启动一个停止的容器。docker stop <容器ID或名称>
: 停止一个运行中的容器。docker restart <容器ID或名称>
: 重启一个容器。docker pause <容器ID或名称>
: 暂停一个运行中的容器。docker unpause <容器ID或名称>
: 恢复一个暂停的容器。docker rm <容器ID或名称>
: 删除一个容器。docker ps
: 列出运行中的容器。docker ps -a
: 列出所有容器,包括停止的容器。镜像操作:
docker images
: 列出本地镜像列表。docker pull <镜像>
: 从 Docker Hub 或其他仓库拉取镜像。docker rmi <镜像ID或名称>
: 删除一个本地镜像。docker build -t <镜像名>:<标签>
: 基于 Dockerfile 构建镜像。docker tag <源镜像> <目标镜像>
: 给镜像打标签。容器日志和执行命令:
docker logs <容器ID或名称>
: 查看容器的日志。docker exec -it <容器ID或名称> <命令>
: 在运行中的容器中执行命令。容器网络和端口:
docker network ls
: 列出 Docker 网络。docker port <容器ID或名称>
: 查看容器的端口映射。docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <容器ID或名称>
: 获取容器的 IP 地址。容器数据管理:
docker volume ls
: 列出 Docker 数据卷。docker volume create <卷名称>
: 创建一个数据卷。docker volume rm <卷名称>
: 删除一个数据卷。Docker Compose:
docker-compose up
: 启动 Docker Compose 项目。docker-compose down
: 停止并删除 Docker Compose 项目的容器。Docker Swarm(集群):
docker swarm init
: 初始化 Docker Swarm 集群。docker service create <选项> <服务名称>
: 创建一个 Docker 服务。docker node ls
: 列出 Swarm 集群节点。这些是一些常用的 Docker 命令,可用于管理容器和镜像,以及执行其他 Docker 相关操作。根据你的具体需求和项目,还有更多高级的 Docker 命令和配置选项可供使用。你可以使用 docker --help
命令来查看 Docker 的帮助文档,或查阅 Docker 官方文档以获取更多信息。
MySQL 支持多种类型的锁,这些锁用于控制对数据库中的数据和表的访问。以下是 MySQL 中常见的锁类型:
共享锁 (Shared Lock,也称为读锁):
SELECT ... FOR SHARE
或 SELECT ... LOCK IN SHARE MODE
获取共享锁。排他锁 (Exclusive Lock,也称为写锁):
SELECT ... FOR UPDATE
获取排他锁。行级锁 (Row-Level Lock):
表级锁 (Table-Level Lock):
意向锁 (Intention Lock):
自动锁定 (Auto-Locking):
表级共享锁和表级排他锁:
LOCK TABLES
语句可以获得表级共享锁或表级排他锁,以控制对表的访问。UNLOCK TABLES
语句用于释放表级锁。死锁 (Deadlock):
在使用锁时,需要根据具体的业务需求和性能要求选择适当的锁策略。不正确的锁定策略可能会导致性能问题或死锁情况的发生。因此,在设计和实施数据库应用程序时,应特别注意锁的使用。
InnoDB 存储引擎支持四种事务隔离级别,分别是:
读未提交(Read Uncommitted): 这是最低的隔离级别。在这个级别下,一个事务可以读取另一个事务尚未提交的数据。这意味着一个事务可以看到其他事务中的脏读(Dirty Read),不可重复读(Non-Repeatable Read)和幻读(Phantom Read)问题。
读已提交(Read Committed): 这是InnoDB的默认隔离级别。在这个级别下,一个事务只能读取已经提交的数据,避免了脏读问题。但仍然可能会遇到不可重复读和幻读问题。
可重复读(Repeatable Read): 在这个级别下,一个事务在执行期间看到的数据保持一致,不会被其他事务修改。这避免了脏读和不可重复读问题,但仍可能会遇到幻读问题。
串行化(Serializable): 这是最高的隔离级别。在这个级别下,事务会按顺序执行,不会发生并发冲突。这避免了脏读、不可重复读和幻读等所有问题,但性能开销较大,通常不建议在高并发环境中使用。
在选择事务隔离级别时,需要权衡事务的一致性和性能之间的关系。较低的隔离级别通常会提高性能,但可能导致一致性问题。较高的隔离级别可以提供更强的一致性,但可能会降低性能。
可以使用SQL语句来设置事务隔离级别,例如:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
请注意,不同的数据库管理系统可能支持不同的事务隔离级别,因此在使用时需要查阅相应数据库的文档以了解具体支持情况。此外,了解事务隔离级别对于编写具有并发访问的数据库应用程序非常重要,可以帮助避免各种并发问题。
在Java中,序列化是将对象转换为字节流的过程,以便可以将其保存到文件、数据库或通过网络传输。Java提供了一些用于序列化操作的核心类和接口,主要是以下几个:
java.io.Serializable
接口。这个接口是一个标记接口,没有需要实现的方法,它告诉Java运行时系统这个类是可序列化的。例如:import java.io.Serializable;
public class MyClass implements Serializable {
// 类的成员和方法
}
ObjectOutputStream
将对象写入到输出流中,然后使用ObjectInputStream
从输入流中读取对象。例如:// 序列化对象
MyClass obj = new MyClass();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("data.ser"));
oos.writeObject(obj);
oos.close();
// 反序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("data.ser"));
MyClass newObj = (MyClass) ois.readObject();
ois.close();
Serializable
接口不同,Externalizable
接口需要实现writeExternal
和readExternal
方法,以自定义对象的序列化和反序列化过程。这允许您更精确地控制对象的序列化和反序列化。示例:import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class MyClass implements Externalizable {
// 成员变量
private int data;
// 构造函数等其他方法
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 自定义序列化过程
out.writeInt(data);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 自定义反序列化过程
data = in.readInt();
}
}
transient
关键字修饰该字段,这样在序列化过程中,该字段的值将被忽略。例如:public class MyClass implements Serializable {
private transient int nonSerializableField;
// 其他成员和方法
}
serialVersionUID
字段来定义类的版本号,以便在类发生变化时进行版本控制。例如:private static final long serialVersionUID = 123456789L;
这些是Java中常见的序列化操作的关键组件和技术。通过使用这些类和接口,您可以有效地执行对象的序列化和反序列化操作。但是要注意,序列化可能涉及到版本控制、安全性等方面的考虑,具体情况取决于您的应用程序需求。
Drools 是一个开源的规则引擎系统,用于实现业务规则的管理和执行。它允许用户定义、管理和执行规则,以便在应用程序中实现灵活的决策逻辑和业务规则。以下是关于 Drools 的一些关键概念和功能:
规则引擎: Drools 是一个规则引擎,可以将业务规则从应用程序代码中分离出来,使规则可以独立管理和修改,而无需修改应用程序的源代码。这使得业务规则的维护和调整更加灵活和高效。
规则定义语言: Drools 使用自己的规则定义语言(DRL)来表示业务规则。DRL 是一种声明性的语言,允许用户描述条件、动作和规则之间的关系。规则可以包括条件部分(例如,当某些条件满足时)和动作部分(例如,执行某些操作)。
规则集合: 在 Drools 中,规则通常以规则集合的形式组织在一起,称为规则库(rulebase)。规则库中可以包含多个规则,这些规则可以相互关联和触发。
推理引擎: Drools 包括一个强大的推理引擎,用于匹配规则条件并执行相应的动作。当应用程序提供事实(facts)时,规则引擎会根据事实和规则来进行推理和决策。
决策表: Drools 还支持决策表,它是一种用于管理和执行决策规则的可视化方式。决策表允许非技术用户以表格形式定义规则,而无需编写规则代码。
集成能力: Drools 可以轻松集成到 Java 应用程序中,并支持与其他技术和框架(如 Spring)的集成。它还提供了 REST API 和 Web 控制台,用于管理和监视规则库的运行时状态。
开源和社区支持: Drools 是一个开源项目,拥有活跃的社区支持。它可以免费使用,且具有可扩展性和灵活性,可以满足各种业务规则管理的需求。
Drools 的主要用途包括业务规则管理、决策支持、复杂事件处理(CEP)、风险评估、定价和促销策略等领域。它为业务逻辑的管理和执行提供了一种强大的工具,帮助组织更好地应对不断变化的业务需求。
97.什么是spring
Spring是一个开源的、轻量级的Java应用框架,它提供了一系列的解决方案和工具,用于构建企业级应用程序和大规模应用的开发。Spring的核心理念是使Java开发更加简单、高效、可维护,并促进了面向对象编程的最佳实践。
以下是Spring框架的一些主要特性和功能:
依赖注入(Dependency Injection): Spring使用依赖注入来管理应用程序的组件(例如JavaBean)。它通过将依赖关系从代码中解耦,使应用程序更容易进行测试、维护和扩展。
面向切面编程(Aspect-Oriented Programming,AOP): Spring支持AOP,允许开发者定义横切关注点(cross-cutting concerns)并将它们应用到应用程序的不同部分。这有助于实现例如日志记录、事务管理和安全性等方面的功能。
事务管理: Spring提供了强大的事务管理支持,允许开发者使用声明性事务管理和编程式事务管理。它可以与多个不同类型的事务管理器(如JDBC、JTA、Hibernate等)集成。
集成其他技术: Spring框架可以与其他Java技术集成,如Java EE、JDBC、JMS、Hibernate、JPA、REST等,使得开发者可以更容易地构建全栈应用程序。
模块化: Spring框架被组织成多个模块,每个模块都有特定的功能,开发者可以根据需要选择性地使用这些模块,从而保持应用程序的轻量级和模块化。
简化测试: 由于Spring的依赖注入,使单元测试和集成测试更加容易。Spring提供了一系列的测试支持类,可以方便地编写和执行各种测试。
注解支持: Spring支持使用注解来配置和管理组件,使得配置更加简洁,代码更加清晰。
Spring框架的核心是IoC(Inversion of Control)容器,它管理应用程序中的对象生命周期和依赖关系。Spring IoC容器可以通过XML配置文件、Java注解或Java代码进行配置,以实现依赖注入和对象的创建与销毁。
总的来说,Spring框架为Java应用程序提供了一套强大的工具和功能,帮助开发者构建高质量、易于维护的企业级应用程序。Spring的灵活性和可扩展性使其成为广泛应用于企业级应用开发的首选框架之一。
IOC(Inversion Of Controll,控制反转,又叫依赖注入)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
AOP(Aspect-Oriented Programming,面向切面编程)AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于增强和扩展应用程序的功能。
能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。
Jointpoint(连接点):具体的切面点点抽象概念,可以是在字段、方法上,Spring中具体表现形式是PointCut(切入点),仅作用在方法上。
Advice(通知): 在连接点进行的具体操作,如何进行增强处理的,分为前置、后置、异常、最终、环绕五种情况。
目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象。
AOP代理:AOP框架创建的对象,简单的说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。
Weaving(织入):将增强处理添加到目标对象中,创建一个被增强的对象的过程
总结为一句话就是:在目标对象(target object)的某些方法(jointpoint)添加不同种类的操作(通知、增强操处理),最后通过某些方法(weaving、织入操作)实现一个新的代理目标对象。
记录日志,权限控制,事务管理,监控性能
特定 JoinPoint 处的 Aspect 所采取的动作称为 Advice。Spring AOP 使用一个 Advice 作为拦截器,在 JoinPoint “周围”维护一系列的拦截器。
前置通知(Before advice) : 这些类型的 Advice 在 joinpoint 方法之前执行,并使用 @Before 注解标记进行配置。
后置通知(After advice) :这些类型的 Advice 在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用 @After 注解标记进行配置。
返回后通知(After return advice) :这些类型的 Advice 在连接点方法正常执行后执行,并使用@AfterReturning 注解标记进行配置。
环绕通知(Around advice) :这些类型的 Advice 在连接点之前和之后执行,并使用 @Around 注解标记进行配置。
抛出异常后通知(After throwing advice) :仅在 joinpoint 方法通过抛出异常退出并使用 @AfterThrowing 注解标记配置时执行。
实现 AOP 的技术,主要分为两大类:
静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;
编译时编织(特殊编译器实现)
类加载时编织(特殊的类加载器实现)。
动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
JDK 动态代理
JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新,Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
JDK Proxy 是通过拦截器加反射的方式实现的;
JDK Proxy 只能代理实现接口的类;
JDK Proxy 实现和调用起来比较简单;
CGLIB
CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
CGLib 无需通过接口来实现,它是针对类实现代理,主要是对指定的类生成一个子类,它是通过实现子类的方式来完成调用的。
106.谈谈对cgLIB的理解
JDK 动态代理机制只能代理实现接口的类,一般没有实现接口的类不能进行代理。使用 CGLib 实现动态代理,完全不受代理类必须实现接口的限制。
CGLib 的原理是对指定目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。
107.Spring aop和aspectJ aop有什么区别?
Spring AOP是属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
Spring AOP已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。
singleton:唯一bean实例,Spring中的bean默认都是单例的。
prototype:每次请求都会创建一个新的bean实例。
request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
@Autowired
注解)来实现。@PostConstruct
注解或XML配置中的init-method
属性来指定初始化方法。@PreDestroy
注解或XML配置中的destroy-method
属性来指定销毁方法。请注意,Bean的生命周期可以受到Spring配置、注解和XML文件的影响。您可以选择使用合适的方法和注解来管理Bean的生命周期,以满足应用程序的需求。
1.工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。
2.代理设计模式:Spring AOP功能的实现。
3.单例设计模式:Spring中的bean默认都是单例的。
4.模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
5.包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
7.适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
作用对象不同。@Component注解作用于类,而@Bean注解作用于方法。
在Spring框架中,你可以使用 @Component
及其相关注解来将一个类声明为Spring的Bean,以便Spring容器能够管理它。以下是一些常用的注解用于将类声明为Spring Bean:
@Component: @Component
是通用的注解,用于声明一个普通的Spring Bean。你可以将它用于任何类,使该类成为一个受Spring容器管理的Bean。
@Component
public class MyBean {
// Bean的代码逻辑
}
@Service: @Service
注解用于表示一个服务层(Service)的Bean。通常用于标识服务层的类,比如业务逻辑处理类。
@Service
public class MyService {
// 服务层的代码逻辑
}
@Repository: @Repository
注解通常用于表示数据访问层(Repository)的Bean,比如数据访问对象(DAO)。
@Repository
public class MyRepository {
// 数据访问层的代码逻辑
}
@Controller: @Controller
注解用于表示控制层(Controller)的Bean,通常用于Spring MVC中的控制器类。
@Controller
public class MyController {
// 控制器层的代码逻辑
}
@Configuration: @Configuration
注解用于表示一个配置类,通常与 @Bean
注解一起使用,用于定义Spring Bean。
@Configuration
public class MyConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
这些注解都用于将类声明为Spring Bean,并且被Spring容器扫描后,会自动注册到容器中。你可以根据项目的需要选择合适的注解来声明Bean,并在配置类中进行组件扫描或手动注册。注意,使用这些注解需要确保Spring的组件扫描功能已启用,以便Spring能够自动发现和管理这些Bean。
在Spring框架中,有多个注解用于实现自动装配(AutoWiring)依赖关系,使Spring容器能够自动将相应的Bean注入到其他Bean中。以下是一些常用的自动装配注解:
@Autowired: @Autowired
注解是最常用的自动装配注解,它可以用于构造函数、成员变量、方法和参数上。Spring容器会尝试找到匹配类型的Bean并自动注入到被标注的位置。
@Autowired
private MyBean myBean;
@Autowired
public MyService(MyDependency dependency) {
// 构造函数注入
}
@Autowired
public void setMyRepository(MyRepository repository) {
// Setter方法注入
}
@Qualifier: @Qualifier
注解通常与 @Autowired
结合使用,用于指定具体的Bean名称,以解决依赖关系模糊性问题。
@Autowired
@Qualifier("mySpecificBean")
private MyBean myBean;
@Resource: @Resource
注解也用于自动装配,它可以用于字段、setter方法和属性方法上,通常用于按名称进行自动装配。
@Resource(name = "mySpecificBean")
private MyBean myBean;
@Inject: @Inject
注解是JSR-330规范中定义的,与 @Autowired
类似,用于标记需要自动装配的依赖关系。
@Inject
private MyBean myBean;
这些注解允许你以不同的方式实现自动装配,根据项目需求和个人偏好进行选择。通常情况下,@Autowired
是最常用的自动装配注解,而 @Qualifier
和 @Resource
用于解决装配时的歧义性问题。@Inject
则是一种更通用的自动装配注解,如果项目遵循JSR-330规范,可以使用它。
1.编程式事务:在代码中硬编码(不推荐使用)。
2.声明式事务:在配置文件中配置(推荐使用),分为基于XML的声明式事务和基于注解的声明式事务。
在Spring事务管理中,可以设置不同的事务隔离级别(Isolation Level)来控制多个事务之间的相互影响程度。Spring支持五种标准的事务隔离级别,这些级别与SQL标准中的事务隔离级别相对应。以下是这些事务隔离级别:
DEFAULT(默认): 使用底层数据库的默认隔离级别。通常情况下,这等同于数据库的默认级别,例如在MySQL中通常是"REPEATABLE READ"。
READ_UNCOMMITTED(读未提交): 允许事务读取其他未提交事务的数据。这是最低的隔离级别,可能导致脏读、不可重复读和幻读等问题。
READ_COMMITTED(读已提交): 保证一个事务只能读取已经提交的其他事务的数据,避免了脏读问题。但仍然可能出现不可重复读和幻读问题。
REPEATABLE_READ(可重复读): 保证事务执行期间读取的数据保持一致,不受其他事务的影响。这避免了脏读和不可重复读问题,但仍可能出现幻读问题。
SERIALIZABLE(串行化): 最高的隔离级别,保证事务串行执行,不会发生并发冲突。这避免了脏读、不可重复读和幻读等所有问题,但性能开销较大,通常不建议在高并发环境中使用。
在Spring中,可以通过在事务注解(如@Transactional
)中设置isolation
属性来指定事务隔离级别。例如:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void myTransactionalMethod() {
// 事务执行逻辑
}
需要根据具体的业务需求和数据一致性要求来选择合适的事务隔离级别。不同的隔离级别在性能和数据一致性之间存在权衡关系,因此需要谨慎选择,并进行充分的测试和评估。
116.可以通过多少种方式完成依赖注入
构造函数注入
Setter注入
接口注入
。
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
用来简化Spring应用的初始搭建以及开发过程,使用特定的方式来进行配置
创建独立的Spring引用程序main方法运行
嵌入的tomcat无需部署war文件
简化maven配置
自动配置Spring添加对应的功能starter自动化配置
SpringBoot来简化Spring应用开发,约定大于配置,去繁化简
119.Springboot的原理
独立运行
Spring Boot 而且内嵌了各种 servlet 容器,Tomcat、Jetty 等,现在不再需要打成war 包部署到容器中,Spring Boot 只要打成一个可执行的 jar 包就能独立运行,所有的依赖包都在一个 jar 包内。
简化配置
spring-boot-starter-web 启动器自动依赖其他组件,简少了 maven 的配置。
自动配置
Spring Boot 能根据当前类路径下的类、jar 包来自动配置 bean,如添加一个 spring
boot-starter-web 启动器就能拥有 web 的功能,无需其他配置。
无代码生成和XML配置
Spring Boot 配置过程中无代码生成,也无需 XML 配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是 Spring4.x 的核心功能之一。
应用监控
Spring Boot 提供一系列端点可以监控服务及应用,做健康检测。
121.请解释一下工厂模式
懒汉模式:在第一次调用的时候实例
饿汉模式:饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。
125.消息队列
126.单元测试
@SpringbootTest
@Test
127.zpl语言
ZPL(Zebra Programming Language)是一种专门用于打印标签和条形码的标签打印机编程语言。ZPL是由斑马技术(Zebra Technologies)开发的,并且广泛用于各种标签打印机和条形码打印机上。
ZPL语言的主要目的是控制标签打印机的行为,包括文本、条形码、图像、位置和格式等方面。通过编写ZPL命令,用户可以自定义标签的外观和内容,并将这些命令发送给标签打印机,以在标签上生成所需的标识信息。
以下是一些常见的ZPL语言元素和命令:
^XA // 标签开始
^XZ // 标签结束