Java高频面试题合集(全网最新)

一、Java 基础
1. 请简述 Java 中多态的实现方式

答案解析: Java 中多态主要通过继承、接口和方法重写来实现。

  • 继承:子类继承父类,并重写父类的方法。当使用父类引用指向子类对象时,调用相同的方法会根据实际的子类对象执行不同的逻辑。例如:
 
  1. class Animal {

  2. public void makeSound() {

  3. System.out.println("Animal makes a sound");

  4. }

  5. }

  6. class Dog extends Animal {

  7. @Override

  8. public void makeSound() {

  9. System.out.println("Dog barks");

  10. }

  11. }

  12. public class Main {

  13. public static void main(String[] args) {

  14. Animal animal = new Dog();

  15. animal.makeSound();

  16. }

  17. }

AI写代码

  • 接口:一个类可以实现多个接口,通过接口引用指向实现类的对象,调用接口中定义的方法,实现不同的行为。例如:
 
  1. interface Shape {

  2. double area();

  3. }

  4. class Circle implements Shape {

  5. private double radius;

  6. public Circle(double radius) {

  7. this.radius = radius;

  8. }

  9. @Override

  10. public double area() {

  11. return Math.PI * radius * radius;

  12. }

  13. }

  14. class Rectangle implements Shape {

  15. private double length;

  16. private double width;

  17. public Rectangle(double length, double width) {

  18. this.length = length;

  19. this.width = width;

  20. }

  21. @Override

  22. public double area() {

  23. return length * width;

  24. }

  25. }

  26. public class Main {

  27. public static void main(String[] args) {

  28. Shape circle = new Circle(5);

  29. Shape rectangle = new Rectangle(4, 6);

  30. System.out.println(circle.area());

  31. System.out.println(rectangle.area());

  32. }

  33. }

AI写代码

篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记的【点击此处即可】即可免费获取

2. 解释 Java 中的静态绑定和动态绑定

答案解析

  • 静态绑定:也称为前期绑定,在编译时就确定了方法的调用。主要用于方法重载和静态方法调用。例如,对于方法重载,编译器根据方法调用时传递的参数类型和数量来确定具体调用哪个方法。
 
  1. class Calculator {

  2. public static int add(int a, int b) {

  3. return a + b;

  4. }

  5. public static double add(double a, double b) {

  6. return a + b;

  7. }

  8. public static void main(String[] args) {

  9. int result1 = add(1, 2);

  10. double result2 = add(1.5, 2.5);

  11. }

  12. }

AI写代码

  • 动态绑定:也称为后期绑定,在运行时根据对象的实际类型来确定要调用的方法。主要用于方法重写。如前面多态的例子中,Animal animal = new Dog(); animal.makeSound(); 这里在运行时根据 animal 实际指向的 Dog 对象来调用 Dog 类重写后的 makeSound 方法。
3. Java 中如何实现浅拷贝和深拷贝

答案解析

  • 浅拷贝:创建一个新对象,新对象的属性和原对象相同,但对于引用类型的属性,新对象和原对象共享同一个引用。在 Java 中,实现浅拷贝可以通过实现 Cloneable 接口并重写 clone 方法。
 
  1. class Address {

  2. String street;

  3. public Address(String street) {

  4. this.street = street;

  5. }

  6. }

  7. class Person implements Cloneable {

  8. String name;

  9. Address address;

  10. public Person(String name, Address address) {

  11. this.name = name;

  12. this.address = address;

  13. }

  14. @Override

  15. protected Object clone() throws CloneNotSupportedException {

  16. return super.clone();

  17. }

  18. }

  19. public class Main {

  20. public static void main(String[] args) throws CloneNotSupportedException {

  21. Address address = new Address("123 Main St");

  22. Person person1 = new Person("Alice", address);

  23. Person person2 = (Person) person1.clone();

  24. person2.address.street = "456 Elm St";

  25. System.out.println(person1.address.street);

  26. }

  27. }

