Java开发面试题汇总

JAVA后端开发工程师面试题

没答好部分

什么是面向对象编程?请举例说明一个类、对象、方法和属性的概念

答:面向对象编程(Object-Oriented Programming,OOP)是一种程序设计的方法,通过将问题拆分成对象并通过对象之间的交互来解决问题。在面向对象编程中,类是对象的蓝图,它定义了对象的属性和行为。对象是类的一个实例,它具有类定义的属性和行为。方法是类中定义的可以执行的操作,用于实现对象的行为。属性是类中定义的用于描述对象状态的变量。

举例:

  • 类:一个 “Person” 类可以定义人的属性(如姓名、年龄、性别)和行为(如说话、走路)
  • 对象:基于 “Person” 类创建的一个人对象,具有具体的姓名、年龄和性别
  • 方法:“Person” 类中的一个方法可以是 speak(),用于让人说话
  • 属性: “Person” 类中的一个属性可以是 name,用于表示人的姓名

编写一个程序,打印出从1到100的奇数序列并倒序排列

public class OddNumbers {
    public static void main(String[] args) {
        for (int i = 100; i >= 1; i--) {
            if (i % 2 != 0) {
                System.out.println(i);
            }
        }
    }
}

Java提供了哪些集合类?请列举并简单描述它们的用途

  • ArrayList:基于动态数组实现的可变长度列表。用于存储和操作元素的集合
  • LinkedList:基于链表实现的可变长度列表。适用于频繁的插入和删除操作
  • HashSet:基于哈希表实现的无序集合。用于存储唯一的元素
  • HashMap:基于哈希表实现的键值对映射。用于存储和检索键值对
  • TreeSet:基于红黑树实现的有序集合。用于存储有序的唯一元素

什么是线程?如何在Java中创建多线程?

线程是程序执行中的一个独立单位,它可以并发执行。在 Java 中,可以通过继承 Thread 类或实现 Runnable 接口来创建线程。下面是两种创建线程的示例:

使用 Thread 类:

public class MyThread extends Thread {
    public void run() {
        // 线程执行的代码
    }
}
// 创建并启动线程
MyThread thread = new MyThread();
thread.start();

使用 Runnable 接口:

public class MyRunnable implements Runnable {
    public void run() {
        // 线程执行的代码
    }
}
// 创建并启动线程
Thread thread = new Thread(new MyRunnable());
thread.start();

如何使用 Java编写一个简单的HTTP客户端和服务器

使用 Java 编写简单的 HTTP 客户端和服务器可以使用 java.net 包中的类。下面是一个简单的示例:

HTTP 客户端:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpClientExample {
    public static void main(String[] args) throws Exception {
        URL url = new URL("http://example.com");
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");

        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer content = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            content.append(inputLine);
        }
        in.close();

        System.out.println(content.toString());
    }
}

HTTP 服务器:

import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServerExample {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("Listening on port 8080...");

        while (true) {
            Socket clientSocket = serverSocket.accept();

            OutputStream out = clientSocket.getOutputStream();
            out.write("HTTP/1.1 200 OK\r\n\r\nHello, World!".getBytes());

            out.flush();
            out.close();
            clientSocket.close();
        }
    }
}

