目录
5. 程序计数器(Program Counter Register)
11.spring运用了什么设计模式?讲一下哪些部分运用到了这些设计模式
12.说说你都知道哪些设计模式?最常用的有哪些?总共有几种设计模式?
13.redis可以储存哪几种数据类型?你的项目里都用redis存储哪些数据
20.redis是如何部署的?是单个部署还是集群部署?为什么这么做?
23.arrarylist,linkedlist;arraylist内部扩容机制是怎样的?
27.String、StringgBuilder、StringBuffer的区别
28.重写equals已经能比较两个对象了,为什么还要重写hashcode方法
10. UnsupportedOperationException
35.HTTP报文有哪几部分组成?Get/Post有什么区别
36.转发(forward)和重定向(redirect)的区别
40.SpringIOC是怎么实现了依赖注入,有几种依赖注入方式?
44.SpringBoot在初始化以前执行一个方法,应该怎么做?初始化以后在执行一个方法应该怎么做,有哪几种方法去做
方法二:使用Spring Boot提供的CommandLineRunner接口或ApplicationRunner接口
方法三:使用Spring Boot提供的ApplicationListener接口
46.@Autowired注入和自己new一个对象有什么区别?
51.@Component,@Controller,@Repository,@Service有何区别?
52.SpringBoot中的注解?SpringBoot的核心注解是什么?
55.SpringBoot项目启动类上有什么注解?除了常用的还有什么注解?
二、在Spring Boot中,配置文件的加载顺序遵循以下步骤
62.Spring运用了什么设计模式?讲一下哪些部分用到了这些设计模式
72.数据库的优化方案?一个SQL语句如何让他搜索变快?你们遇到的数据量最大的表是什么样,有多少条数据。
73.慢日志怎么使用?MYSQL怎么发现动作速度慢的SQL语句?
74.分库分表是怎么做的?数据库分表分库后怎么保证其一致性?
76.oracle与mysql的区别?mysql分页和oracle分页的区别?
82.mysq1锁的粒度?并发编程锁到行还是表?(介绍页级、表级、行级)
87.Sleep()和wait()区别?Start()和run()区别?wait,sleep, notify的区别?
91.synchronized和]ock的区别。Synchronize关键字修饰方法和修饰代码块的区别
97.ReentrantLock和synchronized的别是什么
1.JVM内存结构
1. 方法区(Method Area)
方法区是JVM内存结构的一部分,用于存放类的相关信息,包括:
- 类的结构(字段、方法、常量池等)。
- 字段和方法的描述,如名称、类型、访问修饰符等。
- 静态变量。
- Java虚拟机在运行时加载的类的信息。
方法区通常在堆区的外部,与堆区是分开的。因为主要存放的是类的信息和静态数据,所以这一部分数据的生命周期与类本身相同。
2. 堆(Heap)
堆是JVM中用于动态分配内存的区域,是对象和数组的存储区。堆内存是JVM运行过程中最大的内存区域,所有对象实例和数组都是在这个区域中分配的。堆区又可以分为几个部分:
- 新生代(Young Generation):用于存放新创建的对象,年轻代又可以分为三个部分:Eden空间和两个Survivor空间。新创建的对象首先在Eden区分配,经过垃圾回收后存活下来的对象会被移动到Survivor区。
- 老年代(Old Generation):用于存放长期存活的对象。经过多次垃圾回收,仍然存活的对象会被转移到老年代。
- 持久代(PermGen)/元空间(Metaspace):在旧版本的JVM中,方法区使用持久代来存储类元信息。在Java 8及其之后的版本中,持久代被元空间替代,主要用于存储类的元数据,不再属于Java虚拟机的堆内存,而是使用本地内存。
3. 虚拟机栈(VM Stack)
虚拟机栈用于存储局部变量、操作数栈、动态链接等信息。每个线程都有自己的虚拟机栈。栈是先进后出(LIFO)的结构,栈中的每一个栈帧(Stack Frame)对应一个方法的调用。在方法调用时,相关的局部变量、参数值以及方法返回地址等都会被推入栈中,而在方法返回时,栈帧会被弹出。
4. 本地方法栈(Native Method Stack)
本地方法栈用于支持JVM调用本地(Native)方法,类似于虚拟机栈。它存储的是本地方法的栈帧。不同的JVM实现可能会有所不同,但其功能主要是协助执行 Java 与其他语言(如C、C++)交互的本地方法。
5. 程序计数器(Program Counter Register)
程序计数器是一个较小的内存区域,保存着当前线程所执行的字节码的地址。它是线程私有的,每个线程都有一个程序计数器,以便在多线程环境中跟踪每个线程的执行点。可以理解为一个指针,指向当前正在执行的指令。
2.垃圾回收机制(GC)
垃圾回收算法
垃圾回收算法有多种,常见的有:
-
标记-清除(Mark-and-Sweep):
- 该算法分为两个阶段。第一阶段“标记”会遍历所有可达对象并标记它们,第二阶段“清除”会删除没有被标记的对象。尽管这个方法简单有效,但它可能导致内存碎片。
-
复制(Copying):
- 该算法将内存分为两个空间(通常称为“From”和“To”区域),在进行垃圾回收时复制所有活跃对象到另一个区域。复制后,原来的区域会被清空。该方法避免了内存碎片,但需要更多内存。
-
标记-整理(Mark-and-Compact):
- 该算法与标记-清除结合。首先,它会标记所有可达对象,然后将它们移动到一端,以消除内存碎片,并清理未使用的内存。
-
分代收集(Generational Collection):
- 根据对象的生命周期将内存分为若干代(通常为年轻代和老年代)。新创建的对象通常会在年轻代中变化较快,因此通过频繁回收年轻代来提高效率。而老年代中的对象较少变化,因此不需要频繁检查和回收。
JVM的垃圾回收机制:GC,是Java提供的对于内存自动回收的机制。
GC(Garbage Collection)是Java虚拟机(JVM)中的一项重要功能,用于自动管理堆内存中不再使用的对象,释放其占用的内存空间。GC通过标记和回收无效对象来实现内存的回收和释放,以避免内存泄漏和溢出。
下面是GC的主要工作原理和过程:
1、对象的标记:GC首先标记出所有活动对象,即仍然被引用或可达的对象。它从一组根对象开始,逐步遍历对象图,将可达的对象标记为活动对象,未标记的对象则被认为是无效的。
2、垃圾回收:在标记完成后,GC会对未标记的对象进行回收。具体的回收算法可以是不同的,常见的算法包括标记-复制算法、三色标记算法等。
3、内存压缩和整理:某些垃圾回收算法在回收完对象后,可能会产生内存碎片。为了优化内存使用,GC可能会进行内存压缩和整理操作,使得分配的对象在内存中连续存放,减少内存碎片的影响。
GC的优点是可以自动管理内存,减少了手动内存管理的复杂性,避免了内存泄漏和溢出的问题。但是,GC也会带来一定的性能开销。因此,在开发Java应用程序时,需要合理配置GC的参数和调整垃圾回收策略,以平衡性能和内存的使用。
3.JVM调优的方法?
1. 内存管理调优
-
堆内存大小设置:可以通过 (初始堆大小)和 (最大堆大小)参数来设置堆内存的大小。例如:。根据应用程序的需求来调整堆的大小
-
选择合适的垃圾回收器:不同的垃圾回收器适用于不同的场景,例如:
- G1垃圾回收器():适合大堆内存和低延迟的应用。
- Parallel垃圾回收器():适合高吞吐量的应用。
- CMS垃圾回收器():适合需要最小停顿的应用。
- ZGC和Shenandoah:适合需要极低延迟的应用,但需要JVM版本支持。
-
调整新生代和老年代的比例:可以通过 或 参数来调整新生代与老年代的比例,以优化对象的存活率和垃圾回收频率。
2. 垃圾回收调优
-
影响GC频率和停顿时间的参数:
MaxGCPauseMillis
:设置最大GC停顿时间。GC会尝试满足这个时间限制。GCTimeRatio
:调整应用可用时间与GC时间的比例。
-
监控和分析:使用JVM提供的工具(如JVisualVM、JConsole、GC日志)来监控和分析垃圾回收的情况,识别GC问题。
3. JIT编译优化
-
开启Tiered Compilation:使用,可以提高启动性能。
-
调整JIT编译行为:通过设置编译阈值,以控制何时将方法编译为机器代码。
-
4. 线程调优
-
线程栈大小:可以用参数设置每个线程的栈大小。例如:。适当调整以减少栈溢出和内存使用。
-
线程池管理:如果应用使用线程池,确保合理配置线程数,避免因过多线程上下文切换引起的性能问题。
5. 其他优化选项
-
类加载与内存优化:
- 使用 可保持对象引用的压缩,有助于减少堆内存的使用。
-
禁用或调整assertions:在生产环境中,可以禁用不必要的断言以提高性能:。
-da
6. 性能监控与分析
-
使用JVM监控工具:如Java Mission Control(JMC)和Flight Recorder来分析应用性能,查找瓶颈。
-
Profiling:使用工具(如YourKit、VisualVM等)进行代码剖析,找到性能开销较大的热点代码。
7. 配置文件与启动参数
- 合理配置启动参数:根据具体的应用场景和服务器配置调整JVM的启动参数,比如开启JIT、选择合适的GC、调整堆内存大小等。
4.堆和栈的区别?
1. 结构和用途:
-
栈(Stack):
- 栈是一种先进后出(LIFO, Last In First Out)的数据结构。
- 用于存储局部变量、函数参数和函数调用信息等。在函数调用时,函数的局部变量会压入栈中,函数返回时会将这些变量从栈中弹出。
- 每个线程都有自己的栈,用于跟踪函数调用和局部变量。
-
堆(Heap):
- 堆是一种基于动态存储分配的内存区域,没有特定的结构(可以认为是自由存储区)。
- 用于存储动态分配的对象和数据,如使用 关键字在C++中分配的对象,或在Java中创建的对象。
- 堆内存可以由程序员直接管理,程序员需要手动申请和释放内存,或者由垃圾回收机制(如Java、C#)自动管理。
2. 存储管理:
-
栈:
- 存储在栈内存中的数据管理简单,入栈和出栈操作的时间复杂度都是O(1)。
- 由于栈的大小是固定的(通常由操作系统限定),栈溢出(Stack Overflow)可能导致程序崩溃。
-
堆:
- 堆内存的大小通常只受到系统内存的限制,算法的复杂性和速度依赖于内存分配和回收的实现。
- 堆内存分配需要更多的时间,且容易出现内存泄漏或内存碎片问题。
3. 生命周期:
-
栈:
- 数据的生命周期由函数调用决定。函数结束后,栈上的数据会自动释放。
-
堆:
- 数据的生命周期由程序员控制,只有在不再需要对象时,才能显式地释放空间。如果没有释放,则会造成内存泄漏。
4. 访问速度:
-
栈:
- 由于其结构简单,内存访问速度较快,通常会比堆更快。
-
堆:
- 由于堆内存的管理要复杂得多,非顺序访问可能导致较慢的访问速度。
5.JVM使用命令
jinfo
描述:输出给定 java 进程所有的配置信息。包括 java 系统属性和 jvm 命令行标记等。
jstat
jstat -gcutil <pid> <interval>
查看java进程的gc情况
以百分比显示每个区域的内存使用情况;
参数interval表示每多少毫秒刷新一次
6.class.forName和Classload的区别
1.相同点
两者都可以对类进行加载。
对于任意一个类/对象,我们都能够通过反射能够调用这个类的所有属性方法。
2.不同点
抽象类ClassLoader中实现的方法loadClass,loadClass只是加载,不会解析更不会初始化所反射的类。常用于做懒加载,提高加载速度,使用的时候再通过.newInstance()真正去初始化类。
7.谈谈JDK1.8特性有哪些
Lambda表达式 类似于ES6中的箭头函数
接口的默认方法和静态方法
新增方法引用格式
新增Stream类
新的日期API,Datetime,更方便对日期的操作
引入Optional,在SpringData中使用较多,然后再通过get获取值,主要用于防止NPE。
支持Base64
注解相关的改变
支持并行(parallel)数组
对并发类(Concurrency)的扩展。
JavaFX。
8.反射获取类中的所有方法和获取类中的所有属性
获取所有方法 :使用 getDeclaredMethods() 方法可获取类中所有方法,包括公共、私有和受保护的方法。通过遍历 Method 数组,可进一步获取每个方法的修饰符和方法名等信息。若只需获取公共方法,可使用 getMethods() 方法。获取所有属性 :运用 getDeclaredFields() 方法能够获取类中的所有属性,包含公共、私有和受保护的属性。同样,遍历 Field 数组可得到每个属性的修饰符和属性名等。若仅需获取公共属性,则可使用 getFields() 方法
9.懒汉和饿汉模式的区别?口述两种模式。
在饿汉式单例模式中,“饿” 体现的是一种急切的状态。就好像一个很饿的人,在看到食物(这里类比于单例对象)的时候,会迫不及待地先把食物拿到手(创建单例对象)。在这个模式下,单例对象在类加载阶段就被创建出来,而不是等到真正需要使用这个对象的时候才去创建。这种方式比较急切,所以被称为 “饿汉模式”。
在懒汉模式下,实例在第一次使用时才进行创建,因此称为“懒汉”,在需要被用的时候被创建,突出一个字“懒”
10.如何保证单例模式在多线程中的线程安全
私有构造函数:通过将构造函数设置为私有,我们禁止了外部类直接实例化Singleton类。这确保了只能通过getInstance()方法来获取实例。
volatile关键字:volatile关键字确保了instance变量的可见性。当一个线程修改了一个volatile变量的值时,其他线程能够立即看到这个变化。这样可以避免线程之间的缓存不一致问题。
双重检查锁定(Double-Checked Locking):为了减少同步开销,我们在第一次检查instance是否为空后,才进入同步块。这是因为大多数情况下,实例已经被创建,不需要进入同步块。只有在第一次创建实例时才会需要同步。
同步块:在同步块内部,我们再次检查instance是否为空,以确保只有一个线程能够创建实例。这是为了防止多个线程同时通过了第一个空检查并尝试创建多个实例。
11.spring运用了什么设计模式?讲一下哪些部分运用到了这些设计模式
- 一、简单工厂模式(Simple Factory)
-
定义:
简单工厂模式:并不属于 GoF(四人组)总结的 23 种设计模式,但它却在实际开发中被频繁使用。其核心是 由一个工厂类根据传入的参数,动态决定创建哪一个产品类的实例。
举例:
Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象。但是,在传入参数后创建 Bean 还是传入参数前创建 Bean,这个要根据具体情况而定。
- 二、工厂方法模式(Factory Method)
-
定义:
工厂方法模式:定义了一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。
举例:
Spring 使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 Bean 对象。两者对比如下:
BeanFactory:延迟注入(使用到某个 Bean 的时候才会注入),相比于 ApplicationContext 来说会占用更少的内存,程序启动速度更快。
ApplicationContext:容器启动的时候,不管你用没用到,一次性创建所有 Bean。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory,除了有 BeanFactory 的功能还有额外更多功能,所以一般开发人员使用 AplicationContext 更多。 - 三、单例模式(Singleton)
-
定义:
单例模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
举例:
在我们系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如;程序的行为异常、资源使用过量、或者不一致性的结果。
- 四、适配器模式(Adapter)
-
定义:
适配器模式:将一个接口转换成客户希望的另一个接口,适配器使接口不兼容的那些类可以一起工作。
举例:
Spring AOP 中的适配器模式:
我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是 AdvicorAdapter。
- 五、代理模式(Proxy)
-
定义:
代理模式:为其他对象提供一个代理以控制对这个对象的访问。
举例:
代理模式在 AOP 中的应用:
AOP(Aspect-Oriented Programming,面向切面编程):能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如:事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性
- 七、观察者模式(Observer)
-
定义:
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
举例:
Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题
- 八、策略模式(Strategy)
-
定义:
策略模式:定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法独立于使用它的客户。
举例:
Spring 框架的资源访问 Resource 接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
- 九、模板方法模式(Template Method)
-
定义:
模板方法模式:在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
举例:
Spring 中 JdbcTemplate、HibernateTemplate 等以 Template 结尾的对接入内容进行操作的类,它们就使用到了模板方法。一般情况下,我们都是使用继承的方法来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模板与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
12.说说你都知道哪些设计模式?最常用的有哪些?总共有几种设计模式?
13.redis可以储存哪几种数据类型?你的项目里都用redis存储哪些数据
-
字符串类型(String):这是Redis中最基础的数据类型,可以存储任何形式的字符串,包括文本、序列化后的对象、二进制数据等。字符串类型的Value最多可以容纳512MB的数据。
-
哈希类型(Hash):哈希类型可以看作是一个键值对的集合,适合存储对象的信息。例如,一个用户的信息可以存储为哈希类型,包含姓名、密码、年龄等字段。
-
列表类型(List):列表类型是一个简单的字符串列表,可以存储一系列的字符串元素。它支持按照插入顺序排序的字符串列表,常用于实现队列和栈等数据结构。
-
集合类型(Set):集合类型是一个无序的字符串集合,支持添加、删除和判断元素是否存在等操作。集合中的元素是唯一的,常用于实现交集、并集和差集等操作。
-
有序集合类型(Zset):有序集合类型是一个集合,其中的每个成员都关联一个双精度浮点数分数,这使得它既可以像集合一样存储不重复的元素,又可以像排序的列表一样返回有序的数据。常用于排行榜等场景
- 字符串类型:常用于缓存数据库查询结果、实现计数器功能(如网站访问量计数)等。
- 哈希类型:适合存储对象信息,如用户信息、商品信息等。
- 列表类型:常用于实现消息队列、任务队列等功能。
- 集合类型:适用于需要去重的数据存储,如好友关系、标签等。
- 有序集合类型:适用于需要排序的数据存储,如排行榜、评分系统等
14.redis有哪些常用操作?(命令)
数据类型相关命令
字符串(String):
SET key value:设置指定键的值。
GET key:获取指定键的值。
INCR key:将指定键的值加1。
哈希表(Hash):
HSET key field value:设置哈希表中指定字段的值。
HGET key field:获取哈希表中指定字段的值。
HGETALL key:获取哈希表中所有字段和值。
列表(List):
LPUSH key value:将一个值插入到列表头部。
RPUSH key value:将一个值插入到列表尾部。
LPOP key:移除并返回列表的第一个元素。
集合(Set):
SADD key member:向集合中添加一个或多个成员。
SMEMBERS key:获取集合中的所有成员。
SINTER key1 key2:返回两个集合的交集。
有序集合(Sorted Set):
ZADD key score member:将一个成员的分数加到有序集合中。
ZRANGE key start stop:通过索引区间返回有序集合指定区间内的成员。
服务器管理命令
信息命令:
INFO:获取关于 Redis 服务器的各种信息和统计数值。
PING:检测服务器是否可用。
持久化:
SAVE:同步保存数据到硬盘。
BGSAVE:异步保存数据到硬盘。
复制:
SLAVEOF host port:将当前服务器设置为指定服务器的从服务器。
事务相关命令
事务:
MULTI:标记一个事务块的开始。
EXEC:执行所有事务块命令。
DISCARD:取消事务,放弃执行事务块内的所有命令。
其他常用命令
键操作:
DEL key:删除一个键。
EXISTS key:检查键是否存在。
KEYS pattern:查找所有符合给定模式的键。
过期时间:
EXPIRE key seconds:为键设置过期时间。
TTL key:获取键的剩余过期时间。
发布与订阅:
PUBLISH channel message:将消息发送到指定频道。
SUBSCRIBE channel:订阅一个或多个频道。
这只是 Redis 命令的一小部分,实际应用中可能会根据具体场景使用更多的命令。不同版本的 Redis 可能会有新增或废弃的命令,建议查阅官方文档获取详细信息。
15.redis缓存和数据库怎么保持一致?
1. 读写策略调整
先更新数据库,再更新缓存
原理:这种策略在更新数据时,先对数据库执行更新操作,然后立即更新缓存中的数据。这样可以尽量保证缓存和数据库的数据同步。
缺点:存在一定的风险。如果更新缓存失败,就会导致数据库和缓存数据不一致。而且,在高并发场景下,可能会出现多个线程同时更新缓存,导致缓存数据的更新顺序混乱,进而引发数据不一致的情况。
先删除缓存,再更新数据库
原理:当需要更新数据时,先将缓存中的对应数据删除。这样,下一次读取数据时,由于缓存中没有数据,就会从数据库中读取最新的数据,并将其放入缓存。
缺点:在高并发场景下可能会出现问题。例如,一个线程 A 删除了缓存,但是还没来得及更新数据库,此时另一个线程 B 来读取数据,发现缓存为空,就会从数据库读取旧数据并放入缓存。然后线程 A 再更新数据库,这样就导致了缓存和数据库的数据不一致。
2. 使用缓存更新队列
原理:将缓存更新操作放入一个队列中,由专门的消费者线程来处理这些更新操作。当有数据需要更新时,先将更新消息发送到队列中,这样可以避免多个线程同时更新缓存导致的混乱。
优点:可以实现异步更新缓存,减少对数据库更新操作的直接依赖,提高系统的并发性能。并且能够保证缓存更新的顺序,降低数据不一致的风险。
缺点:增加了系统的复杂性,需要维护一个消息队列和相应的消费者线程。而且如果队列处理不及时,可能会导致缓存更新延迟。
3. 设置合理的缓存过期时间
原理:为缓存中的数据设置过期时间,当缓存过期后,下一次读取数据时就会从数据库中重新获取最新的数据,从而更新缓存。通过合理设置过期时间,可以在一定程度上减少数据不一致的情况。
举例:对于一些数据更新频率较低的业务场景,如电商系统中的商品分类信息,可以设置较长的缓存过期时间,比如一天。而对于更新频率较高的数据,如商品的库存信息,可以设置较短的缓存过期时间,如几分钟。
缺点:如果过期时间设置过长,可能会导致数据不一致的时间过长;如果过期时间设置过短,会频繁地从数据库读取数据,增加数据库的压力。
4. 采用数据库的事务机制与缓存操作结合
原理:在更新数据库的操作中加入事务机制,确保数据库更新成功后再进行缓存操作。如果缓存操作失败,可以通过事务回滚来保证数据的一致性。
示例:在关系型数据库(如 MySQL)中,可以使用存储过程来包裹数据库更新和缓存更新操作。在存储过程中,开启一个事务,先更新数据库表,然后更新缓存。如果缓存更新失败,就回滚事务,从而保证数据库和缓存数据的一致性。
缺点:这种方法会增加数据库的负担,并且需要对业务逻辑进行比较复杂的改造,以适应事务和缓存操作的结合。
5. 读取时对比缓存和数据库数据
原理:在读取数据时,同时获取缓存中的数据和数据库中的数据,然后进行对比。如果发现数据不一致,就以数据库数据为准更新缓存。
缺点:这种方法会增加读取数据的时间成本,因为每次读取都需要从数据库获取数据进行对比。而且在高并发情况下,频繁地对比和更新缓存可能会导致性能下降。
16.redis可以持久化么?如何持久化?
Redis的三种持久化方法详解_redis appendfsync-CSDN博客
17.redis分布式锁怎么使用?
Redis实现分布式锁的7种方案,及正确使用姿势!_redis锁使用-CSDN博客
18.redis缓存数据丢失怎么使用?
Redis如何解决数据丢失问题_redis 数据丢失-CSDN博客
19.redis如果崩溃了如何快速恢复?
Redis宕机了,如何恢复数据_redis宕机数据如何恢复-CSDN博客
20.redis是如何部署的?是单个部署还是集群部署?为什么这么做?
单机模式
新手入门模式。单机模式意味着 Redis 是单点的,部署在一台服务器,挂了就挂了,用在本地测试还可以,但是生产环境就算了。
优势
- 部署简单
- 省钱,一台服务器就可以了
- 不涉及主从复制等,数据强一致
劣势
- 单点意味着稳定性基本上为 0,挂了就挂了
- 吞吐量受限于单机资源
主从模式
当流量越来越大,单台机器资源不能无限增长,就需要水平扩展到多个节点,使用多个节点分散承接读流量。
主从模式为主节点承接写流量,从节点承接读流量,二者数据一致通过主节点异步复制(全量复制 / 增量复制)到从节点。
优势
- 读流量被分摊到多个节点上,读流量支持力度变大
- 当主节点宕机/不可用时,可以手动切换主节点继续提供服务
劣势
- 当主节点宕机/不可用时手动切换节点,切换过程中 redis (主节点)不可用,并且会丢失主节点 / 从节点之间未同步的数据
- 稳定性还是不够,依赖手动切换。不适用于生产。
- 写流量还是让主节点独自承受,写流量还是靠单机资源支撑
哨兵模式
哨兵模式主要解决主从模式中手动切换的部分,本质上哨兵代替了人,通过 gossip 协议监控主节点的健康情况。
优势
- 不用手动切换主节点了,切换过程中虽然 redis 也是不可用的,但是这个时间会极大的降低
劣势
- sentinel 与主节点多了一层心跳检测,有可能 sentinel 与主节点的网络抖动导致重新选举主节点。
- redis 主从节点吞吐因心跳检测可能稍微降低。
集群模式
集群模式主要解决了两个问题:写流量水平扩展 & 哨兵与主节点的网络抖动。
集群模式主要的架构为:主节点平分 16384 个槽,集群支持主节点的动态上线/下线(需要 rehash),主节点与从节点通过心跳关联,主节点失联后从节点有权发起选举成为主节点(raft 算法)。
优势
- 自管理集群内主从节点上下线,减少因外部集群网络抖动之类的发起的无效选举
- 数据按照 slot 存放在多个节点,客户端通过服务端主节点的重定向跳转到具体的槽,可动态调整数据分布
- 减少了集群整体不可用的概率,某一主节点宕机只影响一部分数据的访问
- 写流量 & 数据平分到多个节点,集群的写请求瓶颈得到缓解
劣势
- 集群间状态同步使用 gossip 协议,节点数较多存在较多的心跳网络流量
- 主节点的上线/下线需要进行 rehash ,当节点内数据较多耗时较长
redis 节点间复制有两种:全量复制 & 部分复制
全量复制
出现场景
- 从节点刚上线需要同步主节点的数据
- 从节点切换脑裂后从节点偏移量与主节点不一致的时间点
- 从节点偏移量不在主节点的复制缓冲区中
过程
- 从节点向主节点发起同步数据的请求
- 主节点通过 bgsave 形成当前数据的快照,发给从节点
- 从节点删除历史数据,加载主节点发过来 RDB 文件
- 从节点拉取主节点缓冲区数据,加载到自身的内存中,并更新当前的偏移量
部分复制
出现场景
- 全量复制出现场景之外的场景
- 主从日常复制
过程
- 主节点将命令同步到缓冲区(AOF)
- 从节点拉取缓冲区数据,更新到自身的节点中,并更新当前的偏移量
21.什么是缓存穿透、缓存击穿、缓存雪崩?如何解决?
一、缓存穿透
1、缓存穿透理解 缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。
缓存穿透示意图:
缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。 造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题(例如:set 和 get 的key不一致),第二,一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)
2、解决方案
2.1 缓存空对象
缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
缓存空对象会有两个问题:
第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
2.2 布隆过滤器拦截
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
布隆过滤器拦截的算法描述:
初始状态时,BloomFilter是一个长度为m的位数组,每一位都置为0。
添加元素x时,x使用k个hash函数得到k个hash值,对m取余,对应的bit位设置为1。
判断y是否属于这个集合,对y使用k个哈希函数得到k个哈希值,对m取余,所有对应的位置都是1,则认为y属于该集合(哈希冲突,可能存在误判),否则就认为y不属于该集合。可以通过增加哈希函数和增加二进制位数组的长度来降低错报率。
3、两种方案的对比
二:缓存击穿
1、缓存击穿的理解
系统中存在以下两个问题时需要引起注意:
当前key是一个热点key(例如一个秒杀活动),并发量非常大。 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。 在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
2、解决方案
2.1 分布式互斥锁 只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
2. 永不过期 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓
3、两种方案对比:
分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
永远不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
三:缓存雪崩 1、概念理解
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
2、解决方案
2.1 缓存层高可用: 可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。 2.2 做二级缓存,或者双缓存策略: 采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底 2.3 数据预热: 可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀 2.4 加锁排队. 限流– 限流算法. 1.计数 2.滑动窗口 3. 令牌桶Token Bucket 4.漏桶 leaky bucket [1] 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
22.List和set和map的区别?
1. 有序性
List:有序,维护元素的插入顺序,可以通过索引访问。
Set:无序(除 LinkedHashSet 保持插入顺序外),不允许重复元素。
Map:键无序(LinkedHashMap 保持插入顺序,TreeMap 按键排序),值无序。
2. 重复性
List:允许存储重复的元素。
Set:不允许存储重复的元素。
Map:键不允许重复,值可以重复。
3. 访问方式
List:按索引访问,通过 get(index) 方法。
Set:只能通过迭代器遍历,不能按索引访问。
Map:通过键访问相应的值,使用 get(key) 方法。
4. 存储结构
List:单一元素的集合,每个元素都有其位置(索引)。
Set:单一元素的集合,但保证元素的唯一性。
Map:键值对的集合,通过键访问对应的值。
五、常见使用场景举例
List:适用于需要按顺序存储和访问元素的场景,如学生名单、任务队列、订单列表等。
Set:适用于需要存储唯一元素的场景,如用户名列表、唯一标识符集合、无重复的集合运算等。
Map:适用于需要通过键快速查找对应值的场景,如字典、配置参数、映射表等。
23.arrarylist,linkedlist;arraylist内部扩容机制是怎样的?
ArrayList和LinkedList_linkedlist扩容机制-CSDN博客
24.hashmap扩容机制,HashMap的底层原理
(转载) HashMap底层原理 + 扩容机制_hashmap的扩容机制-CSDN博客
25.hashmap的底层用什么储存的?线程是否安全?
HashMap的底层数据结构是数组+链表+红黑树。HashMap使用哈希表来实现键值对的存储,当发生哈希冲突时,会使用链表来存储冲突的键值对。当链表的长度大于8时,会将链表转换为红黑树,以提高查询效率。
HashMap的线程安全性
HashMap本身是线程不安全的。当多个线程同时对HashMap进行操作时,可能会导致数据丢失或覆盖。例如,当一个线程正在进行插入操作时,另一个线程也可能进行插入操作,由于线程调度的原因,可能会导致第二个线程的操作覆盖第一个线程的操作,从而造成数据不一致。
26.final和finally和finalize的区别
27.String、StringgBuilder、StringBuffer的区别
了解String、StringBuilder与StringBuffer之间的区别(入门)-CSDN博客
28.重写equals已经能比较两个对象了,为什么还要重写hashcode方法
1、为了保证一个原则,equals相同的两个对象hashcode必须相同。如果重写了equals而没有重写hashcode,会出现equals相同hashcode不相同这个现象。
2、在散列集合中,是使用hashcode来计算key应存储在hash表的索引,如果重写了equals而没有重写hashcode,会出现两个完全相同的两个对象,hashcode不同,计算出的索引不同,那么这些集合就乱套了。
3、提高效率,当我们比较两个对象是否相同的时候,先比较hashcode是否相同,如果hashcode不相同肯定不是一个对象,如果hashcode不同再调用equals来进行比较,减少比较次数提高效率
【Java面试】为什么重写equals()方法,就一定要重写hashCode()方法?_哔哩哔哩_bilibili
29.基本数据和包装数据类型的区别?
1. 两者之间的区别
1.基本类型在初始化时有默认值,包装类在初始化时可以为空 (如:int a; Interge b; 都不进行赋值操作,a的值为0,b的值为 null )
2.基本类型存储在栈里,包装类型存储在堆里。因为栈的效率更高。
3.包装类是对象,拥有方法和字段,对象的调用是引用对象的地址。
4.基本类型是值传递,包装类是引用传递(即地址值的传递)。
5.向ArrayList,LinkedList中放数据的时候,只能放Object类型和包装类,基本类型放不进去。
6.包装类是不能被继承的,因为被final修饰
2.关于 == 比较
对于基本数据类型,==比较的是值,而对于包装类型,==比较的则是2个对象的内存地址。
2.1.当基本类型和包装类型进行比较时,包装类会进行拆箱并转换成基本类型,这时就是2个基本类型进行比较,结果为true
Integer a=150;
int b=150;
System.out.println(a==b); // true
2.2.当使用2个包装类型进行比较时,会出现4种情况
---第一种:当使用new Integer()新建变量时,2个包装类进行比较,结果为false
因为每次 new Integer()产生的变量都会在堆中新建,不管2个值是否相等都会新建,所以2个对象的地址值并不相同
Integer c=10;
Integer d=new Integer(10);
System.out.println(c==d); //false
Integer e =new Integer(150);
Integer f =new Integer(150);
System.out.println(e==f); //false
---第2种:当使用 Integer.valueOf() 时,会出4种情况,导致比较结果并不相同
因为当变量值在-128~127之间时,生成的Integer变量指向的是java常量池中的对象,而超出范围的对象会进行新建
(不同赋值方式 在-128~ 127 之间)
Integer a=100;
Integer b=Integer.valueOf(100);
System.out.println(a==b); //true
(相同赋值方式 在-128~ 127 之间)
Integer a=Integer.valueOf(110);
Integer b=Integer.valueOf(110);
System.out.println(a==b); // true
(不同赋值方式 不在范围之间)
Integer a= Integer.valueOf(150);
Integer b=150;
System.out.println(a==b); //false
(相同赋值方式 不在范围之间)
Integer a=Integer.valueOf(150);
Integer b=Integer.valueOf(150);
System.out.println(a==b); // false
---第3种:直接赋值操作时,超出 -128~127 时,也会出现不同的结果
(直接赋值 在-128~127之间)
Integer a=120;
Integer b=120;
System.out.println(a==b); // true
(直接赋值 不在-128~127之间)
Integer a=150;
Integer b=150;
System.out.println(a==b); // false
---第4种:除了使用 new Integer()新建变量,其他赋值方式只要在 -128~127之间,都会使用常量池中的变量,值比较结果为true, 超出范围 都会在堆中新建变量,结果为false
3.关于equals
2个基本类型无法比较,无equals方法
两个包装类比较时,先比较的是类型,如果类型相同,在比较具体的值
包装类和基本类型比较时,会将基本类型装箱,在比较类型,在比较具体的值
4.类型转换
类型转换有一个关键点:向上/向下转型
包装类 :无法向下转型,只能向上
基本类型:可以向上转型,向下转型必须使用强制类型转换,会有异常风险
30.什么是多态?举个例子
31.抽象类和接口的区别
抽象类是半抽象的,有构造方法,抽象类可以包含抽象方法和非抽象方法,也可以有成员变量(不仅仅是常量)
类和类之间只能单继承,一个抽象类只能继承一个类(单继承)
接口是完全抽象的,没有构造方法,接口和接口之间支持多继承,一个类可以同时实现多个接口
这在Java 8之前基本正确,但在Java 8及之后,接口可以包含默认方法和静态方法
32.java中的几种基本类型,各占用多少字节?
33.运行时异常有哪些?
Java常见的运行时异常以及解决方案
运行时异常在Java中是RuntimeException
及其子类的实例,它们通常是由程序逻辑错误引起的,而不是外部错误。以下是一些常见的运行时异常,代码示例,以及相应的解决方案:
01 NullPointerException
1.1 异常描述
当对null
引用执行非空操作时抛出。
1.2 代码示例
代码语言:javascript
复制
String text = null;
int length = text.length(); // 这里会抛出NullPointerException
1.3 解决方案
在操作之前检查引用是否为null
。
代码语言:javascript
复制
if (text != null) {
int length = text.length();
}
02 IndexOutOfBoundsException
2.1 异常描述
当访问数组或列表的非法索引(如负数或超出范围的索引)时抛出。
2.2 代码示例
代码语言:javascript
复制
List<String> list = new ArrayList<>();
list.get(0); // 这里会抛出IndexOutOfBoundsException,因为列表为空
2.3 解决方案
确保索引在有效范围内
代码语言:javascript
复制
if (!list.isEmpty()) {
String firstElement = list.get(0);
}
03 IllegalArgumentException
3.1 异常描述
当方法接收到不合法的参数值时抛出
3.2 代码示例
代码语言:javascript
复制
int result = Math.sqrt(-1); // 这里会抛出IllegalArgumentException,因为负数不能开平方
3.3 解决方案
检查参数是否符合预期的条件。
代码语言:javascript
复制
if (value >= 0) {
double result = Math.sqrt(value);
}
04 IllegalStateException
4.1 异常描述
当对象处于不合法的状态时抛出,通常用于控制对象的状态。
4.2 代码示例
代码语言:javascript
复制
try (InputStream input = new FileInputStream("file.txt")) {
// ... 一些操作
} // 这里会抛出IllegalStateException,如果文件不存在
4.3 解决方案
确保对象在使用前处于合法状态。
代码语言:javascript
复制
File file = new File("file.txt");
if (file.exists() && !file.isDirectory()) {
try (InputStream input = new FileInputStream(file)) {
// ... 一些操作
}
}
05 ArithmeticException
5.1 异常描述
当发生算术异常,如除以零时抛出。
5.2 代码示例
代码语言:javascript
复制
int divisor = 0;
int quotient = 10 / divisor; // 这里会抛出ArithmeticException
5.3 解决方案
避免除以零或处理除数为零的情况。
代码语言:javascript
复制
if (divisor != 0) {
int quotient = 10 / divisor;
}
06 NumberFormatException
6.1 异常描述
当尝试将字符串转换为数字,但字符串不符合数字格式时抛出。
6.2 代码示例
代码语言:javascript
复制
String number = "abc";
int num = Integer.parseInt(number); // 这里会抛出NumberFormatException
6.3 解决方案
确保字符串是有效的数字格式。
代码语言:javascript
复制
String number = "123";
try {
int num = Integer.parseInt(number);
} catch (NumberFormatException e) {
// 处理无效的数字格式
}
07 ClassCastException
7.1 异常描述
当尝试对对象进行不正确的类型转换时抛出。
7.2 代码示例
代码语言:javascript
复制
Object obj = new Object();
String str = (String) obj; // 这里会抛出ClassCastException
7.3 解决方案
确保类型转换是合法的。
代码语言:javascript
复制
if (obj instanceof String) {
String str = (String) obj;
}
08 NoSuchElementException
8.1 异常描述
当从迭代器或枚举中尝试获取下一个元素,但已经没有更多元素时抛出。
8.2 代码示例
代码语言:javascript
复制
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
String next = iterator.next(); // 这里会抛出NoSuchElementException
8.3 解决方案
在调用next()
之前检查迭代器是否有更多的元素。
代码语言:javascript
复制
if (iterator.hasNext()) {
String next = iterator.next();
}
09 SecurityException
9.1 异常描述
当程序试图执行安全策略不允许的操作时抛出
SecurityException
是Java中的一个运行时异常,它表示程序试图执行一个安全策略不允许的操作。这种异常通常与安全相关的操作有关,比如访问系统资源或者执行一些需要特权的操作。
9.2 代码示例
代码语言:javascript
复制
public class SecurityExceptionExample {
public static void main(String[] args) {
try {
// 尝试打开一个文件,但没有足够的权限
File file = new File("/path/to/protected/file.txt");
FileReader fileReader = new FileReader(file);
// ... 其他操作
fileReader.close();
} catch (SecurityException e) {
// 捕获到SecurityException异常
System.out.println("SecurityException caught: " + e.getMessage());
}
}
}
在这个示例中,我们尝试使用FileReader
类打开一个受保护的文件。如果当前用户没有足够的权限去读取这个文件,就会抛出SecurityException
。在捕获到这个异常后,我们可以记录日志或者通知用户他们没有执行该操作的权限。
需要注意的是,SecurityException
通常是由JVM抛出的,而不是由程序员在代码中显式抛出。此外,某些安全策略可能由Java安全管理层(如Java Security Manager)控制,如果启用了安全管理器,那么任何违反安全策略的操作都可能导致SecurityException
。
在实际开发中,应该确保程序有足够的权限执行它需要的操作,并且在用户没有相应权限时提供适当的错误处理和提示信息。
9.3 解决方案
通常需要修改程序的安全性策略或避免执行不允许的操作。
10. UnsupportedOperationException
10.1 异常描述
当尝试执行不支持的操作时抛出,如在不可变的集合上执行添加或删除操作。
UnsupportedOperationException
是 Java 中的一个运行时异常,它表明某个操作不支持在特定的对象上执行。这通常发生在尝试对不可变对象进行修改,或者在不支持某种操作的集合类型上执行该操作时。
10.2 代码示例
代码语言:javascript
复制
import java.util.Collections;
import java.util.List;
public class UnsupportedOperationExceptionExample {
public static void main(String[] args) {
// 创建一个不可修改的列表
List<String> immutableList = Collections.unmodifiableList(List.of("Apple", "Banana", "Cherry"));
try {
// 尝试修改列表内容,将会抛出 UnsupportedOperationException
immutableList.add("Date");
} catch (UnsupportedOperationException e) {
// 捕获到 UnsupportedOperationException 异常
System.out.println("Caught exception: " + e.getMessage());
}
}
}
在这个示例中,我们首先使用 Collections.unmodifiableList
方法创建了一个不可修改的列表 immutableList
。当我们尝试使用 add
方法向这个列表中添加新元素时,会抛出 UnsupportedOperationException
,因为 immutableList
是不可变的,不支持添加或删除操作。
为了处理这种异常,我们可以在执行可能会抛出 UnsupportedOperationException
的代码块中使用 try-catch
语句。在 catch
块中,我们可以添加错误处理逻辑,比如记录日志、通知用户或者执行其他恢复操作。
在实际编程中,如果你正在使用的是一个不可变对象或者一个不支持某些操作的集合,那么你需要确保不执行这些不支持的操作,或者在执行前进行检查以避免抛出 UnsupportedOperationException
。同时,如果你自己实现了一个集合类型,并且某些操作对于你的集合来说没有意义,你可以选择抛出这个异常来告知调用者这一点。
10.3 解决方案
使用支持所需操作的合适数据结构或方法。 处理运行时异常的关键是理解为什么会抛出异常,并在代码中采取适当的预防措施。这通常涉及到对输入的验证、对对象状态的管理以及对异常情况的适当处理。通过这些方法,可以提高程序的健壮性和可靠性。
34.对面向对象的理解?面向对象有的特征有哪些?
如何理解面向对象(什么是面向对象?)_面向对象的理解-CSDN博客
35.HTTP报文有哪几部分组成?Get/Post有什么区别
GET 和 POST 的区别_get post-CSDN博客
36.转发(forward)和重定向(redirect)的区别
1、地址栏变化
转发(Forward):地址栏URL不变,客户端不知内部跳转细节。
重定向(Redirect):地址栏URL更新为新URL,客户端发起新的HTTP请求。
2、数据共享
转发(Forward):转发前后页面共享同一个request对象,可轻松传递数据。
重定向(Redirect):因是两个独立请求,需通过URL参数、Session或Cookie等方式传递数据。
3、运用场景
转发(Forward):适用于内部页面跳转,保持用户登录状态或传递敏感信息。
重定向(Redirect):常用于改变浏览器地址栏URL,如登录后跳转、注销后返回首页,或防止表单重复提交。
4、效率对比
转发(Forward):服务器内部处理,仅一次响应,效率较高。
重定向(Redirect):涉及两次服务器到客户端的响应过程,效率相对较低。
5、总结
选择转发或重定向应根据具体需求而定。转发适用于内部逻辑跳转和敏感信息传递,而重定向则更适合用于URL改变和防止表单重复提交的场景。
37.什么是AOP?什么是IOC?
一、什么是IOC?
1、IoC (Inversion of control )就是控制反转/反转控制。它是一种思想不是一个技术实现。主要就是用来解决创建和管理对象的
2、就是不需要通过new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可
3、可以让对象之间的耦合度和依赖程度降低
4、使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器要就可以了
二、什么是 AOP
1、AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。
2、比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了,只能通过AOP来解决
3、AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,从根本上解耦合,避免横切逻辑代码重复
三、JAVA中实现AOP的方式有哪些?
1、第一种是通过原生jdk实现方式,首先要定义接口,实现一个切面代理,原生的AOP需要实现InvocationHandler接口,才能实现AOP
2、第一种是通过cglib切面来实现,相同点是都使用了代理模式,不同点是前者使用了接口代理,后者使用了继承代理,什么意思呢?
四、AOP代理方式有哪些?
1、AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表
2、使用AspectJ的编译时增强,实现AOP,AspectJ是静态代理的增强。所谓的静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。
3、Spring AOP使用的是动态代理。所谓的动态代理,就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
4、Spring AOP中的动态代理,主要有两种方式:
JDK动态代理和CGLIB动态代理。
1)JDK动态代理通过“反射”来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
2)CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态地生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
五、IOC控制反转的其他解释
IoC(控制反转)就是将程序中原来 new 对象,交给spring创建,从spring工厂获取对象,使用spring来负责控制对象的生命周期和对象间的关系
Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
六、AOP面向切面编程的相关术语
Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点.
Pointcut(切入点):所谓的切入点是指我们要对哪些Joinpoint进行拦截的定义
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的事情)。
Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field。
Target代理的目标对象。
Weaving(织rget(目标):入):是指把增强应用到目标对象来创建的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
38.AOP用到什么设计模式
在 AOP 中,代理、装饰器和拦截器等设计模式经常被使用,下面分别介绍它们在 AOP 中的应用:
- 代理模式:代理模式在 AOP 中的应用较为广泛。在 AOP 中,代理模式通常用于实现动态代理,即在程序运行时生成代理对象,代理对象包含了目标对象的所有方法,并且可以在方法执行前后插入额外的逻辑,如日志记录、性能监控、事务管理等。
- 装饰器模式:装饰器模式是一种结构型设计模式,它可以动态地为一个对象添加新的行为,而不需要对原始类进行修改。在 AOP 中,装饰器模式通常用于实现 Aspect,即将一组通用的横切关注点包装在一个 Aspect 中,然后将 Aspect 应用到目标对象中。
- 拦截器模式:拦截器模式是一种结构型设计模式,它可以在不修改原始类的情况下,通过拦截并修改方法的调用,来实现额外的逻辑。在 AOP 中,拦截器模式通常用于实现 Advice,即在特定的 Join Point 上执行特定的操作,例如在方法执行前后记录日志、验证权限、处理异常等
使用场景
- 日志记录:在程序执行期间,记录关键操作的执行情况、错误信息等。
- 安全性检查:在程序执行期间,检查用户的权限或身份验证,以确保只有授权的用户才能执行特定的操作。
- 事务管理:确保多个操作在一个事务中执行,以确保数据的完整性和一致性。
- 性能监控:监控程序执行的性能,包括执行时间、内存使用等指标,以便进行优化。
- 异常处理:在程序执行期间捕获和处理异常,以避免程序崩溃或出现未知错误。
- 缓存管理:管理程序中的缓存,包括缓存的存储、刷新和删除等操作。
- 国际化:管理程序中的国际化资源,以便程序能够在不同的语言环境中正确地运行
39.SpringBean的生命周期
Spring容器系列-bean的生命周期 - 欢乐豆123 - 博客园
40.SpringIOC是怎么实现了依赖注入,有几种依赖注入方式?
Spring IoC——依赖注入_ioc依赖注入-CSDN博客
41.SpringMVC的执行流程
SpringMVC 执行流程整体如下:
执行流程分析
(1)浏览器提交请求到中央调度器。 (2)中央调度器直接将请求转给处理器映射器。 (3)处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器。 (4)中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器。 (5)处理器适配器调用执行处理器。 (6)处理器将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给处理器适配器。 (7)处理器适配器直接将结果返回给中央调度器。 (8)中央调度器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。 (9)视图解析器将封装了的视图对象返回给中央调度器。 (10)中央调度器调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象。 (11)中央调度器响应浏览器。
执行流程中的 API 简要说明
1. DispatcherServlet
中央调度器,也称为前端控制器,在 MVC 架构模式中充当控制器 C, DispatcherServlet 是整个流程的控制中心,由它调用诸如处理器映射器、处理器适配器、视图解析器等其它组件处理用户请求。 中央调度器的存在降低了组件之间的耦合度。
2. HandlerMapping
处理器映射器, 负责根据用户请求找到相应的将要执行的 Handler,即处理器。 即用于完成将用户请求映射为要处理该请求的处理器,并将处理器封装为处理器执行链传给中央调度器。
3. HandlAdapter
处理器适配器, 通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 中央调度器会根据不同的处理器自动为处理器选择适配器,以执行处理器。
4. Handler
处理器,也称为后端控制器,在 DispatcherServlet 的控制下 Handler 调用 Service 层对具体的用户请求进行处理。由于 Handler 涉及到具体的用户业务请求,所以一般情况下需要程序员根据业务需求自己开发 Handler。
5. ViewResolver
视图解析器, 负责将处理结果生成 View 视图, ViewResolver 首先将逻辑视图名解析为物理视图名,即具体的页面地址,再生成 View 视图对象。最后将处理结果通过页面形式展示给用户。 SpringMVC 框架提供了很多的 View 视图类型,包括: JstlView、 RedirectView 等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
42.SpringMVC与springboot的区别?
43.SpringMVC的事物有过哪几种?怎么处理的?
springmvc事务管理详解_springmvc的事务有用过哪几种?怎么处理的?-CSDN博客
44.SpringBoot在初始化以前执行一个方法,应该怎么做?初始化以后在执行一个方法应该怎么做,有哪几种方法去做
方法一:@PostConstruct
此方法可能是最常用的
可以使用Spring Boot的@PostConstruct注解来实现在启动时执行一次的功能。@PostConstruct注解标记的方法会在Bean初始化完成后自动调用,可以在该方法中执行只需要在启动时执行一次的操作。
如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
Constructor >> @Autowired >> @PostConstruct
例如,在一个Spring Boot应用程序的启动类中添加一个@PostConstruct注解标记的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
在以上示例代码中,init()方法被标记为@PostConstruct注解,表示它会在MyApplication Bean初始化完成后自动调用。在init()方法中可以执行只需要在启动时执行一次的操作,例如初始化一些数据、建立数据库连接等。
方法二:使用Spring Boot提供的CommandLineRunner接口或ApplicationRunner接口
此方法已经在项目中实践使用ok。
除了@PostConstruct注解,还可以使用Spring Boot提供的CommandLineRunner接口或ApplicationRunner接口来实现在启动时执行一次的功能。
这两个接口都有一个run()方法,在应用程序启动后会被自动调用。需要在该方法中实现需要在启动时执行的操作,例如初始化数据、开启定时任务等。
如果需要多个操作在启动时执行,可以定义多个实现了CommandLineRunner或ApplicationRunner接口的Bean,并通过@Order注解指定它们的执行顺序。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
以上示例代码定义了两个Bean,分别是实现CommandLineRunner接口的MyCommandLineRunner和实现ApplicationRunner接口的MyApplicationRunner。它们的run()方法会在应用程序启动后自动调用,可以在这里实现需要在启动时执行的操作。其中,@Order注解用于指定它们的执行顺序,数字越小越先执行。
方法三:使用Spring Boot提供的ApplicationListener接口
此方法暂未实践
还可以使用Spring Boot提供的ApplicationListener接口来实现在应用程序启动时执行一次的功能。这个接口定义了监听Spring Boot应用程序事件的方法,当应用程序触发相应的事件时,监听器会自动调用相应的方法进行处理。
具体实现步骤如下:
创建一个实现ApplicationListener接口的类,例如MyApplicationListener。
实现onApplicationEvent()方法,在该方法中编写需要在启动时执行的操作,例如初始化数据、建立数据库连接等。
通过@Component注解或@Bean注解将MyApplicationListener注册成Spring Bean。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 |
|
以上示例代码创建了一个名为MyApplicationListener的Bean,并实现了ApplicationListener接口,用于监听ApplicationReadyEvent事件。在onApplicationEvent()方法中编写需要在启动时执行的操作。最后通过@Component注解将MyApplicationListener注册成Spring Bean。
当应用程序启动完成后,MyApplicationListener会自动监听到ApplicationReadyEvent事件并执行其中的代码。可以在这里实现需要在启动时执行一次的操作,确保其只在应用程序启动时执行一次。
45.在项目中会经常用到哪些注解?讲出十个
Springboot注解
核心注解
1. @SpringBootApplication
- 作用: 标注一个主程序类,表明这是一个Spring Boot应用程序的入口。
- 功能: 这是一个复合注解,组合了
@Configuration
、@EnableAutoConfiguration
和@ComponentScan
。@Configuration
: 标识一个类作为配置类,类似于Spring XML配置文件。@EnableAutoConfiguration
: 启用Spring Boot的自动配置机制,根据项目中的依赖和应用上下文自动配置Spring应用程序。@ComponentScan
: 自动扫描指定包及其子包中的Spring组件。
- 示例:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
自动配置注解
2. @EnableAutoConfiguration
- 作用: 启用Spring Boot的自动配置机制,根据项目中的依赖和应用上下文自动配置Spring应用程序。
- 细节: 它会尝试根据添加的依赖和项目中的配置自动配置Spring Bean。例如,如果项目中有HSQLDB的依赖,Spring Boot会自动配置一个内存数据库。
- 示例:
@EnableAutoConfiguration
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
3. @Configuration
- 作用: 标注一个类作为配置类,相当于一个Spring XML配置文件。
- 细节: 配置类可以包含一个或多个
@Bean
注解的方法,这些方法会返回要注册到Spring应用上下文中的Bean。 - 示例:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
组件扫描和注入注解
4. @ComponentScan
- 作用: 指定要扫描的包,以便发现和注册Spring组件。
- 细节: 默认情况下,
@ComponentScan
会扫描主应用类所在的包及其子包。 - 示例:
@ComponentScan(basePackages = "com.example")
public class MyApplication {
}
5. @Component
- 作用: 将一个类标识为Spring组件(Bean),可以被Spring容器自动检测和注册。
- 细节:
@Component
是一个通用的注解,可以用来标注任何Spring管理的Bean。 - 示例:
@Component
public class MyComponent {
}
6. @Service
- 作用: 标识服务层组件,实际上是
@Component
的一个特化,用于表示业务逻辑服务。 - 细节: 用于标识服务层的类,这些类通常包含业务逻辑。
- 示例:
@Service
public class MyService {
}
7. @Repository
- 作用: 标识持久层组件,实际上是
@Component
的一个特化,用于表示数据访问组件
。
数据访问组件
(Data Access Component)通常是指在软件系统中负责数据访问和持久化的部分或模块。在典型的企业应用程序中,数据访问组件通常涉及与数据库交互、执行数据操作(如增删改查)、数据转换和持久化等任务。
- 示例:
@Repository
public class MyRepository {
}
8. @Controller
- 作用: 标识控制层组件,实际上是
@Component
的一个特化,用于表示Web控制器。 - 细节: 用于标识Spring MVC控制器,处理Web请求并返回视图。
- 示例:
@Controller
public class MyController {
}
9. @RestController
- 作用: 标识一个RESTful Web服务的控制器,实际上是
@Controller
和@ResponseBody
的结合。 - 细节: 返回的对象会自动序列化为JSON或XML,并写入HTTP响应体中。
- 示例:
@RestController
public class MyRestController {
}
数据绑定和验证注解
10. @RequestMapping
- 作用: 映射HTTP请求到处理方法上(GET、POST、PUT、DELETE等)。
- 示例:
@Controller
public class MyController {
@RequestMapping("/hello")
public String sayHello() {
return "hello";
}
}
11. @GetMapping
- 作用: 映射HTTP GET请求到处理方法上。
- 示例:
@RestController
public class MyRestController {
@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}
}
12. @PostMapping
- 作用: 映射HTTP POST请求到处理方法上。
- 示例:
@RestController
public class MyRestController {
@PostMapping("/users")
public User
13. @ResponseBody
- 作用: 将方法的返回值转换为指定格式(如JSON、XML)作为HTTP响应的内容返回给客户端。
- 细节: 常用于RESTful服务中,标识方法返回的对象不是视图名称,而是直接写入HTTP响应体中的数据。
- 示例:
@RestController
public class MyRestController {
@GetMapping("/hello")
@ResponseBody
public String sayHello() {
return "Hello, World!";
}
}
14. @RequestBody
- 作用: 将HTTP请求体的内容(如JSON、XML等)映射到一个Java对象。
- 细节: 通常用于POST请求中,将客户端发送的数据绑定到方法的参数上。
- 示例:
@RestController
public class MyRestController {
@PostMapping("/users")
public ResponseEntity<String> addUser(@RequestBody User user) {
// 处理用户添加逻辑
return ResponseEntity.ok("User added successfully");
}
}
15. @PathVariable
- 作用: 从URI路径中提取参数值,将其映射到方法的参数上。
- 细节: 常用于RESTful服务中,允许动态地将URL中的部分作为方法参数使用。
- 示例:
@RestController
public class MyRestController {
@GetMapping("/users/{userId}")
public ResponseEntity<User> getUserById(@PathVariable Long userId) {
// 根据userId查询用户信息
User user = userService.getUserById(userId);
return ResponseEntity.ok(user);
}
}
16. @RequestParam
- 作用: 用于从请求中获取参数的值。
- 细节: 可以指定参数的默认值,是否必需等属性。适用于处理查询参数或表单数据。
- 示例:
@RestController
public class MyRestController {
@GetMapping("/users")
public ResponseEntity<User> getUserByName(@RequestParam String username) {
// 根据用户名查询用户信息
User user = userService.getUserByUsername(username);
return ResponseEntity.ok(user);
}
}
这些注解是Spring MVC和Spring Boot中常用的数据绑定和处理注解,帮助开发者更方便地处理HTTP请求和响应,实现灵活的接口设计和数据交互。
数据访问注解
17. @Entity
- 作用: 标识一个JPA实体。
- 细节: 用于定义一个与数据库表映射的持久化类。
- 示例:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
18. @Table
- 作用: 指定实体对应的数据库表名称。
- 细节: 如果类名与数据库表名不同,可以使用
@Table
注解进行指定。 - 示例:
@Entity
@Table(name = "users")
public class User {
// fields and methods
}
其他常用注解
19. @Value
- 作用: 用于将外部属性值注入到Spring Bean中。
- 细节: 可以用于注入属性文件中的值、系统环境变量、系统属性等。
- 示例:
@Value("${my.property}")
private String myProperty;
20. @Autowired
- 作用: 自动装配Bean,进行依赖注入。
- 细节: Spring会自动满足Bean的依赖。可以用于字段、构造函数和方法上。
- 示例:
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
}
21. @Bean
- 作用: 用于定义一个Spring Bean。
- 细节: 通常用于配置类中,定义的方法返回的对象会被注册为Spring容器中的Bean。
- 示例:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
22. @Conditional
- 作用: 根据特定条件创建Bean。
- 细节: 可以与自定义条件类一起使用,只有在条件满足时,Spring才会创建该Bean。
- 示例:
@Bean
@Conditional(MyCondition.class)
public MyService myService() {
return new MyServiceImpl();
}
23. @Primary
- 作用: 指定当一个接口有多个实现时,优先选择的Bean。
- 示例:
@Service
@Primary
public class PrimaryService implements MyService {
}
24. @Qualifier
- 作用: 配合
@Autowired
使用,用于指定注入的Bean的名称或ID。 - 示例:
@Service
public class MyService {
@Autowired
@Qualifier("myRepositoryImpl")
private MyRepository myRepository;
}
25. @Lazy
- 作用: 延迟初始化Bean,只有在首次使用时才创建Bean。
- 示例:
@Service
@Lazy
public class LazyService {
}
26. @Scope
- 作用: 指定Bean的作用域(单例、多例、请求、会话等)。
- 示例:
@Service
@Scope("prototype")
public class PrototypeService {
}
其他注解
简化POJO开发的注解
这三个注解通常与Lombok(一种Java库)结合使用,用于简化POJO(Plain Old Java Object)的开发。
1. @Data
-
作用:
@Data
是一个复合注解,集成了@ToString
、@EqualsAndHashCode
、@Getter
、@Setter
和@RequiredArgsConstructor
的功能。 -
功能:
@ToString
: 自动生成toString()
方法,包含所有字段。@EqualsAndHashCode
: 自动生成equals()
和hashCode()
方法,基于所有非静态和非瞬态字段。@Getter
和@Setter
: 自动生成字段的 getter 和 setter 方法。@RequiredArgsConstructor
: 自动生成包含必需字段的构造方法。
-
示例:
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private String email;
}
编译时,Lombok会自动生成如下代码:
public class User {
private Long id;
private String username;
private String email;
// 自动生成的 toString(), equals(), hashCode(), getter 和 setter 方法
// 以及包含所有字段的构造方法
}
2. @AllArgsConstructor
-
作用: 自动生成包含所有字段的构造方法。
-
示例:
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String email;
}
编译时,Lombok会自动生成如下构造方法:
public User(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
}
3. @NoArgsConstructor
-
作用: 自动生成无参构造方法。
-
示例:
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String email;
}
编译时,Lombok会自动生成如下无参构造方法:
public User() {
// 空构造方法
}
46.@Autowired注入和自己new一个对象有什么区别?
在Spring框架中,@Autowired注解用于依赖注入(Dependency Injection, DI),这是一种设计模式,它允许你将对象的创建和管理交给容器(如Spring IoC容器)来处理,而不是通过传统的new关键字直接创建对象。
通过@Autowired注入:
自动装配:Spring容器负责查找合适的Bean并将其注入到需要的地方。这意味着你无需手动实例化依赖的对象,从而降低了耦合度。
生命周期管理:由Spring容器管理的Bean具有完整的生命周期,包括初始化、销毁等阶段,容器会根据需要进行相应的处理。
松耦合:由于依赖关系是由容器解析并注入的,因此组件之间解除了硬编码的依赖,更容易测试和替换实现。
面向接口编程:通常注入的是接口或者抽象类的实现,使得代码更侧重于接口定义而非具体的实现细节,提高了代码的灵活性和可扩展性。
通过new关键字创建对象:
手动控制:程序员必须自己管理和创建对象,包括初始化过程中的所有步骤。
紧密耦合:当一个类直接创建另一个类的实例时,它们之间的耦合度较高。修改被创建类的实现会影响到创建它的类。
缺乏容器功能支持:通过new创建的对象不会享受到Spring容器提供的诸如事务管理、AOP等功能。
不便于单元测试:在没有依赖注入的情况下,为了对某个类进行单元测试,可能需要模拟其依赖项,而使用DI则可以通过Mockito等工具轻松注入模拟对象。
总结来说,@Autowired注入是基于Spring框架的依赖注入机制,能够更好地实现组件间的解耦、提高代码可读性和可维护性,同时也便于利用Spring的各种服务,如事务管理、资源清理等。而直接使用new关键字创建对象,则意味着放弃了这些优点,但提供了更加直接和底层的控制方式。
47.myBaties防sql注入的操作
MyBatis提供了一些机制来防止SQL注入攻击,以下是几种常用的防御方法:
1. 使用`#{}`占位符:在SQL语句中使用`#{}`占位符来代替传入的参数值,而不是直接拼接参数值到SQL语句中。`#{}`会对参数值进行预处理,包括自动转义和格式化操作,从而有效地防止SQL注入攻击。
2. 使用动态SQL:MyBatis提供了动态SQL的功能,可以根据条件动态构建SQL语句。通过使用动态SQL的条件判断和循环等功能,可以避免直接拼接参数值到SQL语句中,从而减少SQL注入的风险。
3. 输入参数校验:在接收用户输入参数之前,进行输入参数的校验和过滤。可以使用正则表达式、白名单等方式对输入参数进行验证,只接受符合规定的参数值,过滤掉潜在的恶意输入。
4. 使用预编译语句:如果需要执行一些动态拼接的SQL语句,可以使用预编译语句(Prepared Statement)来执行。预编译语句会将参数值安全地传递给数据库,数据库会对参数值进行预处理,从而有效地防止SQL注入攻击。
5. 限制权限:在数据库层面,可以限制数据库用户的权限,只给予其执行特定操作的权限,避免恶意操作和注入攻击
48.Mybaties都要用到哪些标签
MyBatis 中常用标签_mybatis标签有哪些-CSDN博客
49.#和$的区别
#原样输出
$占位符
50.Mybaties如何实现循环?
在MyBatis中实现循环可以使用<foreach>
元素。<foreach>
元素允许你在输入集合中迭代,并在每次迭代中执行一组 SQL 语句。
下面是一个示例,展示如何在MyBatis中使用<foreach>
元素实现循环:
<select id="getUsersByIds" resultMap="userResult" parameterType="java.util.List">
SELECT * FROM user
WHERE id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</select>
复制代码
在这个示例中,我们定义了一个<select>
元素,它接收一个java.util.List
类型的参数ids
,并使用<foreach>
元素在ids
集合中迭代。在每次迭代中,会将id
值插入到 SQL 语句中的IN
子句中。
要注意的是,在<foreach>
元素中,item
属性指定了每次迭代中的变量名,collection
属性指定了要迭代的集合,open
属性指定了迭代开始时插入的字符串,separator
属性指定了各个元素之间的分隔符,close
属性指定了迭代结束时插入的字符串。
通过这种方式,你可以在MyBatis中很方便地实现循环操作。
51.@Component,@Controller,@Repository,@Service有何区别?
- 1.四个注解的作用是一样的
- 2.@Controller、@Service、@Repository的源注解就是@Component
- 3.@Controller用于控制层
- 4.@Service用于业务逻辑层
- 5.@Repository用于数据访问层
52.SpringBoot中的注解?SpringBoot的核心注解是什么?
1、@SpringBootApplication
这是 Spring Boot 最最最核心的注解,用在 Spring Boot 主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。
其实这个注解就是 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 这三个注解的组合,也可以用这三个注解来代替 @SpringBootApplication 注解。
2、@EnableAutoConfiguration
允许 Spring Boot 自动配置注解,开启这个注解之后,Spring Boot 就能根据当前类路径下的包或者类来配置 Spring Bean。
如:当前类路径下有 Mybatis 这个 JAR 包,MybatisAutoConfiguration 注解就能根据相关参数来配置 Mybatis 的各个 Spring Bean。
3、@Configuration
这是 Spring 3.0 添加的一个注解,用来代替 applicationContext.xml 配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在类来进行注册。
4、@SpringBootConfiguration
这个注解就是 @Configuration 注解的变体,只是用来修饰是 Spring Boot 配置而已,或者可利于 Spring Boot 后续的扩展。
5、@ComponentScan
这是 Spring 3.1 添加的一个注解,用来代替配置文件中的 component-scan 配置,开启组件扫描,即自动扫描包路径下的 @Component 注解进行注册 bean 实例到 context 中。
6、@Conditional
这是 Spring 4.0 添加的新注解,用来标识一个 Spring Bean 或者 Configuration 配置文件,当满足指定的条件才开启配置。@Conditional注解需要和Condition接口搭配一起使用。通过对应Condition接口来告知是否满足匹配条件。
@Conditional注解可以添加在@Configuration、@Component、@Service等修饰的类上用于控制对应的Bean是否需要创建,或者添加在@Bean修饰的方法上用于控制方法对应的Bean是否需要创建
7、@ConditionalOnBean
组合 @Conditional 注解,当容器中有指定的 Bean 才开启配置。
8、@ConditionalOnMissingBean
组合 @Conditional 注解,和 @ConditionalOnBean 注解相反,当容器中没有指定的 Bean 才开启配置。
9、@ConditionalOnClass
组合 @Conditional 注解,当容器中有指定的 Class 才开启配置。
10、@ConditionalOnMissingClass
组合 @Conditional 注解,和 @ConditionalOnMissingClass 注解相反,当容器中没有指定的 Class 才开启配置。
11、@ConditionalOnWebApplication
组合 @Conditional 注解,当前项目类型是 WEB 项目才开启配置。
当前项目有以下 3 种类型。
<code class="language-plaintext hljs">enum Type {
/**
* Any web application will match.
*/
ANY,
/**
* Only servlet-based web application will match.
*/
SERVLET,
/**
* Only reactive-based web application will match.
*/
REACTIVE
}</code>
12、@ConditionalOnNotWebApplication
组合 @Conditional 注解,和 @ConditionalOnWebApplication 注解相反,当前项目类型不是 WEB 项目才开启配置。
13、@ConditionalOnProperty
组合 @Conditional 注解,当指定的属性有指定的值时才开启配置。
14、@ConditionalOnExpression
组合 @Conditional 注解,当 SpEL 表达式为 true 时才开启配置。
15、@ConditionalOnJava
组合 @Conditional 注解,当运行的 Java JVM 在指定的版本范围时才开启配置。
16、@ConditionalOnResource
组合 @Conditional 注解,当类路径下有指定的资源才开启配置。
17、@ConditionalOnJndi
组合 @Conditional 注解,当指定的 JNDI 存在时才开启配置。
18、@ConditionalOnCloudPlatform
组合 @Conditional 注解,当指定的云平台激活时才开启配置。
19、@ConditionalOnSingleCandidate
组合 @Conditional 注解,当指定的 class 在容器中只有一个 Bean,或者同时有多个但为首选时才开启配置。
20、@ConfigurationProperties
用来加载额外的配置(如 .properties 文件),可用在 @Configuration 注解类,或者 @Bean 注解方法上面。
21、@EnableConfigurationProperties
一般要配合 @ConfigurationProperties 注解使用,用来开启对 @ConfigurationProperties 注解配置 Bean 的支持。
22、@AutoConfigureAfter
用在自动配置类上面,表示该自动配置类需要在另外指定的自动配置类配置完之后。
如 Mybatis 的自动配置类,需要在数据源自动配置类之后。
<code class="language-plaintext hljs">@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {<!-- --></code>
23、@AutoConfigureBefore
这个和 @AutoConfigureAfter 注解使用相反,表示该自动配置类需要在另外指定的自动配置类配置之前。
24、@Import
这是 Spring 3.0 添加的新注解,用来导入一个或者多个 @Configuration 注解修饰的类,这在 Spring Boot 里面应用很多。
25、@ImportResource
这是 Spring 3.0 添加的新注解,用来导入一个或者多个 Spring 配置文件,这对 Spring Boot 兼容老项目非常有用,因为有些配置无法通过 Java Config 的形式来配置就只能用这个注解来导入。
53.springBoot要发布一个接口需要几个注解?
在Spring Boot中发布一个接口通常需要以下几个核心注解:
-
@RestController:这是一个组合注解,包含了@Controller和@ResponseBody。它用于标注一个控制器类,表示该类中的方法会直接返回数据给客户端,而不是返回视图名称。通常用于构建RESTful Web服务。
-
@RequestMapping:用于映射HTTP请求到特定的处理方法上。可以在类级别和方法级别使用。在类级别上,它定义了一个基本的请求路径前缀;在方法级别上,它进一步细化了请求的具体路径和HTTP方法(如GET、POST、PUT、DELETE等)。
-
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping等:这些注解是@RequestMapping的特化形式,分别对应于不同的HTTP请求方法。例如,@GetMapping注解用于处理GET请求。
-
@RequestParam:用于将请求参数绑定到方法参数上。例如,获取查询参数或表单参数。
-
@PathVariable:用于将URL中的占位符参数绑定到方法参数上。例如,获取URL中的动态部分。
-
@Autowired:用于自动装配Spring Bean。当一个类的字段、构造函数或方法参数上标注了@Autowired注解时,Spring容器会自动将匹配类型的Bean注入到该字段、构造函数或方法中。
-
@Value:用于将外部配置的值注入到字段中。可以从properties文件、环境变量等来源获取值。
54.SpringBoot的运行原理/启动流程?
Spring Boot 启动流程及原理介绍_springboot的启动流程及原理-CSDN博客
55.SpringBoot项目启动类上有什么注解?除了常用的还有什么注解?
56.SpringBoot配置文件怎么配?
57.SpringBoot怎么整合MyBatis
58.SpringBoot项目启动时配置文件加载顺序
一、Spring Boot 配置文件的加载顺序
1)bootstrap.properties 或 bootstrap.yml (如果存在) application.properties 或 application.yml
注意:
yml 文件优先加载于 properties 文件,后加载的配置项会覆盖先加载的配置项,所以如果 yml 和 peoperties 文件有相同的配置项,那么最终的值会是 properties 配置文件的值。
在单机版项目中:
配置文件加载顺序:application.yml > application.properties
在微服务项目中:
配置文件加载顺序:boostrap.yml > bootstrap.properties > application.yml > application.properties
2)命令行参数
3)操作系统环境变量
4)从 RandomValuePropertySource 生成的 random.* 属性
5)由 @TestPropertySource 注解声明的属性
6)由 @SpringBootTest 注解并且 #properties 注解属性的测试属性
7)由 SpringBootApplication 注解的 exclude 属性排除的自动配置的类
8)由应用程序的 RandomValuePropertySource 生成的 random.* 属性
9)在 application.properties 或 application.yml 中使用 SpringApplication 的 setDefaultProperties 方法设置的属性
这个加载顺序是有意为此的,因为有些属性需要在后续加载的时候覆盖前面的同名属性。
外部配置文件的加载方式:
- 命令行参数:可以直接在启动命令后添加启动参数。
- spring.config.location:用于指定配置文件的新位置
二、在Spring Boot中,配置文件的加载顺序遵循以下步骤
- 自动加载:Spring Boot在启动时会扫描特定位置的配置文件。这些位置包括jar包内的classpath路径、当前项目的根目录以及桌面上的文件路径。Spring Boot会优先加载高优先级的配置文件,并在低优先级配置文件被加载时覆盖掉冲突的属性。
- 自定义配置文件:开发者可以通过spring.config.name属性指定自定义配置文件名。Spring Boot会按照以下顺序查找这些配置文件:application.和application-default.,并根据扩展名的优先级进行加载。扩展名包括:.properties、.xml、.yml、.yaml。
- 命令行参数:开发者可以在命令行中指定一些参数来覆盖默认的配置值。这些参数将优先于任何其他配置文件中的值生效。
- 环境变量:环境变量也可以用来覆盖配置文件中的属性值。这些变量在应用程序启动时自动加载,无需额外操作。
- 属性占位符:在配置文件中,可以使用${...}语法来引用其他属性的值。这种方式可以创建依赖关系,使得某些属性在其他属性被解析后才能确定其值。
- 自动配置类:Spring Boot提供了一系列的自动配置类,可以根据项目需求自动配置一些组件。开发者可以通过禁用特定的自动配置类或自定义自动配置类来覆盖默认设置。
- 条件注解:Spring Boot允许使用条件注解来控制特定组件的创建。例如,只有当某个属性存在或满足特定条件时,某个bean才会被创建。
- 外部化配置:Spring Boot支持将部分配置移动到外部属性文件中,以提高可维护性和复用性。这些外部属性文件可以包含在jar包内部、当前项目根目录或其他指定位置。
- 总结来说,Spring Boot的配置加载顺序遵循以下原则:优先从高优先级的源加载配置,并在低优先级源加载时覆盖冲突的属性;开发者可以通过自定义配置文件、命令行参数和环境变量来覆盖默认值;自动配置类和条件注解允许更灵活地控制组件的创建;而外部化配置则提高了应用程序的维护性和复用性。了解这个加载顺序有助于更好地管理和优化Spring Boot应用程序的配置。
59.事物的隔离级别
60.事物的七种传播方式
1,REQUIRED:只允许有一个事务存在,在大事务里如果再有小事务,小事务会自动并入到大事务里。(这也是默认的传播机制)
2,REQUIRES_NEW:每次新开一个事务,如果当前存在事务,当前事务挂起。(相当于每一个小事务都是独立的)
3,SUPPORTS:当前存在事务则加入事务,不存在事务则普通执行。(和不加有一些区别,如果不在乎事务管理器,和不加没什么区别)
4,NOT_SUPPORTS:有事务则挂起该事务,没有则普通执行。(即该段代码不希望以事务方式执行)
5,MANDATORY:如果当前存在事务,则加入事务,不存在则报错。(该段代码必须以事务方式执行,但自己本身不会开启事务)
6,NEVER:有事务报异常。(当前存在事务,则报异常,改端代码必须以非事务方式执行)
7,NESTED:如果当前存在事务,当前事务挂起,新开事务,新开的事务回滚不影响外部事务的回滚。
61.过滤器和拦截器的区别?
过滤器和拦截器的区别详解_拦截器和过滤器的区别-CSDN博客
62.Spring运用了什么设计模式?讲一下哪些部分用到了这些设计模式
63.去重SQL语句怎么写
1. 使用 DISTINCT
关键字
DISTINCT
是最常用的去重方法,可以直接应用于SELECT语句中。
示例:
代码语言:txt
复制
SELECT DISTINCT column_name FROM table_name;
优势:
- 简单易用。
- 适用于大多数去重场景。
应用场景:
- 查询某个字段的所有不重复值。
2. 使用 GROUP BY
子句
GROUP BY
可以根据一个或多个列对结果集进行分组,并且通常与聚合函数(如 COUNT
)一起使用。
示例:
代码语言:txt
复制
SELECT column_name FROM table_name GROUP BY column_name;
优势:
- 可以与聚合函数结合使用,提供更多的数据处理能力。
应用场景:
- 需要对数据进行分组并统计每个组的数量。
3. 使用 HAVING
子句
HAVING
子句用于过滤 GROUP BY
分组后的结果。
示例:
代码语言:txt
复制
SELECT column_name FROM table_name GROUP BY column_name HAVING COUNT(*) = 1;
优势:
- 可以在分组后进行更复杂的条件过滤。
应用场景:
- 需要在分组后根据特定条件筛选数据。
4. 使用 JOIN
子句
通过自连接表并比较列值来实现去重。
示例:
代码语言:txt
复制
SELECT DISTINCT t1.column_name
FROM table_name t1
JOIN table_name t2 ON t1.column_name = t2.column_name
WHERE t1.id < t2.id;
优势:
- 可以处理更复杂的去重逻辑。
应用场景:
- 需要根据多个列进行去重,或者需要排除自连接的情况。
5. 使用 NOT EXISTS
子句
通过子查询来实现去重。
示例:
代码语言:txt
复制
SELECT column_name
FROM table_name t1
WHERE NOT EXISTS (
SELECT 1
FROM table_name t2
WHERE t1.column_name = t2.column_name
AND t1.id != t2.id
);
优势:
- 可以处理复杂的去重逻辑,特别是当需要排除某些特定条件时。
应用场景:
- 需要根据多个条件进行去重。
6. 使用 UNION
操作符
UNION
操作符用于合并两个或多个 SELECT
语句的结果集,并自动去除重复行。
示例:
代码语言:txt
复制
SELECT column_name FROM table_name WHERE condition1
UNION
SELECT column_name FROM table_name WHERE condition2;
优势:
- 可以合并多个查询结果并去重。
应用场景:
- 需要合并多个查询结果并去除重复行。
遇到的问题及解决方法
问题:去重效率低
原因:
- 数据量过大,导致查询效率低下。
- 去重逻辑复杂,涉及多个表或条件。
解决方法:
- 使用索引优化查询性能。
- 尽量简化去重逻辑,避免复杂的子查询或连接操作。
- 考虑使用临时表或存储过程来优化性能。
问题:去重结果不准确
原因:
- 去重条件设置不当。
- 数据中存在NULL值,导致去重结果不准确。
解决方法:
- 确保去重条件正确无误。
- 处理NULL值,可以使用
COALESCE
或IS NULL
条件。
64.MYSQL大批量数据如何导入
MySQL 大数据量导入与导出全攻略_mysql大数据量导入-CSDN博客
65.mysql如何做大数据量分页
在MySQL中,当你的表数据量非常大,一次性加载到内存可能会导致性能问题和内存溢出。这时可以使用分页查询来处理。
以下是一般步骤:
-
设置每页显示的数量(LIMIT):这是最直观的分页方式,例如每页显示10条记录。
SELECT * FROM your_table LIMIT 10 OFFSET 0;
其中,OFFSET 0表示从第一条记录开始查看。
-
使用游标处理(CURSOR):对于大型数据集,一次性获取所有结果可能会影响性能。这时可以使用游标来逐条获取和处理数据。
-
根据实际需求进行分页优化:如果某些查询频繁用于分页,你可以考虑为这些查询创建临时表,以提高性能。
66.mysql索引类型
-
单列索引: 对表中的单个列创建的索引。
-
唯一索引: 与普通索引类似,但要求所有的索引列的值都是唯一的。
-
复合索引: 对表中的多个列创建的索引,可以包含多个列的值。
-
全文本索引: 用于全文搜索的索引,适用于文本字段。
-
空间索引: 用于空间数据类型(GIS数据类型)的索引。
67.怎么避免索引失效
索引生效规则:
-
尽量使用覆盖索引,即索引包含了查询所需的所有列。
-
索引列的数据类型要匹配,避免类型转换。
-
在多表连接时,连接条件上的字段应该创建索引。
-
在使用范围查询时,确保被查询的字段上有索引。
避免索引失效:
-
避免在索引列上使用函数,这会导致索引失效。
-
尽量避免使用
OR
条件,可以改写为UNION
。 -
谨慎使用通配符(例如
%
)开头的LIKE
查询。 -
避免在索引列上进行计算或使用表达式。
-
注意不要过度索引,过多的索引可能导致优化器选择不合适的索引。
68.了解过MySQL数据库储存引擎么?
-
MyISAM:
-
不支持事务,不支持外键。
-
表级锁定,对于读操作效率较高,但在写操作并发性能较差。
-
支持全文本索引。
-
-
InnoDB:
-
支持事务和外键。
-
行级锁定,对于写操作并发性能较好。
-
支持事务的ACID特性。
-
支持外键约束。
-
选择存储引擎要根据应用场景和需求进行权衡,MyISAM适合读密集型操作,而InnoDB适合写密集型操作和要求事务支持的场景。
69.讲讲mysql最左匹配原则
最左匹配原则指的是,当使用联合索引进行查询时,MySQL会优先使用最左边的列进行匹配,然后再依次向右匹配。
假设我们有一个表,包含三个列:A、B、C
创建联合索引(A,B,C) 等同于创建了索引 A, 索引 (A,B), 索引 (A,B,C)
我们使用(A,B,C)这个联合索引进行查询时,MySQL会先根据列A进行匹配
再根据列B进行匹配,最后再根据列C进行匹配。
如果我们只查询了(A,B)这两个列,而没有查询列C,那么MySQL只会使用(A,B)这个前缀来进行索引匹配,而不会使用到列C
如果我们要查询 了(B,C)这两个列,而没有查询列A,那么MySQL索引就会失效,导致找不到索引,因为最左侧匹配原理
所以 我们应该尽量把最常用的列放在联合索引的最左边,这样可以提高查询效率
70.笛卡尔积是什么
MySQL的多表查询涉及到多个表的联合操作。如果没有明确指定连接条件,MySQL默认采用笛卡尔积(Cartesian Product)原理。笛卡尔积是指两个集合中的每个元素与另一个集合中的每个元素组合,得到的结果集的行数是两个表行数的乘积。
-- 例子:两个表的笛卡尔积 SELECT * FROM table1, table2;
上述查询将返回两个表的所有可能的组合,即笛卡尔积。
71.脏读、幻读、不可重复读
脏读(Dirty Read): 一个事务读取了另一个事务未提交的数据,如果另一个事务回滚,则读到的数据是无效的。
幻读(Phantom Read): 一个事务读取了另一个事务已提交的数据,但由于另一个事务插入或删除了数据,导致读到的数据不一致。
不可重复读(Non-repeatable Read): 在一个事务中,多次读取相同的数据,但由于其他事务的更新导致数据发生变化,产生不一致的现象
72.数据库的优化方案?一个SQL语句如何让他搜索变快?你们遇到的数据量最大的表是什么样,有多少条数据。
SQL查询的优化方案_sql查询优化有哪些措施-CSDN博客
73.慢日志怎么使用?MYSQL怎么发现动作速度慢的SQL语句?
慢查询日志是 MySQL 提供的一种用于记录执行时间超过设定阈值的 SQL 查询的日志系统。要启用慢查询日志,可以在MySQL 配置文件 ( my.cnf 或 my.ini ) 中设置如下参数:slow_query_log = 1 slow_query_log_file = /path/to/slow-query.log long_query_time = 2 # 设置慢查询的时间阈值,默认为10秒,这里设为2秒 log_queries_not_using_indexes = 1 # 记录没有使用索引的查询
重启 MySQL 服务后,慢查询日志就会开始记录执行时间超过 long_query_time 设定值的 SQL 语句。通过分析慢查询日志,可以找出执行效率低下的SQL 语句,并对其进行优化。此外,还可以使用 mysqldumpslow 工具来汇总和分析慢查询日志,帮助快速定位问题 SQL 。
74.分库分表是怎么做的?数据库分表分库后怎么保证其一致性?
75.MySQL表为何产生死锁?如何避免死锁?
死锁及避免方法
-
死锁: 两个或多个事务互相等待对方释放锁,导致所有事务都无法继续执行。
-
避免死锁的方法:
- 加锁顺序: 事务按照相同的顺序获取锁,降低死锁的概率。
- 加锁时限: 设置事务的锁等待时限,超过时限则放弃锁。
- 死锁检测: 定期检测死锁并主动回滚某些事务,释放资源。
76.oracle与mysql的区别?mysql分页和oracle分页的区别?
主要区别 :事务处理模型 : Oracle 支持多版本并发控制 (MVCC) ,而 MySQL 的 InnoDB 存储引擎也支持 MVCC ,但MyISAM 不支持。存储过程和触发器 :两者都支持存储过程和触发器,但在语法和支持的功能上有所不同。备份恢复 : Oracle 提供了更复杂的备份恢复选项,如 RMAN ; MySQL 则相对简单些。
分页查询的区别 :MySQL : 使用 LIMIT 关键字进行分页,例如 SELECT * FROM table LIMIT offset, count ;Oracle : 早期版本需要借助子查询或 ROWNUM 伪列实现分页,较新版本(如 Oracle 12c 及以上) 可以直接使用 OFFSET FETCH 语法实现分页,如 SELECT * FROM table OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;
77.Mysql默认的事务隔离级别?
1、READ-UNCOMMITTED(读取未提交)
最低的隔离级别,允许脏读,也就是可能读取 到其他会话中未提交事务修改的数据,
可能会导致脏读、幻读或不可重复读。
2、READ-COMMITTED(读取已提交)
只能读取到已经提交的数据。
Oracle 等大多数数据库默认都是该级别(不重复读)
可以阻止脏读,但是幻读或不可重复读仍有可能发生。
3、REPEATABLE-READ(可重复读)(MySQL默认)
对同一字段的多次读取结果都是一致的,除非数据 是被本身事务自己所修改,
可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL 默认采用的 REPEATABLE_READ 隔离级别。
4、SERIALIZABLE(可串行化)
最高的隔离级别,完全服从 ACID 的隔离级别。
所有的 事务依次逐个执行,这样事务之间就完全不可能产生干扰,
也就是说,该级别可以防止脏 读、不可重复读以及幻读。该事务隔离级别执行效率低下,且性能开销也最大,所以一般情况下不推荐使用。
如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。那么这种隔离级别就称之为串行化。
78.Mysql的索引的数据结构?
MySQL 中最常用的索引数据结构是 B+ 树 。此外,对于全文搜索, MySQL 还使用了特定的全文索引结构。B+ 树索引的特点是所有的叶子节点都包含了指向实际数据记录的指针,并且所有叶子节点之间是有 序链接的,这使得范围查询非常高效。
79.Mysql中count()和count(1)区别
在大多数情况下, count(*) 和 count(1) 在 MySQL 中的执行效果是一样的,都会计算表中的总行数。 理论上, count(*) 是标准 SQL 定义的统计行数的方式,它会统计包括 NULL 值在内的所有行;而 count(1) 则是对每行返回一个常量值(即 1 ),然后计数。但实际上, MySQL 优化器会将这两种形式 优化为相同的操作,因此性能上没有明显差异。
80.Sql看执行计划?
EXPLAIN SELECT * FROM your_table WHERE conditions;
- id : 查询中每个部分的选择标识符。
- select_type : 显示对应行是简单还是复杂查询类型。
- table : 输出行引用的表名。
- type : 访问类型,从最佳到最差依次为:system > const > eq_ref > ref > range > index > ALL。
- possible_keys : 显示哪些索引可能被用来查找行。
- key : 实际使用的索引。
- rows : 预估需检查的行数。
- Extra : 提供额外信息,如是否使用了文件排序或临时表等。
- 通过分析这些信息,可以了解查询是否有效地使用了索引,是否存在全表扫描等问题,并据此进行相应的优化。
81.创建线程的方式及其区别
class MyThread extends Thread {
public void run() {
// 线程执行代码
}
}
class MyRunnable implements Runnable {
public void run() {
// 线程执行代码
}
}
new Thread(new MyRunnable()).start();
82.mysq1锁的粒度?并发编程锁到行还是表?(介绍页级、表级、行级)
MySQL 支持不同级别的锁定机制:表级锁 :锁定整个表,适用于读操作较多、写操作较少的情况。 MyISAM 存储引擎主要使用表级锁。行级锁 :锁定表中的特定行,适合高并发环境下的写操作。 InnoDB 存储引擎支持行级锁。页级锁 :介于表级锁和行级锁之间,锁定的是数据库页(通常是几 KB 的数据块)。尽管 MySQL 本身不直接支持页级锁,但在某些场景下可以通过插件或其他数据库系统实现类似的功能
83.什么是线程和进程?它们之间的区别是什么?
一个程序只有一个进程一个进程包含多个线程(必须有一个主线程)
进程( Process ):进程是操作系统分配资源的基本单位,拥有独立的内存空间。进程之间通信较为复杂,需要采用进程间通信( IPC )机制,如管道、消息队列、共享内存等。进程的切换开销相对较大。线程( Thread ):线程是进程的一部分,共享进程的内存空间。线程之间通信相对简单,可以直接通过共享内存进行通信。线程的切换开销相对较小
84.请解释一下线程的几种状态以及状态之间的转换关系
线程五种状态创建状态: new Thread 时就绪状态:调用 start() 方法运行状态:执行 run() 方法阻塞状态:调用 sleep , join 会进入阻碍状态,恢复后改为就绪状态 --> 运行状态死亡状态: run() 运行结束
85.什么是线程上下文切换?
线程的上下文是指线程在某一时刻的执行状态,包括寄存器状态、程序计数器(Program Counter,PC),栈指针,堆栈内容,以及相关的内核资源信息(如文件描述符、内存映射等)。在一个多线程环境中,每个线程都有自己独立的上下文。
寄存器状态:CPU的寄存器保存了线程执行的中间数据、地址、指令计数等。
程序计数器:指示当前线程将要执行的下一条指令的位置。
栈指针和堆栈内容:线程的调用栈保存了函数调用的状态,包括局部变量、返回地址等
86.项目中是否用到过多线程?怎么用的?
在现代软件开发中,使用多线程是很常见的,尤其是在需要提高程序性能、实现并发处理或者响应式编程时。具体应用场景包括但不限于:后台任务处理 :如文件上传下载、数据同步等。网络请求 :避免主线程被阻塞,提升用户体验。定时任务 :执行周期性或延迟性的任务。并行计算 :加速计算密集型任务的处理速度。例如,在 Web 应用中,可以通过创建线程池来管理多个线程,以异步方式处理用户请求,从而提高系统的响应速度和吞吐量。
87.Sleep()和wait()区别?Start()和run()区别?wait,sleep, notify的区别?
Thread.sleep() vs Object.wait() :sleep() 是静态方法,属于 Thread 类,它会使当前线程暂停执行指定的时间,但不会释放任何锁。wait() 是实例方法,必须在同步上下文中调用(即在 synchronized 块或方法内),它会释放对象上的锁,并使当前线程等待直到其他线程调用 notify() 或 notifyAll() 方法唤醒它。start() vs run() :start() 方法用于启动一个新线程,在 JVM 中创建一个新的线程并执行该线程的 run() 方法。直接调用 run() 方法不会创建新线程,而是在当前线程中执行 run() 方法中的代码。wait , sleep , notify 的区别 :wait() 和 notify()/notifyAll() 需要在同步环境下调用,用于线程间的通信; sleep() 则只是让当前线程暂停一段时间而不涉及线程间通信。wait() 会释放锁,允许其他线程获取该锁; sleep() 不会释放已经持有的锁。
88.线程安全指的是什么?如何保障线程安全
线程安全 是指当多个线程同时访问共享资源时,能够正确地管理这些访问,确保数据的一致性和完整性。要保障线程安全,可以采取以下措施:使用同步机制 :如 synchronized 关键字、 ReentrantLock 等。原子变量 :利用 java.util.concurrent.atomic 包下的类,如 AtomicInteger 。不可变对象 :一旦创建就不能修改的对象,自然就是线程安全的。并发集合 :使用 java.util.concurrent 提供的线程安全集合,如 ConcurrentHashMap 。
89.线程锁有几种方式?使用的关键字?
线程锁主要有以下几种方式:synchronized 关键字 :可以用来修饰方法或代码块,提供最基本的同步机制。显式锁( Explicit Lock ) :如 ReentrantLock ,提供了比 synchronized 更灵活的锁定策略,支持公平锁、可中断锁等特性。读写锁( ReadWriteLock ) :如 ReentrantReadWriteLock ,允许多个读操作同时进行,但在写操作时独占资源。
90.什么是乐观锁和悲观锁?
悲观锁: 在事务开始前,通过数据库的锁机制锁定数据,使其他事务无法修改,事务结束后释放锁。悲观锁的代表是数据库中的行级锁和表级锁。
乐观锁: 假设并发冲突的概率很小,事务在读取数据时不加锁,而是在更新时检查数据是否被其他事务修改,如果被修改则回滚。乐观锁的代表是版本控制,通过版本号或时间戳进行控制。
91.synchronized和]ock的区别。Synchronize关键字修饰方法和修饰代码块的区别
synchronized vs Lock :
- synchronized 是Java语言内置的关键字,使用简单,自动管理锁的获取与释放(在退出同步方法或同步代码块时自动释放锁)。它不支持尝试获取锁、非阻塞获取锁、定时获取锁等功能。
- Lock 是一个接口,提供了比 synchronized 更灵活的锁定机制,比如可以尝试获取锁 ( tryLock() )、定时等待获取锁( tryLock(long time, TimeUnit unit) )、公平锁等。但是需要手动显式地获取和释放锁,容易忘记释放锁导致死锁等问题。
synchronized 修饰方法和修饰代码块的区别 :
- 当用于修饰方法时,整个方法体被视为同步区域,锁住的是当前实例对象(对于实例方法)或类本身(对于静态方法)。
- 当用于修饰代码块时,可以根据需要指定更小范围的同步区域,并且可以明确指定锁的对象(如某个特定对象),这有助于减少锁的粒度,提高并发性能
92.如何保证单例模式在多线程中的线程安全性
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public enum Singleton {
INSTANCE;
}
93.线程池的常见类型有哪些?
FixedThreadPool : 固定大小的线程池,适用于负载较稳定的情况。CachedThreadPool : 根据需求创建新线程,空闲线程会被回收,适合执行大量短生命周期的任务。SingleThreadExecutor : 只有一个工作线程执行任务,保证所有任务按照顺序执行。ScheduledThreadPool : 支持定时及周期性任务执行。WorkStealingPool : ( Java 8 新增)基于 “ 工作窃取 ” 算法的线程池,适用于多核处理器,能够高效处理大量小任务。
94.线程池中线程的停止?
线程池中的线程不能直接通过调用 Thread.stop() 方法来强制停止,因为这种方法是不安全的。正确的做法包括:调用 shutdown() :平滑关闭线程池,不再接受新的任务,但会完成已提交的任务。调用 shutdownNow() :尝试立即停止所有正在执行的任务,并返回等待执行的任务列表。对于每个任务,可以通过设置中断标志位或者在任务内部定期检查是否被取消来实现优雅退出
95.线程池中的线程数多少合适?
理想的线程池大小取决于多个因素,包括但不限于:任务性质:如果是 CPU 密集型任务,线程数一般设置为 CPU 核心数;如果是 IO密集型任务,则可以设置更多的线程数,因为这类任务大部分时间都在等待IO操作完成。系统资源限制:考虑可用内存、其他进程的影响等因素。任务队列长度:如果任务队列很长,可能需要增加线程数以加快处理速度;反之则可适当减少线程数。通常情况下,可以通过实验和监控来调整线程池大小,找到最佳配置。例如,对于 CPU 密集型任务,线程数可以设为 N ( CPU 核心数) +1 ,而对于 IO 密集型任务,可以设为 2*N 或更高。
96.线程池一般是自动创建的还是手动?为什么?
线程池一般是手动创建的。手动创建线程池(使用ThreadPoolExecutor
)比自动创建(使用Executors
工具类)更为推荐,原因如下:
- 控制能力和灵活性:手动创建线程池(使用
ThreadPoolExecutor
)可以让我们更精确地控制线程池的行为。通过设置核心线程数、最大线程数、存活时间、工作队列等参数,可以更好地管理线程池的资源,避免资源耗尽的风险。 - 避免资源耗尽:自动创建的线程池(如
FixedThreadPool
和CachedThreadPool
)可能会导致资源耗尽的问题。例如,CachedThreadPool
不限制线程数量,当任务量非常大时,会创建大量线程,可能导致内存溢出。 - 任务执行策略:手动创建的线程池可以通过参数设置拒绝策略,处理无法处理的任务,而自动创建的线程池可能默认采用不合适的拒绝策略,导致任务丢失或程序崩溃
97.ReentrantLock和synchronized的别是什么
98.什么是死锁?如何避免死锁?
死锁: 死锁是指两个或多个线程在执行过程中因争夺资源而造成的一种互相等待的现象,导致所有参与的线程无法继续执行。
避免死锁的方法:
加锁顺序: 约定所有线程按照相同的顺序获取锁,降低死锁的概率。
加锁时限: 设置每个锁的获取时限,超过时限则放弃锁。
死锁检测: 定期检测死锁,主动回滚某些事务,释放资源。
使用Lock: 使用java.util.concurrent.locks包中的Lock接口及其实现类,具有更灵活的加锁和释放锁的方式,以及更好的死锁避免机制
99.Java 中线程间通信有哪些方式
在Java中,线程之间的通信主要通过共享内存和线程同步机制来实现。常见的线程通信方式包括使用共享变量、wait和notify/notifyAll方法、Condition接口以及BlockingQueue。共享变量通过同步机制保护访问,wait和notify/notifyAll通过等待和通知机制实现通信,Condition接口提供更灵活的等待和通知机制,而BlockingQueue通过线程安全的队列实现生产者-消费者模式
1. 共享变量
最简单的线程通信方式是通过共享变量。多个线程可以访问和修改同一个变量,从而实现信息的传递。为了确保线程安全,通常需要使用同步机制(如
synchronized
关键字或Lock
接口)来保护共享变量的访问。2.
wait
和notify
/notifyAll
wait
、notify
和notifyAll
方法是Object
类的方法,用于线程之间的等待和通知机制。一个线程调用wait
方法后会进入等待状态,直到另一个线程调用相同对象的notify
或notifyAll
方法来唤醒它。3.
Condition
接口
Condition
接口是java.util.concurrent.locks
包中的一个接口,提供了比wait
和notify
更灵活的线程通信机制。它通常与ReentrantLock
一起使用。总结
共享变量:通过同步机制保护共享变量的访问,实现线程之间的通信。
wait 和 notify/notifyAll:通过等待和通知机制实现线程之间的通信。
Condition 接口:提供更灵活的等待和通知机制,通常与ReentrantLock一起使用。
BlockingQueue:通过线程安全的队列实现生产者-消费者模式,实现线程之间的通信