AI写代码

  • 深拷贝:创建一个新对象,新对象的属性和原对象相同,对于引用类型的属性,会递归地创建新的对象。可以通过序列化和反序列化来实现深拷贝。
 
  1. import java.io.*;

  2. class Address implements Serializable {

  3. String street;

  4. public Address(String street) {

  5. this.street = street;

  6. }

  7. }

  8. class Person implements Serializable {

  9. String name;

  10. Address address;

  11. public Person(String name, Address address) {

  12. this.name = name;

  13. this.address = address;

  14. }

  15. public Person deepClone() throws IOException, ClassNotFoundException {

  16. ByteArrayOutputStream bos = new ByteArrayOutputStream();

  17. ObjectOutputStream oos = new ObjectOutputStream(bos);

  18. oos.writeObject(this);

  19. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

  20. ObjectInputStream ois = new ObjectInputStream(bis);

  21. return (Person) ois.readObject();

  22. }

  23. }

  24. public class Main {

  25. public static void main(String[] args) throws IOException, ClassNotFoundException {

  26. Address address = new Address("123 Main St");

  27. Person person1 = new Person("Alice", address);

  28. Person person2 = person1.deepClone();

  29. person2.address.street = "456 Elm St";

  30. System.out.println(person1.address.street);

  31. }

  32. }

AI写代码

二、集合框架
4. 比较 ArrayList 和 LinkedList 的优缺点