另外一个例子:
HTTP客户端代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpClientExample {
    public static void main(String[] args) {
        try {
            // 创建URL对象
            URL url = new URL("http://example.com");
            
            // 打开URL连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            
            // 设置请求方法
            connection.setRequestMethod("GET");
            
            // 发送请求并获取响应
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            
            // 读取响应内容
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            
            // 关闭连接
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

HTTP服务器代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServerExample {
    public static void main(String[] args) {
        try {
            // 创建ServerSocket对象并指定监听的端口号
            ServerSocket serverSocket = new ServerSocket(8080);
            
            while (true) {
                // 等待客户端连接
                Socket clientSocket = serverSocket.accept();
                
                // 获取客户端请求信息
                BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                
                // 解析请求信息并处理
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
                
                // 构造响应并发送给客户端
                PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
                writer.println("HTTP/1.1 200 OK");
                writer.println("Content-Type: text/html");
                writer.println();
                writer.println("<h1>Hello, World!</h1>");
                writer.flush();
                
                // 关闭连接
                reader.close();
                writer.close();
                clientSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

以上示例代码分别实现了一个简单的HTTP客户端和服务器。在客户端代码中,我们创建了一个URL对象,打开URL连接,并发送GET请求获取响应。在服务器代码中,我们创建了一个ServerSocket对象并指定监听的端口号,然后在一个无限循环中等待客户端连接,获取客户端请求信息并处理,然后构造响应并发送给客户端。

使用Java连接到数据库的基本步骤是什么?请给出一段简单的代码示例

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DatabaseExample {
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            // 加载数据库驱动程序
            Class.forName("com.mysql.jdbc.Driver");

            // 建立数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");

            // 创建 SQL 语句
            String sql = "SELECT * FROM mytable";

            // 创建 Statement 对象
            statement = connection.createStatement();

            // 执行查询并获取结果集
            resultSet = statement.executeQuery(sql);

            // 处理结果集
            while (resultSet.next()) {
                String column1 = resultSet.getString("column1");
                int column2 = resultSet.getInt("column2");
                // 处理获取到的数据
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭结果集
                if (resultSet != null) {
                    resultSet.close();
                }
                // 关闭语句
                if (statement != null) {
                    statement.close();
                }
                // 关闭连接
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

在Java中如何读写文件?请给出一个简单的文件读取和写入的代码示例**

在 Java 中,可以使用 java.io 包中的类来进行文件读写。下面是一个简单的文件读取和写入的示例:

文件读取:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文件写入:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterExample {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, world!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Java有哪些高级特性?例如,解释一下接口、抽象类、泛型和反射的概念

  • 接口(Interface)是一种定义了一组需要实现的方法的抽象类型。它定义了行为的契约,实现接口的类必须提供接口中定义的方法实现
  • 抽象类(Abstract Class)是一种不能被实例化的类,它可以包含抽象方法和具体方法。抽象类用于作为其他类的基类,并通过继承来共享通用的属性和方法
  • 泛型(Generics)是一种参数化类型的机制,它允许在编译时指定类或方法的类型。通过使用泛型,可以增加代码的类型安全性和重用性
  • 反射(Reflection)是一种在运行时检查和修改类、对象和方法的能力。它允许程序在运行时获取类型信息、访问和修改对象的属性和方法

如何在Java中实现MVC(模型-视图-控制器)设计模式?请给出一个简单的示例

在 Java 中实现 MVC(模型-视图-控制器)设计模式可以将代码分为三个主要部分:

  • 模型(Model)负责处理应用程序的数据逻辑和业务规则
  • 视图(View)负责显示用户界面,并与用户交互
  • 控制器(Controller)负责处理用户输入、更新模型和管理视图

以下是一个简单的示例:

Model(模型):

public class UserModel {
    private String username;
    private String password;

    // getters and setters
}

View(视图):

public class UserView {
    public void displayUserDetails(String username) {
        System.out.println("Username: " + username);
    }

    public String getUsernameFromUser() {
        // 获取用户输入的用户名
    }

    public String getPasswordFromUser() {
        // 获取用户输入的密码
    }
}

Controller(控制器):

public class UserController {
    private UserModel model;
    private UserView view;

    public UserController(UserModel model, UserView view) {
        this.model = model;
        this.view = view;
    }

    public void updateUser() {
        String username = view.getUsernameFromUser();
        String password = view.getPasswordFromUser();

        model.setUsername(username);
        model.setPassword(password);

        view.displayUserDetails(model.getUsername());
    }
}

主程序:

public class Main {
    public static void main(String[] args) {
        UserModel model = new UserModel();
        UserView view = new UserView();
        UserController controller = new UserController(model, view);

        controller.updateUser();
    }
}

http和https的区别

  • 信息传输方式不同:http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议
  • 连接方式不同:http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
  • 安全性不同:http连接是无状态的,https协议是由ssl+http协议构建的可进行加密传输、身份认证的网络协议,安全性高于http协议
  • 费用不同:https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用,http则没有这种费用

== 和 === 的区别

" == " 运算符用于比较两个对象的引用是否相等。也就是说,当两个变量指向内存中的同一个对象时,即两个变量引用同一个对象,那么使用"=="运算符比较它们会返回真。例如:
在这个例子中,a和b都是指向同一个新创建的Object对象的引用,所以a == b返回真

Object a = new Object();
Object b = a;
if (a == b) {
    System.out.println("a == b"); // 输出 "a == b"
}

“===” 运算符是Java 7中引入的严格比较运算符,它不仅比较两个对象的引用是否相等,还比较两个对象的类型是否相同。例如:

String s1 = "Hello";
String s2 = new String("Hello");
if (s1 === s2) {
    System.out.println("s1 === s2"); // 不会输出
}

在这个例子中,s1和s2虽然内容相同,但是它们在内存中的对象类型不同(一个是字面量字符串,一个是通过new关键字创建的字符串对象),所以s1 === s2返回假。

需要注意的是," === " 运算符只适用于原始类型和String类型,对于其他对象类型,Java虚拟机会自动将其转化为 " == " 运算符。所以在实际编程中," === “的使用并不多见,更多时候我们使用的还是” == "

MySQL分页查询的关键字

MySQL分页查询的关键字是LIMIT和OFFSET

  • LIMIT关键字用于限制查询结果集的返回行数,可以设置查询从哪里开始,以及返回多少行数据
  • OFFSET关键字用于指定结果集的偏移量,通常与LIMIT关键字一起使用,用于实现分页效果。

可以通过LIMIT和OFFSET的组合来实现分页查询,例如:

 SELECT * FROM table LIMIT offset, rows			//其中offset表示从哪里开始查询,rows表示要查询的行数。

左连接的结果集是怎样的

  • 左连接是以左表为主,结果集包含在左表的基础上加上左右表都匹配的共同部分
  • 右连接是以右表为主,结果集包含在右表的基础上加上左右表都匹配的共同部分
  • 内连接的结果集为左右表都匹配的中间部分

在这里插入图片描述

什么是值传递和引用传递以及区别?

值传递和引用传递是Java等编程语言中两种不同的参数传递方式

  • 值传递是指方法调用时,实际参数拷贝一份传递到方法中,这样在方法中如果对参数进行修改,将不会影响到实际参数
  • 引用传递是指方法调用时,实际参数的地址直接传递到方法中,那么在方法中对参数所进行的修改,将影响到实际参数

Mysql数据库和Oracle数据库的区别

  • MySQL是开源的数据库,而Oracle是商业数据库,需要购买许可证才能使用
  • Oracle在处理大规模企业级数据库时表现更好,具有更高的性能和可扩展性。MySQL则更适合小型和中型应用,处理较小规模的数据库
  • MySQL主要以表级锁为主,对资源锁定的粒度很大,Oracle使用行级锁,对资源锁定的粒度要小很多
  • MySQL没有表空间、角色管理、快照、同义词和包以及自动存储管理等特性

前端怎么获取一个标签里的属性,有哪些方法?

  • 使用 getAttribute()方法:该方法可以获取指定属性名的属性值
  • 使用元素对象的属性:例如,element.id可以获取标签的id属性值,element.className可以获取标签的class属性值
  • 使用dataset属性:该属性可以获取标签的data-*属性值,例如,element.dataset.name可以获取标签的data-name属性值
  • 使用属性选择器:可以使用CSS选择器来获取标签的属性值,例如,使用[attribute=value]选择器获取指定属性名和属性值的标签
  • 使用jQuery库:jQuery库提供了attr()方法来获取指定属性名的属性值,例如,$(element).attr(‘id’)可以获取标签的id属性值

vue中v-if和v-show的区别

  • v-show的作用与v-if一致,但区别在于v-if的html元素会消失在body中,而v-show只是隐藏,元素还在!因此在元素的隐藏和显示之间切换次数比较高的时候使用v-show会比较好

Mybatis-plus中怎么设置主键自动递增

  • 在实体类中,使用 @TableId 注解来设置主键,并指定 type 为 IdType.AUTO,表示自动递增
  • Mybatis-plus 的全局配置文件 mybatis-plus-config.xml 中,使用 GlobalConfig 配置对象来设置主键自动递增,例如:
<configuration>
    <global-config>
        <db-config>
            <!-- 主键自动递增 -->
            <key-generator type="com.baomidou.mybatisplus.incrementer.OracleKeyGenerator" />
        </db-config>
    </global-config>
</configuration>

MySQL中怎么查询除了左连接结果集后剩下的部分(也就是剩余的右表部分)

使用 NOT IN:

SELECT *
FROM right_table
WHERE right_table.id NOT IN (
    SELECT left_table.id
    FROM left_table
    LEFT JOIN right_table ON left_table.id = right_table.id
    WHERE left_table.id IS NULL
);

使用 NOT EXISTS:

SELECT *
FROM right_table
WHERE NOT EXISTS (
    SELECT 1
    FROM left_table
    LEFT JOIN right_table ON left_table.id = right_table.id
    WHERE left_table.id IS NULL
);

arraylist中间的一个元素删除后,后面的元素会自动向前补位吗?还是会怎么样?

  • 后面的元素会自动向前补位,之前的下标会重新排列,数组大小变小

在java8里面为什么要加个default(方法体)

  • 在Java 8中引入了接口的默认方法,主要是为了向现有的接口添加新的方法,而不会影响已有的实现类。在Java 8之前,一旦一个接口添加了新的方法,所有实现该接口的类都需要实现新方法,这可能会导致一些问题。通过在接口中添加默认方法,可以在不影响已有实现类的情况下,为接口添加新的方法。使得实现类可以选择性地覆盖默认方法或者直接使用默认实现。这样可以减少代码的重复性,并提高代码的可维护性和灵活性。因此,在Java 8中加入default方法体的功能是为了增强接口的灵活性和扩展性。

小例子:
当我们在Java 8中定义一个接口时,可以使用default关键字来定义一个默认方法。例如,我们定义一个简单的接口Greeting,其中包含一个默认方法defaultGreet:

public interface Greeting {
    void greet(String name);
    
    default void defaultGreet() {
        System.out.println("Hello, how are you?");
    }
}

然后我们可以创建一个实现该接口的类GreetingImpl,并选择性地覆盖默认方法:

public class GreetingImpl implements Greeting {
    @Override
    public void greet(String name) {
        System.out.println("Hello, " + name);
    }
    // 不覆盖默认方法,直接使用默认实现
}
public class Main {
    public static void main(String[] args) {
        GreetingImpl greeting = new GreetingImpl();
        greeting.greet("Alice"); // Output: Hello, Alice
        greeting.defaultGreet(); // Output: Hello, how are you?
    }
}

你怎么理解类的单一继承?有什么作用?

  • 避免类的多重继承带来的复杂性。如果一个类可以继承多个父类,那么就会带来多个父类中可能存在的重名方法、变量等问题,从而增加了代码的复杂度和维护难度

String、StringBuilder和StringBuffer的区别

  • String 是不可变的(immutable)类,一旦创建就不能被修改。每次对String进行操作(例如拼接、替换等),都会创建一个新的String对象。由于不可变性,String在多线程环境下是安全的。适用于字符串不经常改变的情况。
  • StringBuilder是可变的(mutable)类,可以通过调用方法来修改其中的字符序列,而不创建新的对象。由于可变性,StringBuilder 的性能比String高,特别是在需要频繁进行字符串操作的情况下。适用于字符串经常需要改变的情况,但StringBuilder不是线程安全的。
  • StringBuffer 与 StringBuilder 类似,但它是线程安全的

java中的字符串可以通过什么拼接

  1. 使用加号 (+) 拼接: 这是最直观的方法,也是最常用的。例如:
String str1 = "Hello";  
String str2 = "World";  
String str3 = str1 + ", " + str2;
  1. 使用 concat() 方法: 这个方法是 String 类中的一个方法,例如:
String str1 = "Hello";  
String str2 = "World";  
String str3 = str1.concat(", ").concat(str2);
  1. 使用 StringBuilder 或 StringBuffer: 当你需要拼接大量的字符串时,使用 StringBuilder 或 StringBuffer 是一个更好的选择,因为它们是可变的,不会为每次拼接创建新的字符串对象。例如:
StringBuilder sb = new StringBuilder();  //或StringBuffer sb = new StringBuffer(); 
sb.append("Hello");  
sb.append(", ");  
sb.append("World");  
String str = sb.toString();
  1. 使用 String.format() 方法: 这个方法可以按照指定的格式拼接字符串。例如:
String str1 = "Hello";  
String str2 = "World";  
String str3 = String.format("%s, %s", str1, str2); //%s 是一个占位符,表示将替换为一个字符串,按参数的顺序替换
  1. 使用文本块(JDK 13 新增): 如果你使用的是 JDK 13 或更高版本,你还可以使用文本块来拼接字符串。例如:
String str1 = "Hello";  
String str2 = "World";  
String str3 = """  
               %s,   
               %s""".formatted(str1, str2);

实现多线程的几种方式之间的区别

在Java中,实现多线程的几种方式包括继承Thread类、实现Runnable接口、使用Callable和Future、使用线程池等,区别如下:

  • 继承Thread类:创建一个继承自Thread类的子类,并重写run()方法来定义线程的执行逻辑。这种方式简单直观,但由于Java是单继承的,因此如果已经继承了其他类,就无法再使用这种方式创建线程。实现Runnable接口:创建一个实现了Runnable接口的类,并实现其中的run()方法。这种方式避免了单继承的限制,因为一个类可以实现多个接口,因此更灵活。
  • 使用Callable和Future:Callable接口允许线程执行并返回结果,并且可以抛出异常。Future接口可以用来获取Callable的返回结果,还可以取消任务、查询任务是否完成等。这种方式相比Runnable更加强大,但也更加复杂
  • 使用线程池:线程池可以重用已创建的线程,减少线程创建和销毁的开销,提高系统的性能。通过线程池,可以更好地控制并发线程的数量,避免系统资源被耗尽。Java提供了Executors工厂类来创建不同类型的线程池。

介绍一下Mybatis-plus里面的QueryWrapper以及里面常用的方法(like…)

  • Mybatis-Plus 是一个Mybatis 的增强工具,在Mybatis的基础上只做增强不做改变,简化开发、提高效率,而且不用写SQL。Query是Mybatis-Plus中的查询工具类,用于执行各种查询操作。Wrapper是查询条件封装类,通常用于封装复杂的查询条件。

like(String column, Object value)方法:用于构建模糊查询条件,其中column为数据库表字段名,value为要匹配的值。
示例:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "admin");

上述示例中,构建了一个模糊查询条件,要求username字段包含"admin"的值。
除了like方法,QueryWrapper还提供了一系列其他常用的方法,如eq、ne、gt、ge、lt、le等,用于构建等值查询、不等值查询、大于、大于等于、小于、小于等于等各种查询条件。

讲讲设计数据库的思路

  • 需求分析:首先需要明确业务需求,了解系统要支持的功能和数据处理需求。这包括收集用户需求、业务流程、数据处理流程等,以便确定数据库需要存储哪些数据以及数据之间的关系
  • 实体建模:根据需求分析,识别出系统中的各种实体,并分析它们之间的关系。这些实体可以转化为数据库中的表,实体之间的关系可以转化为表之间的关系(如一对一、一对多、多对多关系)
  • 属性定义:为每个实体确定需要存储的属性(字段),包括属性的数据类型、长度、约束条件等
  • 范式设计:考虑数据库的范式化设计,以确保数据的一致性和有效性。范式设计包括将数据组织成符合第一范式、第二范式、第三范式等范式的形式,以减少数据冗余和提高数据的完整性
  • 性能考虑:考虑性能需求,包括数据的读写频率、数据量的大小等。根据性能需求进行索引设计、分区设计等优化措施
  • 安全性考虑:包括对敏感数据的加密、权限控制、防止SQL注入等
  • 可维护性设计:包括可读性、可扩展性、可维护性等。这需要采用合理的命名规范、文档编写、代码注释等方法
  • 备份与恢复策略:为了应对意外情况,需要制定备份与恢复策略,包括定期备份数据、使用日志文件进行恢复等

Spring Boot 应用程序的启动过程

  • 加载启动类SpringBootApplication,Spring Boot通过扫描启动类所在的包和子包,自动装配配置类和Bean
  • 加载配置文件,默认从 application.properties 文件中加载,也可以通过在启动类上使用@PropertySource注解指定其他的属性文件
  • 通过 SpringApplication来创建 Spring容器
  • 加载自动配置,根据 classpath中的 jar包和 Bean的装配情况,自动装配相应的Bean
  • 当Spring容器准备就绪后,Spring Boot就会启动内嵌的Tomcat服务器并运行应用程序。如果使用的是外部Web服务器,Spring Boot就会将应用程序打包成一个可执行的 jar文件,并启动外部Web服务器。

SpringBoot中怎么解决循环依赖的问题

List和Set的区别

什么是静态变量?什么是常量?

Tomcat为什么要打破双亲委派机制?

@Component和@Service的区别(性能上)

说一下java的异常体系,继承结构

什么是运行时异常和非运行时异常以及两者的区别

为什么会出现空指针异常

  • 因为代码中使用了一个空对象,而这个空对象没有进行初始化或者已经被释放了

Mybatis常用的注解和Xml中的标签

Mybatis中怎么做查询结果的嵌套

介绍SpringBoot的自动装配

同一个SpringBoot项目怎么启动两个服务

SpringBoot和SpringCloud的关系

为什么需要SpringCloud?有什么作用

介绍SpringCloud必要的组件

A服务查询B服务用到什么组件

Spring怎么管理事务

事务的传播机制

为什么需要事务

单表操作需要事务吗

Redis的常见数据类型

  • 字符串(string):最基本的数据类型,可以存储任意类型的数据,例如数字、文本等
  • 列表(list):按照插入顺序存储一系列的元素,可以在列表的两端进行插入、删除操作,支持对列表进行分片操作
  • 集合(set):无序的唯一元素的集合,可以进行元素的添加、删除、判断某元素是否存在等操作
  • 有序集合(sorted set):类似于集合,但每个元素都关联了一个分数(score),可以根据分数对元素进行排序,支持按照分数范围获取元素
  • 哈希表(hash):类似于字典或散列表,可以存储多个键值对,每个键对应一个值,支持对单个键值对的增、删、改、查操作
  • Redis还支持一些其他的数据类型,例如位图(bitmap)、超级日志(hyperloglog)等。不同的数据类型在Redis中有不同的存储结构和操作方式,可以根据具体的需求选择适合的数据类型

索引用的是什么数据结构,可以用B树吗

中软国际面试题

什么是Java?介绍面向对象及四大特征概念?

Java概念: Java是一种广泛应用于软件开发的编程语言,具有跨平台性和面向对象的特性。它由Sun公司1995年推出,后被Oracle公司收购。

面向对象思想: 是一种编程思想,将程序中的数据和操作数据的方法组合成一个对象,通过调用对象的方法来实现功能

面向对象四大特征:

  • 封装:将数据和操作封装在对象中,隐藏内部实现细节,只通过对象接口与外界交互
  • 继承:通过继承已有类的属性和方法,可以减少代码的重复性,提高代码的复用性
  • 多态:同一个方法在不同的对象上产生不同的行为和操作
  • 抽象:定义抽象类和抽象方法,由子类实现具体细节

增删改查基本语句是什么?

  • 增:insert
  • 删:drop/delete
  • 改:update/alter(设置)
  • 查:select

servlet生命周期是什么?

  • servlet生命周期:在第一次访问时调用init初始化,每次请求使用service服务,在服务器关闭时调用destory销毁

JDBC连接步骤是什么?

  1. 加载驱动(使用Class.forName()方法加载数据库驱动)
  2. 获得连接
  3. 创建Statement对象
  4. 发送sql语句
  5. 处理结果集
  6. 关闭连接

线程和进程的区别

  • 线程是程序执行的最小单位,而进程是操作系统分配资源和调度的最小单位;线程是进程的一部分,一个进程可以包含多个线程

多线程有几种实现方法?分别是什么?

目前我知道的有四种:

  • 继承Thread类:重写run()方法,并通过调用start()方法启动线程
  • 实现Runnable接口:实现run()方法,并通过创建Thread对象,将Runnable对象作为参数传入,再调用start()方法启动线程
  • 实现Callable接口
  • 使用线程池:通过Executor框架的线程池来管理和执行线程

如何实现线程安全?

  1. 互斥锁:通过使用synchronized关键字或ReentrantLock类来确保同一时间只有一个线程可以访问共享资源
  2. 读写锁:ReadWriteLock接口提供了读写锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源
  3. 使用Lock接口:通过Lock对象的lock()和unlock()方法来实现线程的同步
  4. 使用原子类:使用java.util.concurrent.atomic包下的原子类来保证对变量的原子操作
  5. 信号量:Semaphore类可以用来限制对共享资源的访问权限,确保同时只有一个线程可以访问共享资源
  6. 倒计时门闩(shuān):CyclicBarrier类可以让一定数量的线程互相等待,当所有线程都准备好后,门闩才会打开,允许所有线程同时访问共享资源
  7. 循环栅栏:CountDownLatch类可以让一个线程等待其他线程都完成操作后才能继续执行

描述关系型数据库中三范式的概念

范式是关系型数据库中的一种设计规范,三范式用于提高数据的一致性、完整性和减少数据冗余:

  1. 第一范式(1NF):确保每个列都是原子性的,即每个列都不可再分;不允许多值属性或重复的属性,每个属性只能包含一个值
  2. 第二范式(2NF):在满足第一范式的基础上,要求非主键列完全依赖于主键
  3. 第三范式(3NF):在满足第二范式的基础上,要求非主键列之间没有传递依赖关系,而是直接依赖于主键

当然还有更高级的范式,如BCNF(巴斯-科德范式)和第四范式(4NF),但在实际设计中并不常用

列举你了解的设计模式

Spring运用了很多设计模式: IOC容器应用工厂设计模式、Bean的管理应用单例设计模式、AOP切面编程应用代理设计模式、HandlerAdapter应用适配器设计模式、不同的HandlerMapping找寻对应的Handler应用策略设计模式、参数的获取以及转换AgurmentResovler应用责任链设计模式、监听器应用了观察者设计模式等

怎么理解MVC、SpringMVC及两者的区别

  • MVC(Model-View-Controller)是一种软件设计模式,将应用程序分为三个部分:模型(数据和业务逻辑)、视图(用户界面)和控制器(处理用户输入和调度模型和视图)。MVC模式可以提高代码的可维护性和可扩展性

  • Spring MVC是一个实现了MVC模式的轻量级Web框架,它基于Java平台,提供了丰富的Web开发工具和API,用于快速开发Web应用程序

列举几种表连接方式

  • 内连接(INNER JOIN):返回两个表中都匹配的行
  • 左连接(LEFT JOIN):返回左表中的所有行以及与右表中匹配的行
  • 右连接(RIGHT JOIN):返回右表中的所有行以及与左表中匹配的行
  • 全连接(FULL JOIN):返回两个表中的所有行

各自的详解如下:

在这里插入图片描述

注意“inner join”两边的表的位置可以互换,结果都一样

-- inner join
select * from course c inner join teacher t on c.t_id = t.t_id 

-- 逗号的连表方式也是内连接
select * from course c ,  teacher t where c.t_id = t.t_id 

在这里插入图片描述

注意“left join”两边的表位置不可以互换,交换后结果可能不一样。需要考虑好哪个是主表,哪个是从表。写在前面的是主表

select * from course c left join teacher t  on  c.t_id = t.t_id 

在这里插入图片描述

select * from course c right join teacher t on   c.t_id = t.t_id 

在这里插入图片描述

注意中间部分包括了左右表两部分,需要去重掉其中的一部分

-- Oracle的全连接
select * from a full join b on a.id = b.id

-- Mysql的全连接没有full join,mysql可以使用union实现全连接
select * from a left join b on a.id = b.id
union
select * from a right join b on a.id = b.id

你知道的排序算法有哪些?

冒泡排序:

  • 实现逻辑:比较相邻的两个元素,如果前面的元素大于后面的元素,就交换它们的位置,重复这个过程直到整个数组排序完成
  • 时间复杂度:最好情况下为O(n),最坏情况下为O(n^ 2),平均情况下为O(n^2)

插入排序:

  • 实现逻辑:将数组分为已排序和未排序两部分,每次从未排序部分中取出一个元素,插入到已排序部分的适当位置,重复这个过程直到整个数组排序完成
  • 时间复杂度:最好情况下为O(n),最坏情况下为O(n^ 2),平均情况下为O(n^2)

选择排序:

  • 实现逻辑:将数组分为已排序和未排序两部分,每次从未排序部分中选择最小(或最大)的元素,将其放到已排序部分的末尾,重复这个过程直到整个数组排序完成
  • 时间复杂度:最好情况下为O(n^ 2),最坏情况下为O(n^ 2),平均情况下为O(n^2)

快速排序:

  • 实现逻辑:选择一个基准元素,将数组分为两部分,左边的元素都小于基准元素,右边的元素都大于基准元素,然后对左右两部分分别进行递归排序
  • 时间复杂度:最好情况下为O(nlogn),最坏情况下为O(n^2),平均情况下为O(nlogn)

归并排序:

  • 实现逻辑:将数组递归地分成两半,对每一半进行排序,然后将两个有序的子数组合并成一个有序的数组
  • 时间复杂度:最好情况下为O(nlogn),最坏情况下为O(nlogn),平均情况下为O(nlogn)

堆排序:

  • 实现逻辑:将数组构建成一个最大堆(或最小堆),然后将堆顶元素与最后一个元素交换,再重新调整堆,重复这个过程直到整个数组排序完成
  • 时间复杂度:最好情况下为O(nlogn),最坏情况下为O(nlogn),平均情况下为O(nlogn)

运行时异常和非运行时异常的区别,或者是异常和错误的区别

  • 运行时异常:是指在程序运行过程中可能发生的异常,通常是由程序逻辑错误引起的,如空指针异常、数组越界异常等。运行时异常不需要在代码中显式处理,可以选择捕获或抛出
  • 非运行时异常:是指在程序编译过程中就能够预测到的异常,需要在代码中显式处理,否则编译不通过。非运行时异常包括IO异常、数据库异常等
  • 异常和错误的区别在于异常是可以被程序处理的,而错误是无法被程序处理的严重问题,错误需要通过调试和修复来解决

表的自连接怎么实现?

表的自链接是指将表与自身进行连接操作。实现自链接可以使用表的别名来区分不同的表实例,然后使用连接条件将表的不同实例进行连接

例如,假设有一个员工表,包含员工的ID、姓名和上级ID等字段。可以使用自链接查询出每个员工的上级姓名

SELECT e1.姓名 AS 员工姓名, e2.姓名 AS 上级姓名
FROM 员工表 e1, 员工表 e2
WHERE e1.上级ID = e2.ID;

JSP九大内置对象(必考)

  • request 请求,封装HTTP请求的信息
  • response 响应,封装HTTP响应的信息
  • session 会话,封装用户会话的信息
  • application 应用,封装Web应用程序的全局信息
  • page 页面,当前JSP页面的实例
  • pageContext 页面上下文,封装页面上下文的信息
  • config 配置,封装Servlet配置的信息
  • exception 异常,封装页面抛出的异常信息
  • out 输出,用于输出响应到客户端的输出流

什么是主键?外键?

  • 主键(Primary Key)是数据库表中用于唯一标识记录的字段,它的值是唯一的,且不能为空
  • 外键(Foreign Key)是用于建立表与表之间的关联关系的字段,它引用了另一个表的主键值

Spring是什么?

Spring是一个开源的Java应用开发框架,用于开发企业级应用程序。Spring框架提供了一系列的模块,包括依赖注入(IoC)、面向切面编程(AOP)、数据访问、事务管理、Web开发等,帮助开发者更简单、高效地构建应用程序

Spring的优点?

  • 轻量级:Spring框架的体积较小,易于学习和使用,不会给开发带来额外的负担
  • 面向切面编程:Spring提供了面向切面编程的支持,可以方便地实现日志记录、性能统计等功能,提高了代码的可重用性和可维护性
  • 依赖注入:Spring通过依赖注入机制,实现了组件之间的解耦,使得代码更加灵活和可配置
  • 模块化:Spring框架采用了模块化的设计,各个模块之间相互独立,可以灵活地组合使用,提高了开发效率
  • 强大的整合能力:Spring可以与各种流行的Java技术进行整合,如Hibernate、Struts等,可以快速构建企业级应用程序
  • 事务管理:Spring提供了统一的事务管理接口,支持编程式和声明式的事务管理,简化了事务管理的开发工作
  • 简化开发:Spring提供了大量的开发工具和集成支持,简化了开发过程,提高了开发效率
  • 测试支持:Spring提供了丰富的测试支持,可以方便地进行单元测试和集成测试

对Spring的AOP理解

  • Spring的面向切面编程是一种编程思想,它允许开发者定义跨多个服务和方法的横切关注点。通过AOP,开发者可以将公共功能(如日志记录、性能统计等)抽取出来,封装成一个独立的模块,将其应用到其他模块中。这样可以将关注点从业务逻辑中分离出来,提高了代码的可重用性和可维护性

对Spring的IoC理解

  • Spring的依赖注入是一种设计模式,它允许开发者通过配置文件或注解等方式,将组件之间的依赖关系从硬编码中解耦出来,将其交给Spring容器来管理。

Spring支持哪几种bean的作用域?

  • singleton:默认的作用域,每个Bean都是单例的,在Spring容器中只会存在一个实例,所有请求都共享同一个实例
  • prototype:每次获取Bean时都会创建一个新的实例
  • request:每个HTTP请求都会创建一个新的Bean实例,不同请求之间的实例是独立的
  • session:每个会话都会创建一个新的Bean实例,不同会话之间的实例是独立的
  • global-session:在整个应用程序中只创建一个实例,但只在基于portlet的web应用程序中有效

Spring的单例Beans是线程安全的么?

  • 是的,在Spring容器中,每个Bean都是单例的,且在初始化时会进行依赖注入,Spring还提供了对线程安全的支持,例如使用synchronized关键字来保证线程安全

Spring如何处理线程并发问题?

  • 使用单例Beans,Spring的单例Beans默认是线程安全的,可以在多线程环境下安全地访问
  • 可以使用ThreadLocal来存储线程私有的数据,保证线程安全
  • 可以使用同步关键字synchronized或锁来保证多个线程对共享资源的访问的互斥性
  • 可以使用线程池来管理线程,控制并发访问的数量

Spring基于xml注入bean(依赖注入)有几种方式?

  • Setter注入
  • 构造器注入
  • 工厂注入
  • 接口注入

Spring的自动装配(自动注入)是什么?

Spring的自动装配(Autowired)是指Spring容器根据类型或名称自动将匹配的依赖注入到相应的属性、构造函数或方法参数中。自动装配可以减少手动配置的工作量,提高开发效率。可以通过@Autowired和@Resource注解来实现自动装配,还可以通过@Qualifier注解指定具体的bean名称进行装配

Spring事务的实现方式和实现原理

  • 实现方式:Spring事务的实现方式有两种,分别是编程式事务管理和声明式事务管理
  • 实现原理:Spring事务的实现原理是通过AOP(面向切面编程)和代理模式来实现的。在编程式事务管理中,通过编写代码手动控制事务的开启、提交和回滚;在声明式事务管理中,通过在方法或类上添加事务注解来实现事务的自动管理

Spring框架中有哪些不同类型的事件?

  • ApplicationEvent:这是一个泛型事件,可以用于实现自定义事件
  • HttpRequestEvent:处理HTTP请求时触发的事件
  • TransactionalEvent:用于事务中的事件
  • ContextRefreshedEvent:容器刷新完成后触发的事件
  • ContextStartedEvent:当ApplicationContext启动时触发的事件
  • ContextStoppedEvent:当ApplicationContext停止时触发的事件
  • ContextClosedEvent:当ApplicationContext关闭时触发的事件
  • NotificationEvent:用于发送通知的事件

Spring的AOP通知有哪些类型?

  • Before:在目标方法执行之前执行通知(使用场景:记录日志)
  • After:在目标方法执行之后执行通知,无论是否发生异常(使用场景:记录日志)
  • AfterReturning:在目标方法执行之后执行通知,仅在方法成功完成时执行
  • AfterThrowing:在目标方法抛出异常后执行通知(使用场景:异常处理、控制事务)
  • Around:在目标方法执行前后执行通知,可以自定义目标方法的执行(使用场景:控制事务、权限控制)

什么是 Spring Boot?

  • Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它简化了Spring应用程序的配置和部署过程,并提供了一种约定优于配置的方式

为什么要用 Spring Boot?

  • 因为它能够大大简化Spring应用程序的开发和部署过程。它提供了自动配置、起步依赖和可嵌入的容器等特性,使开发人员能够更专注于业务逻辑而不是繁琐的配置

Spring Boot 的配置文件有哪几种格式以及区别?

主要有以下三种:

  • application.properties:键值对格式
  • application.xml:XML格式
  • application.yml(还有.yaml):缩进的简洁的键值对格式

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

Spring Boot 的核心注解是 @SpringBootApplication

它主要由以下三个注解组成:

  • @Configuration:用于标记一个配置类
  • @EnableAutoConfiguration:用于启用自动配置功能,让 Spring Boot 能够根据需要自动配置相关的组件
  • @ComponentScan:这个注解用于扫描指定包下的组件,让 Spring 能够找到并注册这些组件

开启 Spring Boot 特性有哪几种方式?

主要有以下几种方式:

  • 使用 @SpringBootApplication 注解
  • 使用@EnableAutoConfiguration 注解
  • 使用 SpringApplication.run() 方法

Spring Boot 需要独立的容器运行吗?

  • 不需要,因为它可以使用内置的Tomcat、Jetty或Undertow等容器运行

运行 Spring Boot 有哪几种方式?

  • 直接通过IDE运行主类
  • 使用命令行运行:使用 Maven 或 Gradle 构建JAR 包,使用命令 mvn spring-boot:run 或 gradle bootRun 来运行

Spring Boot 自动配置原理是什么?

  • 约定优于配置:Spring Boot 会根据项目的结构和已有的配置,自动地配置你的应用程序。例如项目中的 application.properties 文件,Spring Boot 会自动地读取这个文件并使用其中的配置
  • 类的路径扫描:Spring Boot 会扫描项目中的类路径,寻找可以自动配置的组件。例如一个类实现了某个特定的接口或注解,Spring Boot 就会自动地将其作为一个 Bean 注册到应用程序中
  • Bean 的依赖注入:Spring Boot 会根据 Bean 的依赖关系,将它们组装在一起。例如一个类依赖于另一个类,Spring Boot 就会自动地创建一个前者实例并将其注入到后者中

你如何理解 Spring Boot 中的 Starters?

  • Starters 是预先配置好的项目模板,可以帮助开发者快速地创建和启动一个 Spring Boot 项目。它们包含了项目的基本结构和依赖关系,以及一些常用的库和工具。使用 Starters可以大大减少创建和配置一个 Spring Boot 项目所需的时间

如何在 Spring Boot 启动的时候运行一些特定的代码?

通过实现ApplicationRunner接口或CommandLineRunner接口,并重写里面的run()方法

@Component  
public class MyStartupRunner implements ApplicationRunner {  
    @Override  
    public void run(ApplicationArguments args) throws Exception {  
        // 在这里编写你需要在启动时运行的代码  
    }  
}

Spring Boot 有哪几种读取配置的方式?

  • 通过 application.properties 文件读取
  • 通过环境变量读取。可以在启动应用程序时设置环境变量,或者在应用程序中使用 Environment 对象来访问环境变量
  • 通过命令行参数读取。可以在启动应用程序时传递命令行参数,或者在应用程序中使用 @Value 注解来访问命令行参数

Spring Boot 支持哪些日志框架?推荐和默认的日志框架是哪个?

  • Spring Boot 支持多种日志框架,包括 SLF4J,Logback,Log4j2,JUL(Java Util Logging)等。推荐和默认的日志框架是 Logback

SpringBoot 实现热部署有哪几种方式?

热部署概念:程序无需手动重启,会自动重新加载类、配置文件和静态资源的更改,从而实现热部署

  • 使用 Spring Boot DevTools
  • 使用Maven 提供的 hot-deploy 插件
  • 使用 JRebel插件

如何理解 Spring Boot 的配置加载顺序?

  • 是按照特定的优先级来加载的,首先会加载内置的默认配置。然后会加载外部的配置文件(如application.properties)。最后会加载通过命令行参数或环境变量传递的配置

Spring Boot 如何定义多套不同的环境配置?

  • 可以使用不同的配置文件来定义多套环境配置。例如,可以使用 application.properties 文件作为默认配置文件,然后为不同的环境创建额外的配置文件,例如 dev.properties 文件用于开发环境,prod.properties 文件用于生产环境。在启动应用程序时,可以通过设置 spring.profiles.active 环境属性来选择加载哪个配置文件。此外还可以使用 @Profile 注解来限制特定的 Bean 只在特定的环境下使用

Spring Boot 可以兼容老 Spring 项目吗?如何做?

  • 可以,可以将老 Spring 项目作为一个普通的 Maven 或 Gradle 项目导入到 Spring Boot 中,然后使用 Spring Boot 的自动配置和约定优于配置的原则来简化配置和启动过程。同时也可以根据需要启用或禁用某些自动配置项,以避免与老项目的兼容性问题

保护 Spring Boot 应用有哪些方法?

  • 使用安全框架:例如 Spring Security,它可以为应用程序提供身份验证和授权机制
  • 编码和加密:对敏感数据进行编码或加密,以保护数据的安全性
  • 防止 SQL 注入:使用参数化查询或 ORM 框架提供的查询构建器来防止 SQL 注入攻击
  • 使用 HTTPS:通过使用 HTTPS 来保护数据传输过程中的安全性
  • 日志和监控:对应用程序进行日志记录和监控,以便及时发现并应对潜在的安全威胁

Spring Boot 2.X 有什么新特性?与 1.X 有的区别?

  • 支持 Java 8 和新的版本进行升级
  • 支持 Spring Data Redis
  • 支持 Spring Session 和响应式数据库连接
  • 支持响应式编程模型(Reactive Programming)
  • 提供新的 Security 相关 API
  • 提供 WebFlux 控制器注解 @WebFluxController
  • 对自定义starter支持进行了重构与提升等

SpringMVC常用注解

  • @Controller:用于标识一个类为控制器。表明这个类是一个处理 HTTP 请求的类
  • @RequestMapping:用于映射 HTTP 请求到特定的处理方法上,在类上使用时,会映射该类所有的处理方法;在方法上使用时,会映射该方法
  • @RequestParam:用于获取请求参数
  • @RequestBody:用于获取 HTTP 请求体的内容
  • @RequestHeader:用于获取 HTTP 请求头的内容
  • @PathVariable:用于获取路径变量/参数
  • @ResponseEntity:用于返回 HTTP 响应,它可以在方法的返回值中使用
  • @Autowired:用于自动装配依赖
  • @Qualifier:用于指定依赖的名称

SpringMVC怎样设定转发和重定向?

  • 可以使用 request.getRequestDispatcher().forward() 方法进行转发
  • 可以使用 response.sendRedirect() 方法进行重定向

转发和重定向的区别

  • 转发的url地址栏不会发生改变;重定向的url地址栏会发生改变
  • 转发实际上是用上一次的请求做的;而重定向是要求浏览器重新发送一次新的请求
  • 转发可以携带上一次请求的参数;而重定向无法携带上一次请求的参数

SpringMVC怎么和AJAX相互调用?

  • SpringMVC可以通过使用@ResponseBody注解将方法返回的对象转换为JSON格式,从而与AJAX进行相互调用

SpringMVC的优点

  • Spring MVC 提供了丰富的文档和示例,使得使用和配置比较容易
  • 支持多种视图技术,包括 JSP、Thymeleaf、FreeMarker 等
  • 提供了强大的数据绑定和验证功能
  • 支持国际化和本地化,可以轻松地创建多语言应用程序
  • 提供了良好的可测试性和可维护性,可以使用单元测试和集成测试来确保代码的质量

Spring MVC的主要组件

  • DispatcherServlet
  • Controller
  • View Resolver
  • Model
  • 验证器(Validator):用于验证命令对象是否符合特定的规则和约束
  • 命令对象(Command Object):用于接收来自表单的参数,通常用于处理表单提交

SpingMVC中的控制器注解一般用哪个?有没有别的注解可以替代?

  • 在 Spring MVC 中,@Controller 是最常用的注解之一,还可以使用 @RestController 注解,它结合了 @Controller 和 @ResponseBody 的功能

如何在拦截请求中拦截 GET 方式提交的方法?

  • 可以在拦截器中配置拦截的URL路径,并在拦截器中通过HttpServletRequest的getMethod()方法获取请求的方法类型,然后进行判断和处理

怎样在方法里面得到Request或者Session?

  • 在 Spring MVC 中,控制器方法可以通过注入 HttpServletRequest 和 HttpSession 对象来获取请求和会话信息。例如,在一个控制器方法中添加 HttpServletRequest 和 HttpSession 类型的参数,Spring 框架会自动将相应的对象注入到方法中

怎么在拦截的方法里得到从前台传入的参数?

  • 可以直接在方法的参数列表中使用@RequestParam注解来获取指定名称的参数值

如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象?

  • 在控制器方法中,使用 @ModelAttribute 注解来指定参数的名称,然后将从前台传入的参数值绑定到该参数上。Spring MVC 会自动将参数值转换为目标对象类型,并将其注入到控制器方法中

SpringMVC中函数的返回值是什么?

  • 可以是一个字符串,表示返回的视图名称;也可以是一个ModelAndView对象,包含了返回的视图名称和模型数据;还可以是一个@ResponseBody注解的方法,返回JSON格式的数据;可以是任何合法的 Java 类型,包括基本数据类型、对象、集合等

SpringMVC用什么对象从后台向前台传递数据?

  • 可以使用ModelAndView对象来向前台传递数据,也可以使用Model对象或者Map对象,这些数据会被自动放入到请求的作用域中

SpringMVC中Controller怎么向JSP返回值?

  • 可以将返回值设置到ModelAndView对象中,然后通过设置视图名称为JSP文件的路径来返回。在 JSP 中可以使用 EL表达式或 Jstl 标签来访问这些数据并进行显示

Controller怎么接收前端的参数?

  • 可以使用@RequestParam、@PathVariable、@RequestBody注解等方式来接收

Jquery常用选择器有哪些?

  • 元素选择器:如$(“div”)选择所有的div元素
  • ID选择器:如$(“#myDiv”)选择id为"myDiv"的元素
  • 类选择器:如$(“.myClass”)选择所有class为"myClass"的元素
  • 属性选择器:如$(“[name=‘myName’]”)选择所有name属性值为"myName"的元素
  • 后代选择器:如$(“div p”)选择所有div元素下的p元素
  • 子元素选择器:如$(“div > p”)选择所有div元素下的直接子元素为p的元素
  • 兄弟元素选择器:选择与指定元素相邻的元素,如$(“div + p”)选择所有紧接在div元素后的p元素
  • 筛选器选择器:如$(“div:first”)选择第一个div元素

什么是Mybatis?

  • MyBatis是一个数据持久层ORM映射框架,把实体类和SQL语句之间建立了映射关系,是一种半自动化的ORM实现

Mybaits的优点

  • 灵活性高,可以自定义SQL语句;易于学习和使用;提供了强大的动态SQL功能;

MyBatis的缺点

  • 需要手动编写SQL语句,对开发人员的要求较高;配置较为繁琐,需要编写XML文件或注解;对于复杂的关联查询支持较弱

MyBatis适用场合

  • 适用于需要灵活控制SQL语句,对性能要求较高的项目,以及对数据库操作较为熟悉的开发人员,适用于需要执行复杂SQL语句的业务场景(如金融领域需要大量分页查询等操作)

MyBatis与Hibernate的区别?

  • Mybatis是半自动化的ORM,可以自定义SQL语句,更加灵活
  • Hibernate是全自动的ORM,会自动生成SQL语句,更加面向对象,通过ORM实现数据的映射

#{}和${}的区别?

  • #{}用于预编译SQL语句中的参数,会将参数值进行安全地替换,防止SQL注入攻击;主要用于查询中的参数绑定,如传入的参数是变量或者是表达式
  • ${}用于拼接SQL语句,会将参数直接替换到SQL语句中,但存在SQL注入攻击的风险;主要用于已知变量的插入,不建议用于查询参数的绑定

实体类中属性名和表中字段名不一样 时怎么办 ?

  • 可以在SQL语句中使用别名
  • 可以在XML文件中的resultMap元素中使用column属性来指定字段名,使用property属性来指定属性名
  • 可以在实体类中使用@Result注解来指定映射关系

模糊查询like语句怎么写?

  • 示例使用了CONCAT函数将%符号与查询参数拼接起来,实现了模糊查询:
SELECT * FROM users WHERE name LIKE CONCAT('%', #{name}, '%')

Xml对应的Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

  • MyBatis通过动态代理机制将XML映射文件中的SQL语句与DAO接口的方法关联起来。当调用DAO接口的方法时,MyBatis会根据方法名和参数类型生成相应的SQL语句并执行
  • Dao接口里的方法可以重载,但是需要使用@Param注解来区分不同的方法

Mybatis是如何进行分页的?分页插件的原理是什么?

  • 可以在XML中使用 rowBounds 属性来指定分页参数,例如:
<select id="selectUsers" resultType="User" parameterType="map" rowBounds="offset,limit">  
  SELECT * FROM users WHERE 1=1 ORDER BY id LIMIT #{offset}, #{limit}  
</select>

offset 是偏移量,表示要跳过的记录数;limit 是每页记录数。当调用selectUsers方法时,MyBatis会自动根据传入的分页参数生成相应的SQL语句并执行

  1. 分页插件的原理是通过拦截器机制,在查询SQL语句执行前,修改SQL语句的内容,添加分页的逻辑

Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

Mybatis将sql执行结果封装为目标对象并返回的过程主要通过以下步骤完成:

  1. Mybatis执行sql后,获取到结果集(ResultSet)
  2. 结果集经过 Mybatis的 ResultSetHandler进行处理,根据XML映射文件中的映射配置,将结果集转化为Java对象
  3. 最后将转化后的Java对象返回给调用者

Mybatis的映射形式主要有以下几种:

  • 单行映射(ResultMap):适用于结果集中只有一行数据的情况
  • 多行映射(ResultMap):适用于结果集中有多行数据的情况
  • 使用@Result和@Results进行映射:可以在Java类中使用注解进行映射配置,适用于简单的情况
  • 使用对象映射(ObjectMapping):将结果集映射到一个Map中,然后根据key将数据取出

如何执行批量插入?

  • 在Mybatis中,可以使用insertList方法来一次性插入多条数据。该方法接受一个列表参数,列表中的每个元素表示一条待插入的数据
  • 也可以使用foreach来循环插入多条数据,在每次遍历时执行插入操作
  • 还可以调用PreparedStatement对象的addBatch()方法,将数据都添加到批处理中,再执行executeBatch()方法执行批处理插入操作

如何获取自动生成的(主)键值?

  • 可以调用PreparedStatement对象的getGeneratedKeys()方法获取
  • 可以在 Mybatis映射文件或注解中设置 useGeneratedKeys=“true”,然后在目标对象的属性上使用 @Options(useGeneratedKeys = true, keyProperty = “id”) 注解来指定自动生成的主键属性

session和cookie的区别?

  • 存储位置:Session是存储在服务器端,Cookie是存储在客户端
  • 存储容量:Session可以存储较大的数据量,而Cookie的存储容量有限,通常只能存储几KB的数据
  • 安全性:Session相对较安全,因为数据存储在服务器端,客户端无法修改;Cookie相对较不安全,因为数据存储在客户端,可以被篡改
  • 使用方式:Session需要在服务器端进行管理,通过Session ID来识别和管理用户的会话状态;Cookie可以直接在客户端使用,通过键值对的方式存储数据
  • 生命周期:都是在浏览器关闭时失效,但如果设置了过期时间就在该过期时间时才失效

LocalStorage和SessionStorage的区别

  • 生命周期:localStorage中的数据没有过期时间,除非手动删除,否则会一直保存在客户端;而sessionStorage中的数据只在当前会话有效,当用户关闭浏览器窗口或标签页时,数据会被删除
  • 存储容量:localStorage的存储容量较大,一般为5MB或更多;而sessionStorage的存储容量较小,一般为5MB或更少
  • 数据共享:localStorage中的数据在同一个域名下的所有页面之间共享;而sessionStorage中的数据只在同一个窗口或标签页下共享
  • 数据安全性:localStorage和sessionStorage中的数据都是保存在客户端,不会被发送到服务器,因此相对较安全。但注意不要将敏感信息存储在localStorage或sessionStorage中,以防止被恶意获取

常用Linux命令

  • ls:列出目录内容
  • cd:切换目录
  • pwd:显示当前目录
  • mkdir:创建目录
  • rm:删除文件或目录
  • cp:复制文件或目录
  • mv:移动文件或目录
  • cat:查看文件内容
  • grep:搜索文件内容
  • find:查找文件
  • tar:打包和解压文件

介绍下Dubbo

  • Dubbo是一个高性能、轻量级的开源Java RPC框架,由阿里巴巴开发并开源。它提供了一种基于服务的架构,用于解决分布式系统中的服务治理问题,广泛应用于大规模分布式系统中

Dubbo特点

  • 高性能:Dubbo使用 Netty作为底层通信框架,支持高性能、高吞吐量的RPC调用
  • 轻量级:Dubbo框架简洁、易于理解和使用,对应用侵入性小
  • 丰富的服务治理能力:Dubbo提供了多种服务治理策略,如服务发现、负载均衡、熔断、链路追踪等,帮助开发者构建稳定、高效的分布式应用
  • 易于扩展:Dubbo支持自定义扩展,方便开发者根据需求进行扩展和定制
  • 透明化的远程调用:Dubbo对远程调用进行了封装,使得开发者可以像调用本地方法一样调用远程服务,屏蔽了底层的通信细节
  • 智能负载均衡:Dubbo提供了多种负载均衡策略,如随机、轮询、一致性哈希等,根据实际情况选择合适的负载均衡策略

Dubbo支持的协议

  • Dubbo协议:它自定义的协议,默认使用Netty进行通信,采用单一长连接,适用于传输大量小数据的场景
  • RMI协议:基于Java的远程方法调用协议,适用于Java环境
  • Hessian协议:基于二进制的远程方法调用协议,适用于各种语言之间的通信
  • HTTP协议:基于HTTP的远程方法调用协议,适用于各种环境和语言
  • Thrift协议:Apache 的 Thrift 跨语言的远程服务调用框架

应用之间如何通讯?

  • 基于HTTP协议的通信:应用之间通过HTTP协议进行通信,可以使用HTTP的GET、POST等方法进行数据的传输
  • 基于RPC框架的通信:应用之间通过RPC框架(如Dubbo)进行远程调用,实现应用之间的通信和数据交互
  • 基于消息队列的通信:应用之间通过消息队列进行异步通信,生产者将消息发送到消息队列,消费者从消息队列中获取消息进行处理
  • 基于TCP/IP的Socket通信:应用之间通过Socket建立连接,通过读写数据流进行通信

应用之间的调用如何规划和监控?

可以通过服务治理来规划和监控,常见的方式包括:

  • 服务注册和发现:应用将自己的服务注册到注册中心,其他应用从注册中心获取服务的地址,实现服务的自动发现和调用
  • 负载均衡:通过负载均衡策略,将请求均匀地分发到多个服务提供者,实现负载均衡和高可用
  • 监控和追踪:通过监控和追踪系统,可以对服务的调用情况进行监控和统计,包括调用次数、调用耗时等指标,以便及时发现和解决问题

介绍下SpringCloud

  • Spring Cloud是一个基于Spring Boot的微服务框架,用于构建分布式系统中的微服务架构。它提供了一系列的组件和工具,用于实现服务注册和发现、负载均衡、断路器、配置管理、消息总线等功能,简化了微服务架构的开发和部署

字节流和字符流的区别

  • 都是用于读写数据的,但是字符流是以字符(char)为单位进行读写,主要用于处理文本数据;而字节流是以字节(byte)为单位进行读写,主要用于处理二进制数据,字节流读取文件速度较快

接口和抽象类的区别

  • 接口没有方法体,而抽象类可以包含抽象方法和普通方法
  • 一个类可以实现多个接口,但只能继承一个抽象类
  • 接口中的方法必须都是public修饰的,而抽象类可以根据需要设置不同的访问控制级别
  • 接口主要用于定义行为,而抽象类主要用于提供实现细节和继承关系

介绍Java的反射机制

  • Java反射机制是指在运行时动态地获取类的信息,包括类名、成员变量、方法、注解等,并通过反射调用类的方法和属性
  • 优点是可以访问私有方法和属性,增加了程序的灵活性和可维护性;可以将对象序列化为字节流,以便于存储和传输
  • 缺点是性能较低,因为反射需要动态地获取类信息并调用方法,比直接调用方法的性能要低;由于可以访问私有属性和方法,破坏代码的安全性;代码复杂和难以阅读

XML和Json的特点

  • XML是一种标记语言,可以用于描述结构化的数据。主要特点是良好的可读性,支持自定义标签和属性,可以很好地扩展和适应新的数据结构
  • JSON是一种轻量级的数据交换格式,使用键值对的形式来描述数据内容。主要特点是可读性好、体积小、解析速度快等。可以使用数组和嵌套对象来表示复杂的数据结构。

关系型数据库、nosql数据库和缓存数据库的区别

  • 关系型数据库以表格为单位,可以使用SQL语言进行数据的查询和操作,支持事务处理。通常用于需要复杂查询和数据一致性的应用
  • NoSQL数据库一般使用键值对、文档等来存数据。适用于需要处理大量数据、对一致性要求不高或需要灵活的数据模型的应用
  • 缓存数据库用于提高数据访问速度和性能,将经常使用的数据存储在内存中;具有快速读写性能和较低的数据持久化要求。适用于需要快速响应和高并发访问的应用中

Zookeeper的主要功能

Zookeeper是一个分布式协调服务,主要用于管理和协调分布式系统中的各种服务和进程。有以下主要功能:

  • 提供分布式命名服务,可以用于唯一标识节点
  • 可以集中管理和维护分布式系统的配置信息
  • 可以用来同步分布式节点之间的状态
  • 可以用来管理分布式系统的集群,包括节点监控、故障转移等
  • 可以用来实现分布式系统之间的同步操作

代码中事务是如何控制的?

  • 手动控制事务:通过在代码中显式地开启和提交或回滚事务来控制事务。这种方式需要手动编写事务的开启、提交和回滚代码,适用于较小的事务场景
  • 声明式事务管理:通过使用注解或XML配置等方式来声明事务的边界和行为。这种方式可以让事务的管理更加简洁和直观,适用于较复杂的事务场景
  • 自动事务管理:通过ORM框架或数据库连接池等自动管理事务的开启和提交或回滚。这种方式可以减少开发者的负担,适用于简单的事务场景
  • 编程式事务管理:通过编程方式来控制事务的开启、提交和回滚。这种方式适用于需要自定义事务行为或需要高级控制的事务场景

介绍一下Mybatis缓存

  • 一级缓存是指在同一个SqlSession中共享的缓存。当执行一个查询语句时,MyBatis会将查询结果缓存到一级缓存中。如果接下来执行相同的查询语句,MyBatis会先检查一级缓存中是否存在该结果,如果存在,则直接从缓存中获取结果,而不再访问数据库。一级缓存的作用范围是SqlSession级别的,当SqlSession关闭时,一级缓存也会被清空
  • 二级缓存是指在多个SqlSession之间共享的缓存。当执行一个查询语句时,MyBatis会将查询结果缓存到二级缓存中。如果接下来执行相同的查询语句,MyBatis会先检查二级缓存中是否存在该结果,如果存在,则直接从缓存中获取结果,而不再访问数据库。二级缓存的作用范围是Mapper级别的,可以被多个SqlSession共享。需要注意的是,二级缓存默认是关闭的,需要在配置文件中进行配置开启

介绍下索引

  • 索引是数据库管理系统中用于加快数据检索速度的数据结构。它可以根据指定的列或列的组合,对数据进行预排序,从而使得查询速度大大提高。索引的原理类似于图书的目录,通过索引可以快速定位到所需数据的位置

介绍下存储过程

  • 存储过程是一段预编译的SQL代码,可以在数据库服务器上执行。它通常用于封装复杂的查询逻辑,以提高性能并减少网络流量。存储过程可以通过参数传递数据,并可以接受输入和产生输出

示例:

CREATE PROCEDURE GetEmployeeName(IN employeeId INT, OUT employeeName VARCHAR(255))
BEGIN
    SELECT name INTO employeeName FROM employees WHERE id = employeeId;
END;

这个存储过程名为GetEmployeeName,它接受一个整数类型的参数employeeId作为输入,并将一个字符串类型的变量employeeName作为输出。在存储过程的主体中,使用SELECT语句从employees表中查询名字,并将结果赋值给employeeName变量。

要调用这个存储过程,可以使用CALL语句:

CALL GetEmployeeName(1, @name);
SELECT @name;

这里的1是作为输入参数传递给存储过程的员工ID,@name是一个用户定义的变量,用于接收存储过程的输出结果。通过SELECT语句可以查看存储过程返回的结果。

项目怎么进行重构,代码怎么进行优化?

  • 重构是指对代码进行修改以提高其质量和可读性,但不改变其外部行为的过程。重构的目标包括消除重复代码、简化条件语句和循环、优化算法、减少冗余代码等
  • 代码优化可以通过减少数据库查询次数、缓存结果、减少I/O操作等

ArrayList和LinkedList的比较

  • ArrayList和LinkedList都是Java中的List接口的实现,ArrayList是基于动态数组实现的,它在内存中以连续的方式存储元素,因此访问元素的速度很快。LinkedList是基于双向链表实现的,它在内存中以非连续的方式存储元素,因此插入和删除元素的速度很快

list与map的底层如何实现?

  • List接口主要包括ArrayList和LinkedList,它们的底层实现方式如上所述。Map接口主要包括HashMap和TreeMap,HashMap底层基于哈希表实现,哈希表是通过哈希函数将键映射到桶中的位置,从而快速定位到元素的位置。而TreeMap底层基于红黑树实现,红黑树是一种自平衡的二叉搜索树,它通过调整树的结构来保持平衡,从而保证查找、插入和删除的时间复杂度为O(log n)

说说对 MySQL 常见的两种存储引擎MyISAM与InnoDB的理解

  • MyISAM存储引擎的特点是简单、快速,但不支持事务和行级锁定。它适用于读多写少的系统,因为读操作是不需要加锁的,而写操作则需要整个表锁定。
  • InnoDB存储引擎的特点是支持事务、行级锁定和外键约束,适用于高并发读写的情况。它通过使用多版本并发控制(MVCC)来提高并发性能,并使用缓冲池来缓存磁盘上的数据,从而减少I/O操作

为什么索引能提高查询速度

  • 原因在于它对数据进行预排序,从而使得查询时不需要全表扫描,而是直接定位到满足条件的数据行。索引可以显著减少需要扫描的行数,特别是在数据量很大的情况下,没有索引可能需要扫描整个表,而有了索引只需要扫描索引本身和满足条件的行即可

什么是事务

  • 事务是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做。事务的目的是确保多个操作的一致性和完整性。在数据库中,事务通常用于保护数据的完整性和一致性,避免数据出现不一致的情况

为什么要用 redis 缓存

  • Redis是一种内存数据库,将数据存储在内存中,可以快速地访问和操作数据,从而提高应用程序的性能
  • Redis支持多种数据结构,如字符串、哈希表、列表、集合和有序集合等,可以方便地存储和操作数据
  • Redis提供了持久化机制,可以将数据保存到磁盘中,保证数据不会因为程序崩溃而丢失
  • Redis支持分布式缓存,可以用于构建高可用的应用程序

缓存的作用是将计算结果临时存储在高速存储介质中,以减少对底层数据源的访问,提高系统性能和吞吐量;可以减少IO操作和网络传输的开销,加快数据访问速度;可以大大提高系统的响应速度和并发能力

数据库优化应该从哪些方法考虑?

  1. 索引优化:合理地使用索引可以显著提高查询速度。对于常用的查询条件和排序字段,应该建立合适的索引
  2. SQL语句优化:编写高效的SQL语句可以提高查询速度。应该尽量避免全表扫描,合理地使用索引和优化查询语句
  3. 数据库分区:将数据库按照功能和数据类型进行分区,可以更好地组织和管理数据,提高查询效率
  4. 数据库复制:使用数据库复制可以提高系统的可用性和性能。当主库出现故障时,可以快速地切换到备用库
  5. 数据分片:对于海量数据和高并发场景,可以考虑使用数据分片技术,将数据分散到多个数据库实例中,提高系统的负载能力和可用性
  6. 控制事务的粒度:尽量减少事务的范围,避免长时间持有锁,以提高并发性能
  7. 适当使用缓存:将热点数据缓存起来,减少对数据库的访问次数,提高系统的响应速度

HashMap的底层原理是什么

  1. HashMap的底层原理是基于哈希表实现的。哈希表是一种根据键的哈希值来快速定位值的数据结构。在HashMap中,键值对被存储在一个数组中,通过对键的哈希值进行计算,得到一个索引,然后将键值对存储在对应索引的位置上。当需要获取值时,再根据键的哈希值计算出索引,并在数组中找到对应位置的值

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,介绍一些常见的优化措施

  1. 分表分库:将单表拆分为多个表或多个库,可以分散数据压力和提高并发处理能力。可以使用中间件来实现分表分库的操作
  2. 索引优化:针对常用的查询条件和排序字段建立合适的索引,可以提高查询效率。但要注意不要过度索引,否则会增加维护成本和降低写入性能
  3. SQL语句优化:编写高效的SQL语句可以提高查询速度。应该尽量避免全表扫描和减少锁竞争,合理地使用索引和优化查询语句
  4. 缓存数据:将经常访问的数据缓存到内存中,可以减少对数据库的访问次数,提高系统的性能和可用性。可以使用Redis等内存数据库来实现缓存功能

项目的开发流程是什么

  1. 项目规划:明确项目的目标、范围、时间计划、预算等
  2. 需求分析:对项目需求进行详细的理解和分析,确定项目的功能、性能、质量等要求
  3. 系统设计:根据需求分析结果,进行系统的总体设计,包括架构设计、数据库设计、界面设计等
  4. 编码开发:根据系统设计,进行编码开发工作,实现相应的功能和性能
  5. 测试验收:对开发完成的系统进行测试和验收,确保系统的质量符合要求
  6. 部署上线:将系统部署到生产环境中,进行上线操作
  7. 维护升级:对系统进行维护和升级,保证系统的稳定性和可用性

在项目中如何设计数据库

  1. 确定数据需求:明确项目中对数据的具体需求,包括数据的种类、格式、关系等
  2. 设计数据模型
  3. 选择合适的数据库管理系统:如MySQL、Oracle、SQL Server等
  4. 创建数据库表
  5. 定义字段和约束:约束包括主键约束、外键约束、非空约束等
  6. 创建索引:索引可以加快查询速度,但过多的索引会增加写操作的开销
  7. 设计关联关系:一对一、一对多、多对多
  8. 考虑事务处理,如果项目需要
  9. 测试和优化:验证设计的正确性和性能
  10. 备份和恢复设计:以防止数据丢失和灾难恢复
  11. 文档记录:编写数据库设计文档,记录设计的详细信息,以便于后续维护和使用

拦截器和过滤器的区别

过滤器(Filter)是Servlet容器提供的一种组件,用于对所有请求进行预处理和后处理;拦截器(Interceptor)是应用程序框架或容器提供的一种组件,用于对特定请求或方法进行预处理和后处理。过滤器具有较好的扩展性,而拦截器通常只能在特定框架或容器中使用

  • 实现方式:过滤器是基于Servlet规范的一种组件,由Servlet容器负责调用
    拦截器是基于AOP的一种组件,由应用程序框架或容器负责调用
  • 作用范围:过滤器可以作用于Web应用中的所有请求,包括静态资源请求和动态请求
    拦截器通常只作用于特定的Controller或Handler方法
  • 功能:过滤器可以对请求进行预处理和后处理,如身份验证、日志记录等。可以修改请求和响应的内容
    拦截器主要用于在Controller或Handler方法执行前中后进行一些额外的处理,如权限验证、日志记录等。也可以修改请求和响应的内容,也可以中断请求的执行
  • 调用顺序:都是根据配置顺序来确定的,先配置的先被调用
  • 扩展性:过滤器是基于Servlet规范的,可以与任何Servlet容器兼容,具有较好的扩展性
    拦截器通常是特定框架或容器提供的功能,只能在该框架或容器中使用,扩展性相对较差
  • 实现原理:过滤器是基于函数回调实现的,而拦截器则是基于Java的反射机制(动态代理)实现的
  • 使用范围:过滤器实现的是javax.servlet.Filter接口,这个接口是在Servlet规范中定义的,因此过滤器的使用依赖于Tomcat等容器,通常仅限于web程序中使用。
    而拦截器则是一个Spring组件,由Spring容器管理,并不依赖Tomcat等容器,可以单独使用
  • 拦截的请求范围:过滤器执行了两次,而拦截器只执行了一次

序列化和反序列化

  • 序列化:序列化是将对象转化为字节流的过程。在序列化过程中,对象的状态信息被转化为字节序列,可以被存储在文件中、传输到网络中或通过其他方式进行持久化。
    序列化可以将对象的状态信息包括属性值、字段值、关联关系等保存下来,以便在需要时进行恢复。
    序列化可以通过Java的ObjectOutputStream类或其他序列化框架(如JSON、XML等)来实现。
  • 反序列化:反序列化是将字节流转化为对象的过程。在反序列化过程中,字节序列被转化为对象的状态信息,可以重新创建出原始的对象。
    反序列化可以将存储在文件中、传输到网络中或通过其他方式持久化的对象重新恢复到内存中。
    反序列化可以通过Java的ObjectInputStream类或其他反序列化框架(如JSON、XML等)来实现。
  • 应用场景:分布式系统:在分布式系统中,对象可以在不同的节点之间进行传输和共享,通过序列化和反序列化可以将对象转化为字节流进行传输,再在接收端进行反序列化恢复成对象。
    缓存和持久化:将对象序列化后存储在缓存或数据库中,以便在需要时进行快速读取和恢复。
    远程调用:在远程调用中,可以将参数对象进行序列化后传输到远程服务器,再在服务器端进行反序列化后调用相应的方法。

需要注意的是,不是所有的对象都可以被序列化,只有实现了Serializable接口的对象才能进行序列化和反序列化。同时,序列化和反序列化的过程也可能存在版本兼容性的问题,需要注意版本的管理和控制。1

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永不掉发的陳不錯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值