答案解析

  • ArrayList
    • 优点
      • 支持随机访问,通过索引可以快速访问元素,时间复杂度为 O(1)O(1)。
      • 内存空间连续,相对紧凑,节省空间。
    • 缺点
      • 插入和删除元素效率较低,尤其是在中间位置,需要移动大量元素,时间复杂度为 O(n)O(n)。
      • 扩容时需要重新分配内存并复制元素,有一定的性能开销。
  • LinkedList
    • 优点
      • 插入和删除元素效率高,只需要修改指针,时间复杂度为 O(1)O(1)(如果已知插入或删除位置)。
      • 不需要连续的内存空间,适合频繁插入和删除的场景。
    • 缺点
      • 不支持随机访问,访问元素需要从头或尾开始遍历,时间复杂度为 O(n)O(n)。
      • 每个节点需要额外的指针来指向前一个和后一个节点,占用更多的内存空间。
        5. HashMap 在 JDK 1.8 中有哪些优化

        答案解析

      • 数据结构优化:JDK 1.8 之前 HashMap 采用数组 + 链表的结构,当链表过长时,查找效率会降低。JDK 1.8 引入了红黑树,当链表长度大于 8 且数组长度大于 64 时,链表会转换为红黑树,将查找、插入和删除操作的时间复杂度从 O(n)O(n) 降低到 O(logn)O(logn)。
      • 哈希函数优化:对哈希函数进行了优化,通过 (h = key.hashCode()) ^ (h >>> 16) 使得哈希值的分布更加均匀,减少了哈希冲突的发生。
      • 扩容机制优化:在扩容时,JDK 1.8 采用了更高效的方式,不需要重新计算每个元素的哈希值,而是根据原哈希值的某一位是 0 还是 1 来决定元素在新数组中的位置,减少了元素移动的次数。
      • 6. 如何保证 HashMap 在多线程环境下的线程安全

        答案解析

      • 使用 HashtableHashtable 是线程安全的,它的所有方法都使用 synchronized 关键字进行同步,保证了在同一时刻只有一个线程可以访问 Hashtable。但由于它的同步粒度较大,会影响性能。
      •  
         
              
        1. import java.util.Hashtable;

        2. public class Main {

        3. public static void main(String[] args) {

        4. Hashtable<String, Integer> hashtable = new Hashtable<>();

        5. hashtable.put("one", 1);

        6. int value = hashtable.get("one");

        7. }

        8. }

        AI写代码

      • 使用 Collections.synchronizedMap:可以将一个非线程安全的 HashMap 转换为线程安全的 Map
      •  
         
              
        1. import java.util.Collections;

        2. import java.util.HashMap;

        3. import java.util.Map;

        4. public class Main {

        5. public static void main(String[] args) {

        6. Map<String, Integer> hashMap = new HashMap<>();

        7. Map<String, Integer> synchronizedMap = Collections.synchronizedMap(hashMap);

        8. synchronizedMap.put("one", 1);

        9. int value = synchronizedMap.get("one");

        10. }

        11. }

        AI写代码

      • 使用 ConcurrentHashMapConcurrentHashMap 是 Java 并发包中的线程安全的 Map 实现,它采用分段锁(JDK 1.7)或 CAS + synchronized(JDK 1.8)机制,在保证线程安全的同时,提高了并发性能。
      •  
              
        1. import java.util.concurrent.ConcurrentHashMap;

        2. public class Main {

        3. public static void main(String[] args) {

        4. ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();

        5. concurrentHashMap.put("one", 1);

        6. int value = concurrentHashMap.get("one");

        7. }

        8. }

         篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

        需要全套面试笔记的【点击此处即可】即可免费获取

      • 三、多线程与并发
        7. 简述 Thread 和 Runnable 的区别及使用场景

        答案解析

      • 区别
        • Thread 是一个类,Runnable 是一个接口。
        • 继承 Thread 类的方式,由于 Java 是单继承的,会限制类的扩展性;而实现 Runnable 接口的方式,一个类可以同时实现多个接口,扩展性更好。
      • 使用场景
        • 当一个类需要继承其他类,同时又要实现多线程功能时,使用 Runnable 接口。
        • 当一个类不需要继承其他类,且实现多线程逻辑相对简单时,可以继承 Thread 类。
      •  
              
        1. // 继承 Thread 类

        2. class MyThread extends Thread {

        3. @Override

        4. public void run() {

        5. System.out.println("Running in MyThread");

        6. }

        7. }

        8. // 实现 Runnable 接口

        9. class MyRunnable implements Runnable {

        10. @Override

        11. public void run() {

        12. System.out.println("Running in MyRunnable");

        13. }

        14. }

        15. public class Main {

        16. public static void main(String[] args) {

        17. MyThread myThread = new MyThread();

        18. myThread.start();

        19. MyRunnable myRunnable = new MyRunnable();

        20. Thread thread = new Thread(myRunnable);

        21. thread.start();

        22. }

        23. }

        AI写代码

        8. 解释 volatile 关键字的作用和原理

        答案解析

      • 作用
        • 保证可见性:当一个变量被声明为 volatile 时,它会保证对该变量的写操作会立即刷新到主内存中,读操作会从主内存中读取最新的值。这样可以保证在多线程环境下,一个线程对 volatile 变量的修改能够及时被其他线程看到。
        • 禁止指令重排序volatile 关键字会在指令序列中插入内存屏障,防止编译器和处理器对指令进行重排序,保证代码的执行顺序符合程序员的预期。
      • 原理volatile 关键字底层是通过内存屏障来实现的。在写操作时,会插入一个写屏障,将缓存中的数据刷新到主内存;在读操作时,会插入一个读屏障,从主内存中读取最新的数据。例如:
      •  
              
        1. class VolatileExample {

        2. private volatile boolean flag = false;

        3. public void writer() {

        4. flag = true;

        5. }

        6. public void reader() {

        7. if (flag) {

        8. // 这里可以保证读取到的 flag 是最新值

        9. }

        10. }

        11. }

        AI写代码

        9. 如何使用 CountDownLatch 实现线程同步

        答案解析: CountDownLatch 是 Java 并发包中的一个同步工具类,它可以让一个或多个线程等待其他线程完成操作后再继续执行。

         
              
        1. import java.util.concurrent.CountDownLatch;

        2. public class Main {

        3. public static void main(String[] args) throws InterruptedException {

        4. int threadCount = 3;

        5. CountDownLatch latch = new CountDownLatch(threadCount);

        6. for (int i = 0; i < threadCount; i++) {

        7. new Thread(() -> {

        8. try {

        9. System.out.println(Thread.currentThread().getName() + " is working");

        10. Thread.sleep(1000);

        11. } catch (InterruptedException e) {

        12. e.printStackTrace();

        13. } finally {

        14. latch.countDown();

        15. }

        16. }).start();

        17. }

        18. latch.await();

        19. System.out.println("All threads have finished working");

        20. }

        21. }

        AI写代码

         

        在上述代码中,CountDownLatch 的初始计数为 3,表示需要等待 3 个线程完成操作。每个线程完成工作后调用 countDown 方法将计数减 1,主线程调用 await 方法会阻塞,直到计数变为 0 才会继续执行。

        10. 简述 ReentrantLock 的可重入性和公平锁、非公平锁的区别

        答案解析

      • 可重入性ReentrantLock 是可重入锁,即同一个线程可以多次获取同一把锁,而不会造成死锁。每次获取锁时,锁的计数器会加 1,每次释放锁时,计数器会减 1,当计数器为 0 时,锁才会真正被释放。
      •  
         
              
        1. import java.util.concurrent.locks.ReentrantLock;

        2. public class Main {

        3. private static ReentrantLock lock = new ReentrantLock();

        4. public static void main(String[] args) {

        5. lock.lock();

        6. try {

        7. System.out.println("First acquire the lock");

        8. lock.lock();

        9. try {

        10. System.out.println("Acquire the lock again");

        11. } finally {

        12. lock.unlock();

        13. }

        14. } finally {

        15. lock.unlock();

        16. }

        17. }

        18. }

        AI写代码

      • 公平锁和非公平锁的区别
        • 公平锁:线程会按照请求锁的顺序依次获取锁,即先来先得。创建 ReentrantLock 时,通过 new ReentrantLock(true) 可以创建公平锁。公平锁可以避免线程饥饿问题,但会降低系统的吞吐量。
        • 非公平锁:线程在请求锁时,会直接尝试获取锁,而不考虑请求的顺序。如果锁刚好处于可用状态,即使有其他线程在等待,该线程也可以立即获取锁。通过 new ReentrantLock(false) 或默认构造函数 new ReentrantLock() 创建的是非公平锁。非公平锁的吞吐量较高,但可能会导致某些线程长时间得不到锁。
          四、JVM
          11. 简述 JVM 的内存结构

          答案解析
          JVM 内存结构主要分为以下几个部分:

        • 程序计数器:是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,它是线程私有的。
        • Java 虚拟机栈:也是线程私有的,它描述的是 Java 方法执行的内存模型。每个方法在执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的调用和返回对应着栈帧的入栈和出栈操作。
        • 本地方法栈:与 Java 虚拟机栈类似,不过它是为本地方法服务的。
        • Java 堆:是 JVM 中最大的一块内存区域,所有对象实例和数组都在堆上分配内存。堆是线程共享的,是垃圾回收的主要区域。根据对象的存活周期,堆又可以分为新生代和老年代。
        • 方法区:也是线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在 JDK 1.8 及以后,方法区被元空间(Metaspace)所取代,元空间使用的是本地内存。
        • 12. 常见的垃圾回收算法有哪些,各有什么优缺点

          答案解析

        • 标记 - 清除算法
          • 原理:先标记出所有需要回收的对象,然后在标记完成后统一回收所有被标记的对象。
          • 优点:实现简单,不需要移动对象。
          • 缺点:会产生大量的内存碎片,可能导致后续大对象无法分配到连续的内存空间。
        • 复制算法
          • 原理:将内存分为大小相等的两块,每次只使用其中一块。当这一块内存用完时,将存活的对象复制到另一块,然后清除当前这块内存。
          • 优点:不会产生内存碎片,回收效率高。
          • 缺点:内存利用率低,只能使用一半的内存空间。
        • 标记 - 整理算法
          • 原理:先标记出所有需要回收的对象,然后将存活的对象向一端移动,最后清理掉边界以外的内存。
          • 优点:不会产生内存碎片,适合用于老年代。
          • 缺点:需要移动对象,效率相对较低。
        • 分代收集算法
          • 原理:根据对象的存活周期将内存分为新生代和老年代。新生代中对象存活率低,使用复制算法;老年代中对象存活率高,使用标记 - 清除或标记 - 整理算法。
          • 优点:结合了不同算法的优点,提高了垃圾回收的效率。
          • 缺点:需要对内存进行分区管理,增加了管理的复杂度。
        •  篇幅限制下面就只能给大家展示小册部分内容了。这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

          需要全套面试笔记的即可免费获取

        • 13. 如何进行 JVM 性能调优
        • 答案解析

        • 调整堆内存大小:通过 -Xms 和 -Xmx 参数分别设置堆内存的初始大小和最大大小,避免频繁的垃圾回收。例如,-Xms512m -Xmx512m 表示将堆内存的初始大小和最大大小都设置为 512MB。
        • 调整新生代和老年代的比例:使用 -Xmn 参数设置新生代的大小,或者使用 -XX:SurvivorRatio 参数调整新生代中 Eden 区和 Survivor 区的比例。例如,-Xmn256m 表示将新生代大小设置为 256MB,-XX:SurvivorRatio=8 表示 Eden 区和一个 Survivor 区的比例为 8:1。
        • 选择合适的垃圾收集器:根据应用的特点和性能需求选择合适的垃圾收集器。例如,对于吞吐量要求较高的应用,可以选择 Parallel Scavenge 收集器;对于响应时间要求较高的应用,可以选择 CMS 或 G1 收集器。
        • 分析 GC 日志:通过 -XX:+PrintGCDetails-XX:+PrintGCDateStamps 等参数开启 GC 日志记录,分析 GC 日志可以了解垃圾回收的频率、停顿时间等信息,从而找出性能瓶颈并进行优化。
        • 14. 简述类加载的过程

          答案解析
          类加载的过程主要分为以下几个阶段:

        • 加载:通过类的全限定名获取定义该类的二进制字节流,将字节流所代表的静态存储结构转化为方法区的运行时数据结构,并在内存中生成一个代表该类的 java.lang.Class 对象。
        • 验证:确保被加载的类的字节码符合 JVM 的规范,不会危害 JVM 的安全。验证过程包括文件格式验证、元数据验证、字节码验证和符号引用验证等。
        • 准备:为类的静态变量分配内存,并将其初始化为默认值。例如,int 类型的静态变量会被初始化为 0,boolean 类型的静态变量会被初始化为 false 等。
        • 解析:将常量池中的符号引用替换为直接引用的过程。符号引用是一种以一组符号来描述所引用的目标,而直接引用是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。
        • 初始化:执行类的初始化代码,包括静态变量的赋值和静态代码块的执行。初始化过程是类加载的最后一个阶段,只有在需要使用该类时才会进行初始化。
        • 15. 什么是双亲委派模型,有什么作用

          答案解析

        • 双亲委派模型:当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都会传送到顶层的启动类加载器中。只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
        • 作用
          • 避免类的重复加载:通过双亲委派模型,同一个类只会被加载一次,保证了类的唯一性。
          • 保证 Java 核心类库的安全性:Java 的核心类库由启动类加载器加载,用户自定义的类加载器无法加载这些核心类,防止了恶意代码对核心类库的篡改。
        • 五、数据库(以 MySQL 为例)
          16. 简述索引的作用和原理

          答案解析

        • 作用:索引可以提高数据库查询的效率,减少数据库的查询时间。通过在表的列上创建索引,数据库可以快速定位到满足查询条件的数据行,避免全表扫描。
        • 原理:常见的索引数据结构是 B+ 树。B+ 树是一种平衡的多路搜索树,它将数据按照一定的顺序组织成树状结构。叶子节点包含了数据的指针,通过对树的遍历可以快速找到目标数据。在 B+ 树中,所有的数据都存储在叶子节点上,并且叶子节点之间通过指针相连,形成一个有序的链表,方便进行范围查询。例如,在一个用户表中,对 username 列创建索引,当执行 SELECT * FROM users WHERE username = 'John'; 时,数据库可以通过 B+ 树索引快速定位到 username 为 John 的数据行。
        • 17. 解释数据库事务的 ACID 特性

          答案解析

        • 原子性(Atomicity):事务是一个不可分割的操作单元,事务中的所有操作要么全部成功,要么全部失败。如果事务中的某个操作失败,整个事务会回滚到初始状态。例如,在银行转账操作中,从一个账户扣款和向另一个账户存款这两个操作必须作为一个事务来执行,要么都成功,要么都失败。
        • 一致性(Consistency):事务执行前后,数据库的状态必须保持一致。也就是说,事务的执行不会破坏数据库的完整性约束。例如,在一个账户表中,所有账户的余额总和应该是一个固定的值,任何转账操作都不能改变这个总和。
        • 隔离性(Isolation):事务之间应该相互隔离,一个事务的执行不应该受到其他事务的干扰。不同的隔离级别规定了事务之间的隔离程度,常见的隔离级别有读未提交、读已提交、可重复读和串行化。
        • 持久性(Durability):一旦事务提交,它对数据库的修改应该是永久性的,即使数据库发生故障也不会丢失。通常通过日志文件来保证事务的持久性,当数据库发生故障时,可以通过日志文件进行恢复。
        • 18. 如何优化 SQL 查询语句

          答案解析

        • 合理使用索引:在经常用于查询条件、排序和连接的列上创建索引,但要注意避免创建过多索引,因为索引会占用额外的存储空间,并且在插入、更新和删除操作时会影响性能。
        • 避免在索引列上使用函数或表达式:例如,SELECT * FROM users WHERE YEAR(created_at) = 2023; 这样的查询会导致索引失效,应该尽量避免。可以改为 SELECT * FROM users WHERE created_at >= '2023-01-01' AND created_at < '2024-01-01';
        • 优化子查询:尽量使用连接查询代替子查询,因为子查询的性能通常较低。例如,SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customers WHERE country = 'USA'); 可以改为 SELECT o.* FROM orders o JOIN customers c ON o.customer_id = c.id WHERE c.country = 'USA';
        • 分页查询优化:对于大偏移量的分页查询,可以采用延迟关联的方式,先通过子查询获取需要查询的行的主键,然后再根据主键关联查询出具体的数据。例如,SELECT * FROM users LIMIT 10000, 10; 可以改为 SELECT * FROM users WHERE id IN (SELECT id FROM users LIMIT 10000, 10);
        • 19. 简述 MySQL 的主从复制原理

          答案解析
          MySQL 的主从复制主要分为以下几个步骤:

        • 主库记录二进制日志:主库上的所有写操作都会记录到二进制日志(Binary Log)中,二进制日志包含了对数据库的所有修改信息。
        • 从库连接主库并请求日志:从库通过 I/O 线程连接到主库,并请求主库发送二进制日志。
        • 主库发送日志:主库接收到从库的请求后,通过 Binlog Dump 线程将二进制日志发送给从库。
        • 从库接收并保存日志:从库的 I/O 线程将接收到的二进制日志保存到本地的中继日志(Relay Log)中。
        • 从库执行日志中的操作:从库的 SQL 线程读取中继日志中的内容,并在从库上执行相应的 SQL 语句,从而实现主从数据的同步。
        • 20. 如何处理 MySQL 中的死锁问题

          答案解析

        • 合理设计事务:尽量缩短事务的执行时间,减少事务持有锁的时间。例如,将大事务拆分成多个小事务,避免长时间占用锁资源。
        • 优化查询语句:通过合理使用索引,减少全表扫描,降低锁的竞争。例如,在查询时使用合适的索引,避免使用范围查询时产生间隙锁。
        • 调整事务隔离级别:不同的事务隔离级别会影响锁的使用和并发性能。可以根据业务需求选择合适的隔离级别,例如,将隔离级别从可重复读调整为读已提交,减少锁的持有时间。
        • 检测和处理死锁:MySQL 会自动检测死锁,并回滚其中一个事务来解除死锁。可以通过 SHOW ENGINE INNODB STATUS; 命令查看最近一次死锁的详细信息,分析死锁的原因并进行优化。
        • 六、Spring 框架
          21. 简述 Spring 的 IoC(控制反转)和 DI(依赖注入)

          答案解析

        • IoC(控制反转):是一种设计原则,它将对象的创建和依赖关系的管理从应用程序代码中转移到 Spring 容器中。在传统的编程方式中,对象的创建和依赖关系的管理是由应用程序代码自己负责的;而在 IoC 模式下,对象的创建和依赖关系的管理由 Spring 容器负责,应用程序只需要从容器中获取所需的对象即可。
        • DI(依赖注入):是 IoC 的一种具体实现方式,它通过在对象创建时将其依赖的对象注入到该对象中,实现对象之间的解耦。依赖注入可以通过构造函数注入、Setter 方法注入和接口注入等方式实现。例如:
        •  
           
                  
          1. import org.springframework.beans.factory.annotation.Autowired;

          2. import org.springframework.stereotype.Component;

          3. @Component

          4. class UserService {

          5. private UserRepository userRepository;

          6. @Autowired

          7. public UserService(UserRepository userRepository) {

          8. this.userRepository = userRepository;

          9. }

          10. public void saveUser() {

          11. userRepository.save();

          12. }

          13. }

          14. @Component

          15. class UserRepository {

          16. public void save() {

          17. System.out.println("Saving user...");

          18. }

          19. }

          AI写代码

           

          在上述代码中,UserService 依赖于 UserRepository,通过构造函数注入的方式将 UserRepository 注入到 UserService 中。

          22. 解释 Spring 的 AOP(面向切面编程)及其应用场景

          答案解析

        • AOP(面向切面编程):是一种编程范式,它允许在不修改目标对象代码的情况下,对方法的执行进行增强。AOP 通过将横切关注点(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,提高了代码的可维护性和可扩展性。
        • 应用场景
          • 日志记录:在方法执行前后记录日志,方便调试和监控。
          • 事务管理:在方法执行前后进行事务的开启、提交和回滚操作。
          • 权限控制:在方法执行前检查用户的权限,只有具有相应权限的用户才能执行该方法。
          • 性能监控:记录方法的执行时间,分析方法的性能瓶颈。
        • 23. Spring Bean 的生命周期是怎样的

          答案解析
          Spring Bean 的生命周期主要包括以下几个阶段:

        • 实例化:Spring 容器通过反射机制创建 Bean 的实例。
        • 属性注入:将配置文件或注解中定义的属性值注入到 Bean 中。
        • 初始化:如果 Bean 实现了 InitializingBean 接口,会调用其 afterPropertiesSet 方法;如果配置了 init - method,会调用该方法进行初始化操作。
        • 使用:Bean 可以被应用程序使用。
        • 销毁:当 Bean 不再需要时,Spring 容器会销毁该 Bean。如果 Bean 实现了 DisposableBean 接口,会调用其 destroy 方法;如果配置了 destroy - method,会调用该方法进行销毁操作。
        • 24. 如何在 Spring 中实现事务管理

          答案解析
          在 Spring 中可以通过编程式事务管理和声明式事务管理两种方式来实现事务管理。

        • 编程式事务管理:通过编写代码来管理事务的开启、提交和回滚。例如:
        •  
           
                  
          1. import org.springframework.beans.factory.annotation.Autowired;

          2. import org.springframework.jdbc.datasource.DataSourceTransactionManager;

          3. import org.springframework.stereotype.Service;

          4. import org.springframework.transaction.TransactionDefinition;

          5. import org.springframework.transaction.TransactionStatus;

          6. import org.springframework.transaction.support.DefaultTransactionDefinition;

          7. @Service

          8. class UserService {

          9. @Autowired

          10. private DataSourceTransactionManager transactionManager;

          11. public void saveUser() {

          12. TransactionDefinition def = new DefaultTransactionDefinition();

          13. TransactionStatus status = transactionManager.getTransaction(def);

          14. try {

          15. // 业务逻辑

          16. transactionManager.commit(status);

          17. } catch (Exception e) {

          18. transactionManager.rollback(status);

          19. }

          20. }

          21. }

          AI写代码

        • 声明式事务管理:通过注解或 XML 配置来管理事务。使用注解方式时,在需要进行事务管理的方法上添加 @Transactional 注解。例如:
        •  
                  
          1. import org.springframework.stereotype.Service;

          2. import org.springframework.transaction.annotation.Transactional;

          3. @Service

          4. class UserService {

          5. @Transactional

          6. public void saveUser() {

          7. // 业务逻辑

          8. }

          9. }

          AI写代码

          25. 简述 Spring MVC 的工作流程

          答案解析
          Spring MVC 的工作流程如下:

        • 客户端发送请求:客户端向服务器发送 HTTP 请求。
        • 请求到达 DispatcherServletDispatcherServlet 是 Spring MVC 的核心控制器,它接收所有的请求。
        • 查找 HandlerMappingDispatcherServlet 根据请求的 URL 查找对应的 HandlerMappingHandlerMapping 会返回处理该请求的 Handler(通常是一个 Controller 类的方法)。
        • 调用 HandlerAdapterDispatcherServlet 调用 HandlerAdapter 来执行 HandlerHandlerAdapter 负责将请求参数绑定到 Handler 的方法上,并调用该方法。
        • 执行 HandlerHandler 执行相应的业务逻辑,并返回一个 ModelAndView 对象,包含了视图名称和模型数据。
        • 查找 ViewResolverDispatcherServlet 根据视图名称查找对应的 ViewResolverViewResolver 会返回具体的 View 对象。
        • 渲染视图DispatcherServlet 将模型数据填充到 View 中,并将渲染后的结果返回给客户端。
        • 七、MyBatis
          26. 简述 MyBatis 的工作原理

          答案解析
          MyBatis 的工作原理主要包括以下几个步骤:

        • 读取配置文件:MyBatis 首先读取配置文件(如 mybatis - config.xml)和映射文件(如 UserMapper.xml),配置文件中包含了数据库连接信息、插件配置等,映射文件中定义了 SQL 语句和 Java 对象之间的映射关系。
        • 创建 SqlSessionFactory:根据配置文件创建 SqlSessionFactory 对象,SqlSessionFactory 是 MyBatis 的核心工厂类,用于创建 SqlSession
        • 创建 SqlSession:通过 SqlSessionFactory 创建 SqlSession 对象,SqlSession 是与数据库交互的核心对象,它提供了执行 SQL 语句的方法。
        • 执行 SQL 语句:通过 SqlSession 调用映射文件中定义的 SQL 语句,将参数传递给 SQL 语句进行预编译,然后执行 SQL 语句并将结果映射到 Java 对象中返回。
        • 关闭 SqlSession:操作完成后,关闭 SqlSession 释放资源。
        • 27. MyBatis 中的一级缓存和二级缓存有什么区别

          答案解析

        • 一级缓存:是 SqlSession 级别的缓存,它存在于 SqlSession 内部。在同一个 SqlSession 中,对于相同的 SQL 语句和参数,只会执行一次查询,结果会缓存在一级缓存中,下次查询直接从缓存中获取。当 SqlSession 关闭时,一级缓存会被清空。
        • 二级缓存:是 Mapper 级别的缓存,它存在于 Mapper 实例中,多个 SqlSession 可以共享二级缓存。二级缓存需要手动开启,在映射文件中添加 <cache /> 标签即可。当一个 SqlSession 执行了插入、更新或删除操作后,会清空二级缓
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值