java面试题整合

1.Java数据类型 ✅

Java是一种静态类型语言,它具有丰富的数据类型用于声明变量和方法返回类型。Java中的数据类型分为两类:原始数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)。

  1. 原始数据类型(Primitive Data Types):

    Java中的原始数据类型是直接存储数据值的简单数据类型,它们不是对象。原始数据类型包括以下8种:

    • byte:用于表示8位有符号整数,取值范围为 -128 到 127。

    • short:用于表示16位有符号整数,取值范围为 -32,768 到 32,767。

    • int:用于表示32位有符号整数,取值范围为 -2^31 到 2^31-1。

    • long:用于表示64位有符号整数,取值范围为 -2^63 到 2^63-1。

    • float:用于表示单精度浮点数,取值范围和精度较小。

    • double:用于表示双精度浮点数,取值范围和精度较大。

    • char:用于表示16位 Unicode 字符,取值范围为 '\u0000'(0)到 '\uffff'(65535)。

    • boolean:用于表示布尔值,只能取值为 true 或 false。

  2. 引用数据类型(Reference Data Types):

    引用数据类型用于引用对象,而不是直接存储实际数据。它们包括类、接口、数组以及Java中的预定义数据类型(如 String)等。引用数据类型存储的是对象在内存中的地址。

    • 类:通过关键字 class 声明,并实例化为对象。

    • 接口:通过关键字 interface 声明,并可以由类实现。

    • 数组:通过关键字 [] 声明,可以存储多个相同类型的数据元素。

    • String:Java提供了特殊的引用类型 String 用于处理字符串。

2.Lombok ✅

Lombok是Java开发中非常流行的一个开源工具,它可以通过注解来简化Java类的编写,减少样板代码(boilerplate code)的量,使代码更加简洁易读。Lombok能够自动生成getter、setter、构造函数、toString等常用方法,从而简化了Java类的定义。

要使用Lombok,你需要在项目中添加Lombok的依赖。通常,你可以通过在Maven或Gradle构建文件中添加以下依赖来实现:

Maven:


    org.projectlombok
    lombok
    1.18.20 
    provided

Gradle:

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.20' // 使用最新版本
}

添加了Lombok依赖后,你需要在IDE中安装Lombok插件(如果IDE没有预安装的话)。这样,IDE就能正确识别Lombok的注解并正确处理代码生成。

Lombok支持的常用注解及其用法包括:

  1. @Getter / @Setter: 自动生成类的getter和setter方法。

  2. @ToString: 自动生成toString方法,方便输出对象的内容。

  3. @EqualsAndHashCode: 自动生成equals和hashCode方法,用于比较对象的内容和哈希码。

  4. @NoArgsConstructor: 自动生成无参构造函数。

  5. @AllArgsConstructor: 自动生成全参构造函数。

  6. @RequiredArgsConstructor: 自动生成含有final@NonNull注解的成员变量的构造函数。

  7. @Data: 组合了@Getter@Setter@ToString@EqualsAndHashCode@RequiredArgsConstructor的功能。

  8. @Builder: 自动生成builder模式的构造器。

  9. @Value: 生成一个不可变的类,包含@Getter方法,适合用于值对象。

  10. @Slf4j: 自动生成一个名为logorg.slf4j.Logger变量,方便日志输出。

使用Lombok注解时,只需在类的顶部添加注解,Lombok会自动处理相应的代码生成。例如:

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
​
@Getter
@Setter
@ToString
public class Person {
    private String name;
    private int age;
}

以上代码相当于手动编写了一个有nameage字段的JavaBean,并且包含了getter、setter和toString方法。

请注意,尽管Lombok在开发中非常方便,但也应该谨慎使用。有时,过度使用Lombok可能会导致代码难以理解和维护,因为生成的代码可能隐藏在注解背后。正确使用Lombok,可以帮助你编写更简洁的Java代码,提高开发效率。

3.Swagger ✅

Swagger是一个用于构建、文档化和测试RESTful Web服务的开源工具。它允许开发人员在编写API时描述API的各种细节,并生成交互式的API文档,方便团队成员和其他开发者了解和调用API。Swagger的核心规范被称为OpenAPI规范。

使用Swagger可以有助于加快API开发和测试的过程,提高API的可读性和可维护性,并促进团队之间的协作。

以下是使用Swagger的一般步骤:

  1. 添加Swagger依赖: 首先,你需要在你的项目中添加Swagger相关的依赖。通常,你可以通过在Maven或Gradle构建文件添加Swagger的依赖来实现。

  2. 配置Swagger: 在你的项目中,你需要配置Swagger来定义API的信息,例如API的名称、版本、作者、描述等。这些信息将用于生成API文档。

  3. 编写API文档: 在编写API的代码时,你可以使用Swagger的注解来描述API的细节。这些注解包括@Api@ApiOperation@ApiParam等。使用这些注解可以指定API的路径、请求方法、参数、返回值等信息。

  4. 启动应用程序: 启动你的应用程序,并访问Swagger UI界面。Swagger UI界面会显示你编写的API文档,并允许你在界面上测试API的调用。

  5. 查看和测试API文档: 通过Swagger UI界面,你可以查看生成的API文档,并尝试调用API来测试其功能和返回结果。把我也带走把

下面是一个简单的使用Swagger的示例:

@RestController
@Api(tags = "用户管理API")
public class UserController {
​
    @Autowired
    private UserService userService;
​
    @ApiOperation("获取所有用户列表")
    @GetMapping("/users")
    public List getAllUsers() {
        return userService.getAllUsers();
    }
​
    @ApiOperation("根据用户ID获取用户信息")
    @ApiParam(name = "userId", value = "用户ID", required = true)
    @GetMapping("/users/{userId}")
    public User getUserById(@PathVariable long userId) {
        return userService.getUserById(userId);
    }
​
    @ApiOperation("创建新用户")
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
}

在上面的示例中,我们使用了@Api@ApiOperation注解来定义API的信息,使用@GetMapping@PostMapping注解定义API的路径和请求方法,并使用@ApiParam注解定义API的参数信息。

在你的项目中添加了Swagger依赖,并配置了Swagger后,你可以通过访问Swagger UI界面(通常在/swagger-ui.html路径下)来查看和测试API文档。在Swagger UI中,你可以看到你编写的API的详细信息,并可以通过界面上的按钮来测试API的调用。

总的来说,Swagger是一个非常有用的工具,可以帮助你更好地描述、文档化和测试API,提高API的可读性和可维护性。

4.git使用 安装 指令

Git是一个分布式版本控制系统,用于管理项目的源代码版本。要使用Git,首先需要在你的计算机上安装Git客户端。以下是安装Git并进行基本配置的指令:

  1. 安装Git:

    • Windows:访问Git官方网站 Git,下载Windows版本的Git安装程序,然后运行安装程序进行安装。

    • macOS:如果你使用Homebrew,可以在终端中运行 brew install git 来安装Git。否则,访问Git官方网站下载macOS版本的Git安装程序进行安装。

  • Linux:在终端中运行适合你Linux发行版的命令来安装Git。例如,对于Debian/Ubuntu系统,可以运行 sudo apt-get install git 进行安装。

  1. 配置Git:

在安装完Git后,你需要配置你的Git用户信息,这些信息将用于标识你的提交记录。在终端中运行以下指令:

  git config --global user.name "Your Name"  # 设置你的用户名
   git config --global user.email "youremail@example.com"  # 设置你的邮箱地址
  1. 检查安装:

    安装完成后,你可以在终端中运行 git --version 指令来检查Git是否成功安装,并查看Git的版本信息。

现在,你已经成功安装并配置了Git。接下来,你可以通过以下一些常用的Git指令来开始使用Git:

  • git init: 在当前目录创建一个新的Git仓库。

  • git clone : 克隆一个远程Git仓库到本地。

  • git add : 将文件添加到Git仓库的暂存区。

  • git commit -m "commit message": 提交暂存区的文件到Git仓库,并附上提交信息。

  • git push: 将本地的代码推送到远程仓库。

  • git pull: 从远程仓库拉取代码到本地。

  • git status: 显示工作区和暂存区的状态。

  • git log: 查看提交历史记录。

  • git branch: 显示当前分支及所有分支的列表。

  • git checkout : 切换到指定分支。

  • git merge : 将指定分支合并到当前分支。

这些是Git的一些基本指令,有助于你开始使用Git来管理你的项目的版本控制。在使用Git时,你还可以学习更多高级指令和技巧来更有效地使用Git。

5.Maven

Maven是一个Java项目管理工具,它使用Maven项目对象模型(POM)来管理项目的构建、测试和部署。Maven的构建过程由一系列预定义的生命周期和阶段(phase)组成,每个生命周期由一组阶段组成。下面是Maven的一些重要概念和常用指令:

  1. Maven生命周期:

    Maven包含三个主要的构建生命周期:

    • clean:清理项目,删除生成的目录和文件。

  • default(或build):构建项目包括编译、测试、打包和安装。

    • site:生成项目的站点文档。

  1. Maven生命周期阶段:

每个生命周期由一系列阶段组成。例如,default生命周期包含以下一些重要的阶段:validate、compile、test、package、install、deploy。你可以在特定的生命周期阶段执行插件目标。

  1. Maven三大坐标:

    Maven使用三个主要的坐标来唯一标识一个项目:

    • groupId:项目所属的组织或公司的唯一标识。

  • artifactId:项目在该组织中唯一标识的项目名。

    • version:项目的版本号。

  1. 常用Maven指令:

    • mvn clean: 清理项目,删除生成的目录和文件。

    • mvn compile: 编译项目。

    • mvn test: 运行项目的单元测试。

    • mvn package: 将项目打包成可分发的格式,如JAR、WAR等。

    • mvn install: 将项目安装到本地Maven仓库,供其他项目引用。

    • mvn deploy: 将项目部署到远程Maven仓库,供其他项目引用。

  • mvn site: 生成项目的站点文档。

  1. 安装Maven版本:

你可以从Maven官方网站(Maven – Welcome to Apache Maven)下载Maven的安装包,然后按照其指南进行安装。安装后,确保你的Maven命令被正确添加到系统的PATH环境变量中,这样你就可以在命令行中直接使用mvn指令了。

  1. 使用Maven:

要使用Maven管理项目,你需要在项目根目录下创建一个名为pom.xml的文件,它是Maven项目的核心配置文件。在pom.xml中,你可以定义项目的坐标、依赖、插件和构建配置等。

示例pom.xml文件:

  
       com.example
       my-project
       1.0.0
       
           
           
               org.springframework
               spring-core
               5.3.10
           
       
       
           
               
           
       
   

使用Maven指令如下:

  • mvn clean: 清理项目。

  • mvn compile: 编译项目。

  • mvn test: 运行项目的单元测试。

  • mvn package: 打包项目。

  • mvn install: 安装项目到本地Maven仓库。

  • mvn deploy: 部署项目到远程Maven仓库。

这些是Maven的一些基本概念和常用指令,希望能帮助你理解和使用Maven来管理你的Java项目。

6.ArrayList如何扩容 ✅

在Java中,ArrayList是一个动态数组,它能够自动扩容以适应元素的添加。当我们向ArrayList添加元素时,如果当前数组的容量不足以存储新的元素,ArrayList会自动进行扩容,以便容纳更多的元素。

ArrayList的扩容机制基于以下两个重要参数:

  1. 容量(Capacity): 容量是指ArrayList内部数组的大小,即能够存储元素的空间。它可以通过ArrayList的构造函数或ensureCapacity方法来指定初始值。

  2. 负载因子(Load Factor): 负载因子是指ArrayList在进行扩容时的一个比例因子。当ArrayList中的元素数量达到容量的负载因子比例时,会触发扩容操作。Java中默认的负载因子为0.75。

当向ArrayList添加元素时,如果当前元素数量已经达到容量的负载因子比例,ArrayList将进行扩容。扩容过程包括以下步骤:

  1. ArrayList会创建一个新的容量更大的数组(通常是原数组容量的两倍),用于存储新的元素。

  2. 将原数组中的元素逐个复制到新数组中。

  3. 新数组取代原数组成为ArrayList内部的存储数组。

由于扩容涉及到元素的复制,因此在插入大量元素时可能会带来一些性能开销。为了优化性能,可以考虑在添加大量元素之前,提前通过调用ensureCapacity方法设置好足够的容量,以避免过多的扩容操作。

示例代码:

import java.util.ArrayList;
​
public class ArrayListExample {
    public static void main(String[] args) {
        // 创建一个初始容量为10的ArrayList
        ArrayList numbers = new ArrayList<>(10);
​
        // 添加元素,此时容量为10,负载因子为0.75
        for (int i = 1; i <= 10; i++) {
            numbers.add(i);
        }
​
        // 继续添加元素,达到容量的负载因子比例,会触发扩容
        numbers.add(11);
​
        // 查看ArrayList的容量
        System.out.println("Capacity after adding 11th element: " + numbers.size());
    }
}

在上面的示例中,我们创建了一个初始容量为10的ArrayList,然后添加了11个元素。当添加第11个元素时,ArrayList触发了扩容,容量增加到了原来的两倍,即20。

7.IoC的作用以及原理 ✅

IoC(Inversion of Control)即控制反转,是一种设计模式,它的主要目的是解耦和管理应用程序的组件依赖关系。在传统的编程模式中,应用程序通过直接创建和管理对象之间的依赖关系,导致代码的耦合性很高,难以进行测试和维护。而IoC将对象的创建和依赖关系的管理交给容器来处理,从而实现了控制反转。

IoC的作用:

  1. 解耦和: IoC通过将组件的依赖关系交给容器管理,实现了组件之间的解耦和,使得代码更加灵活、可维护和可测试。

  2. 可重用性: 由于依赖关系被集中管理,组件的重用性得到了提高。

  3. 灵活性: 可以通过配置来决定组件之间的依赖关系,而不需要修改代码,从而使得应用程序更加灵活和可配置。

  4. 易于测试: IoC将依赖关系注入到组件中,可以轻松地使用模拟对象来进行单元测试。

  5. 集中化管理: IoC容器将应用程序的组件集中在一起管理,方便维护和管理。

IoC的原理:

IoC的核心原理是通过依赖注入(Dependency Injection)来实现控制反转。依赖注入是指将组件所依赖的其他组件注入到组件中,而不是组件自己去创建或查找依赖的组件。

在IoC容器中,通常有以下几个关键角色:

  1. 容器(Container): IoC容器负责管理应用程序中所有组件的生命周期和依赖关系。

  2. 组件(Component): 组件是应用程序中的基本构建块,每个组件都有自己的功能和责任。

  3. 依赖关系(Dependency): 组件可能依赖于其他组件,IoC容器负责将这些依赖注入到组件中。

  4. 注入点(Injection Point): 注入点是指组件中接收依赖注入的位置。

IoC容器通过读取配置文件或注解来确定组件之间的依赖关系,然后在应用程序启动时创建和管理这些组件。当一个组件需要依赖其他组件时,IoC容器会查找对应的依赖,并将其注入到组件的注入点中。

通常,IoC容器会使用构造函数注入、属性注入或方法注入等方式来实现依赖注入。

简而言之,IoC的原理就是将对象的创建和依赖关系的管理交给容器处理,从而实现了组件之间的解耦和灵活性。这样,应用程序的组件只需要关注自身的功能,而不需要关心如何创建和管理依赖的组件。

8.缓存问题 ✅

缓存是一种常见的性能优化技术,它将计算结果、数据或资源临时保存在高速存储器中,以便后续快速访问,从而减少对原始数据源或耗时操作的频繁访问,提高系统的响应速度和性能。

缓存问题主要涉及以下方面:

  1. 缓存一致性(Cache Coherency): 当使用缓存时,必须确保缓存中的数据与原始数据源的数据保持一致。否则,可能会导致数据不一致的问题。解决这个问题的常用方法是采用缓存失效机制,在原始数据发生变化时,及时使缓存失效,以便下一次访问时重新获取最新数据。

  2. 缓存雪崩(Cache Avalanche): 缓存雪崩是指在某个时间点,缓存中的大量数据同时失效或被清除,导致大量请求直接落到原始数据源上,造成数据库或服务器压力骤增,甚至引发系统崩溃。为了避免缓存雪崩,可以采用多级缓存、不同的缓存失效时间、随机化缓存失效时间等策略。

  3. 缓存击穿(Cache Miss): 缓存击穿是指一个非常热门的数据在缓存中过期失效后,大量请求直接访问原始数据源,导致请求处理速度下降。为了避免缓存击穿,可以采用加锁机制,当一个请求发现缓存失效时,它可以先尝试获取锁,然后再去数据库中加载数据,并将数据设置到缓存中,避免其他请求同时去加载同一份数据。

  4. 缓存穿透(Cache Penetration): 缓存穿透是指大量请求访问缓存中根本不存在的数据,导致请求直接访问原始数据源。为了避免缓存穿透,可以采用布隆过滤器等方法,在缓存中设置一个标记,用于标识某些数据不存在,从而避免对原始数据源的频繁访问。

  5. 缓存更新策略: 缓存中的数据可能在一段时间后变得过时,需要更新。更新缓存时,可以采用主动更新策略(比如定时刷新)或者被动更新策略(比如在获取数据时检查缓存是否过期并更新)。

  6. 缓存大小和过期策略: 缓存的大小和过期策略需要根据具体的应用场景来设置。缓存太小会导致频繁失效和替换,缓存太大则可能导致内存压力增加。过期策略可以根据数据的访问频率和重要性来设定,例如可以使用LRU(最近最少使用)策略或LFU(最少使用)策略。

综上所述,缓存是一个非常重要的性能优化技术,但同时也需要注意处理好缓存一致性、缓存雪崩、缓存击穿、缓存穿透等问题,以确保应用程序的稳定性和性能。

9.了解哪些锁 ✅

在并发编程中,锁是用于控制对共享资源的访问的机制。锁的使用可以确保多个线程之间对共享资源的访问是有序的,避免竞争条件和数据不一致的问题。

以下是一些常见的锁:

  1. ReentrantLock: 是Java提供的基于AQS(AbstractQueuedSynchronizer)的可重入锁。它支持公平和非公平两种模式,并且提供了更灵活的锁获取和释放方式。可以使用ReentrantLocklock()unlock()方法来控制临界区的访问。

  2. synchronized: 是Java内置的关键字,用于实现同步,也称为内部锁。synchronized可以用于修饰方法或代码块,确保同一时间只有一个线程访问被synchronized修饰的方法或代码块。

  3. ReadWriteLock: 读写锁是一种特殊的锁,用于解决读多写少的场景。ReadWriteLock允许多个线程同时获取读锁,但只允许一个线程获取写锁。

  4. StampedLock: 是Java 8引入的一种新的锁,它支持三种模式:读锁、写锁和乐观读。StampedLock提供了乐观读模式(tryOptimisticRead())来避免阻塞,但需要在后续检查中验证是否发生了写入。

  5. LockSupport: 是Java提供的用于创建锁和其他同步类的基本线程阻塞原语。LockSupport.park()和LockSupport.unpark()方法可以分别阻塞和唤醒线程。

  6. Condition: 是Java提供的基于锁的条件等待机制。Condition可以用于在多个线程之间进行通信,通过await()和signal()等方法来实现线程的等待和唤醒。

这些锁在不同的场景和应用中都有各自的优势和适用性。在使用锁时,需要根据具体的并发场景和需求来选择合适的锁机制,以保证线程安全和性能。同时,锁的使用也需要注意避免死锁和活锁等问题,合理设计锁的粒度和控制范围。

10.反射的作用

反射是Java编程语言的一项强大特性,它允许程序在运行时动态地获取和操作类的信息,包括类的字段、方法、构造函数等。反射使得程序可以在运行时探知和修改类的结构和行为,而不需要在编译时就确定类的具体信息,这为编写灵活、通用和可扩展的代码提供了支持。

反射的作用包括以下几个方面:

  1. 动态加载类: 反射允许程序在运行时根据需要动态地加载和使用类,这样可以避免在编译时将所有类都包含在代码中,从而减小应用程序的体积。

  2. 获取类信息: 反射可以获取类的完整结构信息,包括类的字段、方法、父类、接口等,使得程序可以在运行时了解类的属性和行为。

  3. 创建对象: 反射可以在运行时创建类的对象,而无需提前知道类的具体类型。这对于一些通用框架或插件系统非常有用。

  4. 调用方法: 反射可以在运行时动态调用类的方法,使得程序可以根据不同的条件选择不同的方法进行执行。

  5. 修改类信息: 反射允许程序在运行时修改类的字段值和方法内容,从而实现对类的动态修改和扩展。

  6. 支持通用代码和框架: 反射使得编写通用的代码和框架更加容易,因为它可以处理未知类型的对象和类。

尽管反射提供了很多灵活性和功能,但由于反射操作需要在运行时进行类型检查和访问权限检查,因此相较于常规的直接调用,反射会导致一些性能上的损失。因此,在使用反射时需要谨慎考虑性能问题,并合理权衡是否真正需要使用反射来解决问题。

11.TCP/IP体系结构 ✅

TCP/IP(Transmission Control Protocol/Internet Protocol)是一种网络通信协议族,它是互联网的核心协议。TCP/IP协议族采用分层的体系结构,将整个网络通信过程划分为多个层次,每个层次负责不同的功能。每一层通过定义接口来与上下层进行交互,从而实现了模块化和可扩展性。

TCP/IP体系结构通常被分为四个层次,自底向上分别是:

  1. 物理层(Physical Layer): 物理层是最底层,它负责传输原始的比特流(Bit)或电信号。它定义了传输媒介、电压规范、接口特性等。

  2. 数据链路层(Data Link Layer): 数据链路层建立在物理层之上,它负责在直接相连的节点之间传输数据。它通过物理地址(MAC地址)来标识设备,并提供了错误检测和纠正、流控制、帧同步等功能。

  3. 网络层(Network Layer): 网络层建立在数据链路层之上,它负责将数据包从源节点传输到目标节点。它使用IP地址来标识设备和网络,实现了路由选择、数据包转发和分段等功能。IPv4和IPv6是网络层最重要的协议。

  4. 传输层(Transport Layer): 传输层建立在网络层之上,它负责提供端到端的通信,确保数据可靠传输。最常用的传输层协议是TCP(Transmission Control Protocol),它提供可靠的、面向连接的通信;另外还有UDP(User Datagram Protocol),它提供不可靠的、无连接的通信。

在TCP/IP体系结构中,每一层的功能相对独立,通过定义标准接口来进行通信和交互。这种分层结构使得网络的设计、实现和维护更加方便和灵活,同时也有助于推动网络技术的发展和演进。

12.TCP和UDP的区别 ✅

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种常见的传输层协议,用于在计算机网络中传输数据。它们在功能、特点和适用场景上有很大的区别。

  1. 连接与无连接:

    • TCP是一种面向连接的协议,数据传输之前需要先建立连接,然后进行数据传输,传输完毕后再释放连接。TCP提供可靠的数据传输,确保数据按照正确的顺序到达目的地,并进行重传以确保数据的完整性。

    • UDP是一种无连接的协议,数据传输时不需要建立连接,直接将数据包发送到目的地。UDP不保证数据的可靠性和顺序,数据可能会丢失或乱序,但由于没有连接建立和维护的开销,UDP传输速度较快。

  2. 可靠性:

    • TCP提供可靠的数据传输。如果数据包丢失或损坏,TCP会进行重传,直到接收方确认收到正确的数据。

    • UDP不提供数据的可靠性保证。发送方将数据发送出去后,不会等待接收方的确认,也不会进行重传。

  3. 传输方式:

    • TCP提供面向字节流的传输方式。应用程序在发送数据时,TCP会将数据分割成合适的大小,传输到接收方后再重新组装。

    • UDP提供面向数据包的传输方式。应用程序发送的每个数据包都是独立的,接收方接收到的也是独立的数据包。

  4. 效率:

    • 由于TCP提供可靠性保证,它需要维护连接状态、进行数据重传等操作,因此在传输效率上相对较低。

    • UDP不提供可靠性保证,不需要进行连接建立和重传操作,因此传输效率较高。

  5. 适用场景:

    • TCP适用于对数据传输的可靠性要求较高的场景,如文件传输、电子邮件、Web页面等。

    • UDP适用于对数据传输的实时性要求较高,但对数据可靠性要求较低的场景,如视频流、音频流、实时游戏等。

综上所述,TCP和UDP在可靠性、传输方式、效率和适用场景等方面有明显的区别。选择使用哪种协议取决于具体的应用需求和性能要求。

13.TCP建立连接和断开连接的过程

TCP建立连接和断开连接的过程遵循三次握手和四次挥手的规则,确保通信的可靠性和稳定性。

TCP建立连接(三次握手):

  1. 客户端向服务器发送一个连接请求报文,其中包含一个SYN(Synchronize)标志位,表示请求建立连接,并选择一个初始序列号(sequence number)x。

  2. 服务器接收到连接请求后,向客户端发送一个响应报文,其中包含一个SYN标志位和一个ACK(Acknowledgment)标志位,表示确认收到客户端的请求。服务器还会选择一个初始序列号y。服务器的响应报文中的ACK标志位的值为x+1,表示确认收到客户端的连接请求,并告知客户端下一次发送数据时要从x+1开始编号。

  3. 客户端收到服务器的响应后,会向服务器发送一个确认报文,其中包含一个ACK标志位,值为y+1,表示确认收到服务器的响应。此时连接建立成功,客户端和服务器都可以开始发送数据。

TCP断开连接(四次挥手):

  1. 客户端向服务器发送一个连接释放报文,其中包含一个FIN(Finish)标志位,表示希望断开连接。

  2. 服务器收到连接释放报文后,会向客户端发送一个确认报文,其中包含一个ACK标志位,值为收到的序列号加1。

  3. 服务器在发送完数据后,也向客户端发送一个连接释放报文,其中包含一个FIN标志位,表示确认客户端的请求,希望断开连接。

  4. 客户端收到服务器的连接释放报文后,向服务器发送一个确认报文,其中包含一个ACK标志位,值为收到的序列号加1。此时,连接断开完成。

通过三次握手和四次挥手,TCP确保了连接的建立和断开是可靠的,避免了数据丢失和重复传输的问题。这种连接的建立和断开机制使得TCP成为可靠的传输协议,适用于需要数据可靠传输的场景,如文件传输、Web请求等。

14.如何判断对象死亡

在Java中,对象的死亡通常是由Java虚拟机的垃圾回收器(Garbage Collector,GC)来判断和处理的。Java中的垃圾回收机制通过检查对象的引用情况来判断一个对象是否存活,如果对象没有被引用,即没有任何强引用指向该对象,那么该对象就可以被判定为死亡,垃圾回收器将在合适的时机回收该对象的内存。

Java中判断对象死亡的主要方式是通过引用计数和可达性分析两种方式。

  1. 引用计数: 引用计数是一种简单的垃圾回收算法,它在对象中维护一个引用计数器,记录有多少个引用指向该对象。当对象的引用计数器为0时,即没有任何引用指向该对象,那么该对象就可以被判定为死亡。然而,在实际应用中,引用计数器算法很难解决循环引用的问题,因此Java并没有采用引用计数的垃圾回收算法。

  2. 可达性分析: 可达性分析是Java中主要采用的垃圾回收算法。通过可达性分析,垃圾回收器会从一组称为"GC Roots"的对象开始,递归地查找所有与"GC Roots"对象之间的引用链。如果一个对象在GC Roots对象之间没有任何引用链相连,那么该对象就可以被判定为不可达,即该对象不再被程序所使用,可以被垃圾回收器回收。

Java中的GC Roots对象包括:

  • 虚拟机栈中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈中JNI引用的对象

当一个对象不再可达时,垃圾回收器会在适当的时机(如在内存不足时或虚拟机空闲时)将该对象回收,释放其占用的内存资源。这样,Java的垃圾回收机制能够有效地管理内存,避免内存泄漏和程序运行过程中的内存溢出等问题。

15.HashMap如何扩容 ✅

在Java中,HashMap是一种常用的哈希表数据结构,用于存储键值对。当HashMap中的元素数量超过负载因子(Load Factor)和初始容量的乘积时,就会触发扩容操作。负载因子是HashMap用来衡量容量利用率的一个参数,通常设置为0.75。

HashMap的扩容过程如下:

  1. 当HashMap中的元素数量超过负载因子和初始容量的乘积时,HashMap会创建一个新的数组,新数组的大小为原数组大小的两倍(默认情况下)。

  2. 然后,HashMap会将原数组中的所有元素重新计算哈希值,并根据新数组大小的不同,放置到新数组的对应位置上。

  3. 扩容过程中,所有元素的顺序可能会发生变化,但HashMap会根据哈希值重新计算后的位置,确保所有键值对仍然能够正确地被访问。

  4. 在重新计算哈希值和移动元素的过程中,如果发现多个键值对哈希到了同一个位置,HashMap会使用链表或红黑树来处理冲突。

  5. 扩容完成后,原数组会被丢弃,释放相应的内存空间。

HashMap的扩容过程可能会比较耗时,因为需要重新计算哈希值和移动元素。但扩容是必要的,它可以保证HashMap的负载因子保持在一个合理的范围内,从而保持HashMap的高效性能。

需要注意的是,HashMap的扩容不是一次性完成的,而是逐步进行的。当元素数量增加时,HashMap会继续扩容,直到满足负载因子的条件。因此,在使用HashMap时,合理设置初始容量和负载因子是很重要的,可以减少扩容的频率和开销,提高HashMap的性能。

16.操作系统的作用

操作系统是计算机系统中最基本的系统软件之一,它是一组管理计算机硬件资源和提供应用程序运行环境的程序集合。

操作系统扮演着连接应用程序和计算机硬件之间的桥梁,它的作用包括但不限于以下几个方面:

  1. 资源管理: 操作系统负责管理计算机的硬件资源,包括处理器(CPU)、内存、存储器、外部设备(如硬盘、打印机、键盘、鼠标等)等。它分配和回收这些资源,确保它们有效地被应用程序使用。

  2. 进程管理: 操作系统管理计算机中运行的进程(程序的执行实例)。它负责创建、销毁、调度和同步进程,确保多个进程能够共享CPU资源,合理利用计算机的计算能力。

  3. 内存管理: 操作系统负责管理计算机的内存,即随机存取存储器(RAM)。它将进程加载到内存中,并为进程分配足够的内存空间,以保证应用程序的正常运行。

  4. 文件系统管理: 操作系统管理计算机的文件系统,负责对文件和目录的创建、读取、写入、删除等操作。它提供了文件的逻辑结构和物理存储之间的映射。

  5. 设备驱动程序: 操作系统提供设备驱动程序,用于与硬件设备进行通信。驱动程序使操作系统能够识别、控制和管理外部设备,如打印机、网络接口卡、显卡等。

  6. 用户接口: 操作系统为用户提供了与计算机交互的用户接口,如命令行界面(CLI)或图形用户界面(GUI)。用户可以通过这些界面执行操作系统和应用程序。

  7. 安全管理: 操作系统负责保护计算机系统和数据的安全。它通过权限管理、身份认证、文件访问控制等机制,防止未经授权的访问和恶意操作。

  8. 错误检测和处理: 操作系统监控计算机系统的运行状态,及时检测和处理硬件错误、应用程序错误和系统故障,以保证系统的稳定性和可靠性。

总的来说,操作系统在计算机系统中起到了重要的桥梁作用,它为应用程序提供了运行环境和访问硬件资源的接口,使得计算机能够高效、稳定地运行各种应用程序。

17.操作系统的分类 ✅

操作系统可以根据其功能、用途、支持的硬件平台等多种标准进行分类。

以下是一些常见的操作系统分类方式:

  1. 单用户单任务操作系统: 这种操作系统只允许单个用户同时运行一个程序。例如,早期的DOS(Disk Operating System)就是一个单用户单任务操作系统。

  2. 单用户多任务操作系统: 这种操作系统允许单个用户同时运行多个程序,并通过任务切换机制在多个任务之间进行切换。例如,Windows和Mac OS就是单用户多任务操作系统。

  3. 多用户操作系统: 这种操作系统允许多个用户同时访问和使用计算机系统,并在用户之间进行任务切换。例如,UNIX和Linux是典型的多用户操作系统。

  4. 分时操作系统: 分时操作系统是一种多用户操作系统,它通过时间片轮转方式为每个用户分配CPU时间,使得每个用户感觉到他们在独占使用计算机。

  5. 实时操作系统: 实时操作系统用于处理对时间敏感的应用程序,要求系统能够在规定的时间范围内响应事件。实时操作系统根据响应时间要求分为硬实时操作系统和软实时操作系统。

  6. 嵌入式操作系统: 嵌入式操作系统是专门为嵌入式系统设计的,通常运行在资源受限的设备上,如手机、路由器、智能家电等。

  7. 网络操作系统: 这种操作系统主要用于网络设备,如路由器、交换机等,以支持网络通信和数据转发。

  8. 分布式操作系统: 分布式操作系统是运行在分布式计算环境中的操作系统,能够管理和协调多台计算机的资源和任务。

  9. 实验操作系统: 实验操作系统是用于学术研究和实验目的的操作系统,通常用于研究操作系统的设计和性能优化。

这些分类方式并不是互相排斥的,实际上,很多操作系统同时具备多种特性和功能。操作系统的分类可以帮助我们了解不同类型的操作系统在不同应用场景下的特点和优势。

18.utf8和utf8mb4的区别✅

UTF-8和UTF-8mb4都是Unicode字符编码的一种实现方式,用于支持多种字符集,包括ASCII字符集和其他语言的字符。

  1. UTF-8: UTF-8是一种变长的字符编码,它使用1到4个字节表示一个字符。在UTF-8编码中,ASCII字符(U+0000至U+007F)使用1个字节表示,而其他Unicode字符使用2到4个字节表示。UTF-8编码最初只支持1到3个字节表示字符,后来扩展为支持4个字节表示字符(U+10000至U+10FFFF),以满足更多字符集的需求。

  2. UTF-8mb4: UTF-8mb4也是一种UTF-8编码,但在实现上,它将所有Unicode字符都使用4个字节来表示。这样,UTF-8mb4可以完全支持所有的Unicode字符,包括一些特殊的表情符号、罕见字符和辅助平面字符等。

区别:

  • 最显著的区别是,UTF-8mb4可以支持更多的Unicode字符,而UTF-8只能支持部分Unicode字符。

  • UTF-8mb4编码的字符在存储和传输时会占用更多的字节,因为所有字符都使用4个字节表示,而UTF-8根据字符的Unicode码点使用1到4个字节表示。

在数据库中,特别是MySQL数据库中,使用UTF-8mb4编码非常重要,因为UTF-8mb4可以完全支持所有的Unicode字符,而UTF-8可能无法正确存储一些特殊字符,导致乱码或截断。在存储和处理包含特殊字符的文本数据时,务必使用UTF-8mb4编码。

19.int和Integer的区别

intInteger是Java中用于表示整数类型的两种数据类型

它们有以下几个主要区别:

  1. 数据类型:

    • int是Java的原始数据类型(Primitive Data Type),它是用于表示整数的基本类型,直接存储在栈内存中,不是对象。

    • Integer是Java中的包装类(Wrapper Class)之一,它是int类型的包装器,可以将int类型转换为对象,并提供了一些实用的方法来处理整数。

  2. 空值处理:

    • int是基本数据类型,不能表示为null,即使不赋值也会有默认值0。

    • Integer是对象,可以为null,可以用于表示一个整数对象不存在或未赋值的情况。

  3. 装箱和拆箱:

    • 装箱是将原始数据类型转换为对应的包装类对象,例如将int转换为Integer,可以使用Integer.valueOf(int)方法或直接赋值Integer i = 10;

    • 拆箱是将包装类对象转换为原始数据类型,例如将Integer转换为int,可以使用Integer.intValue()方法或直接赋值int x = i;

  4. 性能:

    • int是原始数据类型,存储在栈上,读写速度较快,占用的内存也较少。

    • Integer是对象,存储在堆上,由于需要额外的对象开销和垃圾回收,性能相对较低,并且会占用更多的内存空间。

由于int是基本数据类型,而Integer是对象,它们在使用时需要注意自动拆箱和装箱的问题,避免出现不必要的装箱和拆箱操作,以提高程序的性能和减少内存开销。在一些情况下,需要使用Integer对象,例如将整数存储在集合类中(如List、Map等)或作为方法参数时,因为集合类只能存储对象,不能存储基本数据类型。但在其他情况下,尽量使用int,避免不必要的装箱和拆箱操作。

20.where和having的区别 ✅

在SQL中,WHEREHAVING是用于过滤数据的两个关键字,

它们有以下几个主要区别:

  1. 用途:

    • WHERE用于在查询时对行进行过滤,它在数据从表中检索出来之前进行条件筛选。

    • HAVING用于在查询结果已经得到之后对分组进行过滤,通常和GROUP BY一起使用,在聚合查询时起到条件筛选的作用。

  2. 使用位置:

    • WHERE子句通常在SQL查询中位于FROM子句和GROUP BY子句之间。

    • HAVING子句通常在SQL查询中位于GROUP BY子句和ORDER BY子句之间,用于对分组进行条件过滤。

  3. 过滤对象:

    • WHERE用于对表的行进行过滤,根据条件筛选出满足条件的行。

    • HAVING用于对聚合函数的结果进行过滤,根据条件筛选出满足条件的分组。

  4. 聚合函数:

    • WHERE不能用于对聚合函数(如SUM、COUNT、AVG等)进行过滤,因为WHERE在数据行还未聚合之前进行筛选。

    • HAVING通常和GROUP BY一起使用,用于对聚合函数的结果进行过滤,筛选出满足条件的分组。

  5. 性能:

    • WHERE子句在查询时进行条件过滤,可以减少参与聚合计算的数据量,因此性能相对较高。

    • HAVING子句在查询结果已经得到之后进行条件过滤,需要对聚合函数的结果进行计算,性能相对较低。

综上所述,WHEREHAVING在功能和用途上有明显的区别。WHERE用于在查询时对行进行过滤,而HAVING用于在查询结果已经得到之后对分组进行过滤。在使用时,需要根据具体的查询需求选择合适的关键字,以确保查询结果符合预期。

21.ThreadLocal的作用及原理 ✅

ThreadLocal是Java中一个线程局部变量的工具类。它提供了一种在多线程环境下,每个线程都有自己独立的变量副本的机制。这意味着每个线程可以独立地访问自己的变量副本,而不会互相干扰。

作用:

ThreadLocal的主要作用是为线程提供一个线程私有的变量,每个线程都可以独立地修改自己的变量副本,互不影响。它适用于一些线程共享的对象,但每个线程都需要拥有独立副本的场景。常见的用途包括:

  1. 线程安全: 将非线程安全的对象转换为线程安全的,通过ThreadLocal将其变为每个线程私有的对象,避免了线程之间的竞争条件和同步操作。

  2. 上下文信息传递: 在多线程任务执行过程中,可以通过ThreadLocal传递一些上下文信息,而不需要通过方法参数传递。

  3. 数据源管理: 在数据库连接池等资源管理中,可以使用ThreadLocal来维护每个线程的数据库连接,确保线程间的数据库连接隔离。

原理:

ThreadLocal通过维护一个特殊的ThreadLocalMap来实现线程间的数据隔离。每个Thread都有一个ThreadLocalMap实例,ThreadLocalMap的键为ThreadLocal对象,值为该线程对应的变量副本。

当使用ThreadLocalget()方法获取变量时,它会先获取当前线程的ThreadLocalMap,然后根据ThreadLocal对象作为键查找对应的变量副本。

当使用ThreadLocalset()方法设置变量时,它也会获取当前线程的ThreadLocalMap,然后将ThreadLocal对象作为键,变量值作为值存储到ThreadLocalMap中。

当线程结束或者不再需要ThreadLocal存储的变量时,为了防止内存泄漏,应当手动调用remove()方法,将ThreadLocal从当前线程的ThreadLocalMap中移除。

需要注意的是,ThreadLocal并不能解决共享对象的线程安全问题,它只是为每个线程提供了独立的变量副本。在使用ThreadLocal时,仍然需要注意线程安全问题,避免多线程访问同一个共享对象而导致的竞争条件。

22.Error和Exception的区别

在Java中,ErrorException都是用于表示程序运行时出现的问题的类,它们继承自Throwable类,但在使用和处理上有一些区别。

Error:

  • Error表示严重的错误,通常是由于虚拟机或系统本身的问题导致的,例如OutOfMemoryError(内存不足错误)和StackOverflowError(栈溢出错误)等。

  • Error一般不应该被程序显式地捕获和处理,因为这些错误表示程序已经无法恢复,并且它们不是由应用程序代码引起的。

  • 通常情况下,Error表示的是虚拟机或系统级的问题,应用程序很难处理这些问题,通常只能终止程序的执行。

Exception:

  • Exception表示一般性的异常情况,通常是由应用程序的代码引起的,例如NullPointerException(空指针异常)和IOException(输入输出异常)等。

  • Exception是可以被程序显式地捕获和处理的,开发人员可以通过try-catch块来处理异常,使程序在出现异常时能够继续执行而不中断。

  • Exception分为两种类型:已检查异常(checked exception)和未检查异常(unchecked exception)。已检查异常是在方法签名中显式声明的,调用这些方法时必须进行异常处理;未检查异常是RuntimeException及其子类,通常是由程序逻辑错误引起的,不需要在方法签名中声明异常。

综上所述,ErrorException都是Throwable的子类,但它们在用途和处理方式上有很大的区别。Error表示虚拟机或系统级的严重问题,通常无法恢复,不应该被处理;而Exception表示一般性的异常情况,可以被程序显式地捕获和处理,使程序能够继续执行。

23.GC的类型

在Java中,垃圾回收(Garbage Collection,GC)是自动管理内存的机制,它可以自动回收不再使用的对象,释放其占用的内存空间。Java虚拟机(JVM)提供了不同类型的垃圾回收器,每种垃圾回收器都有不同的算法和特点,以适应不同场景的内存管理需求。

常见的Java垃圾回收器类型包括:

  1. Serial Garbage Collector(串行回收器): Serial GC是一种单线程的垃圾回收器,它在回收垃圾时会暂停所有应用线程。它适用于单核CPU或低配置的环境,因为它的暂停时间较长,对于小型应用或客户端应用来说是一个不错的选择。

  2. Parallel Garbage Collector(并行回收器): Parallel GC是一种多线程的垃圾回收器,它可以利用多核CPU并行回收垃圾,减少了垃圾回收的暂停时间。它适用于多核CPU和大内存的服务器应用,可以在减少暂停时间的同时保证高吞吐量。

  3. CMS Garbage Collector(并发标记清除回收器): CMS GC是一种以获取最短回收停顿时间为目标的垃圾回收器。它使用多个线程并发地标记和清除垃圾,尽量减少垃圾回收过程中的暂停时间。CMS GC适用于对延迟要求较高的应用场景,但在并发标记和清除过程中可能会产生一些额外的CPU负担。

  4. G1 Garbage Collector(G1回收器): G1 GC是一种以获取最短回收停顿时间和高吞吐量为目标的垃圾回收器。它将堆内存分成多个区域(Region),在进行垃圾回收时优先回收垃圾最多的区域,从而减少停顿时间。G1 GC适用于大内存应用和对低延迟要求较高的应用场景。

  5. Z Garbage Collector(Z回收器): Z GC是一种实验性的垃圾回收器,旨在实现低延迟和高吞吐量的垃圾回收。它使用了一些新的技术,例如柔性的内存区域和可并发的垃圾回收算法,以提供更好的性能。

需要根据具体的应用场景和硬件配置来选择合适的垃圾回收器,以确保程序在高效和稳定地运行。在Java 9及以后的版本中,G1 GC成为了默认的垃圾回收器。但在一些特殊场景下,可能需要根据实际情况来选择其他类型的垃圾回收器。

24.谈谈你对GC的理解

垃圾回收(Garbage Collection,GC)是一种自动化的内存管理机制,在编程语言中,特别是Java和其他托管语言中,它负责自动地识别和回收不再使用的内存资源,以避免内存泄漏和内存溢出等问题。

我的理解是,垃圾回收是一种代替开发人员手动管理内存的方式,它能够自动跟踪对象的引用情况,并在对象不再被引用时回收其占用的内存。在传统的编程语言中,开发人员需要手动分配和释放内存,而在使用垃圾回收机制的语言中,如Java,内存的管理由垃圾回收器自动完成。

垃圾回收器的工作原理大致如下:

  1. 标记阶段: 垃圾回收器会从根对象(通常是全局变量或栈中的对象)开始遍历对象引用链,标记所有可达的对象。所有未被标记的对象被认为是垃圾。

  2. 清除阶段: 在标记阶段之后,垃圾回收器会对堆中的未被标记的对象进行清除,释放这些对象所占用的内存空间。

  3. 压缩阶段(可选): 在清除阶段之后,一些垃圾回收器还可能进行内存碎片整理,将存活的对象紧凑排列,以便更好地利用内存。

垃圾回收的优势是可以避免常见的内存管理错误,例如内存泄漏和释放后的访问。它减轻了开发人员的负担,使得开发者可以更关注业务逻辑而不是手动内存管理。然而,垃圾回收并不是完美的,它有可能导致一些不可预测的延迟,因为在进行垃圾回收时,应用程序可能会暂停一段时间。

为了最大程度地发挥垃圾回收的优势并提高程序的性能,开发人员需要了解垃圾回收器的不同类型、配置参数和最佳实践,并在设计和编写代码时避免创建大量的临时对象和不必要的对象引用,以尽量减少垃圾回收的压力。

25.拦截器

拦截器(Interceptor)是一种常见的软件设计模式,在计算机编程中用于截获、拦截和处理请求或操作。在不同的编程环境中,拦截器有不同的应用场景和实现方式。

以下是拦截器的一般概念和在某些具体框架中的应用:

一般概念:

  • 拦截器允许在请求或操作的不同阶段插入自定义的处理逻辑,从而实现对请求或操作的增强、修改、记录或验证等功能,而无需修改原始代码。

  • 拦截器通常采用责任链模式(Chain of Responsibility)实现,多个拦截器可以形成一个链,每个拦截器都可以在处理完成后决定是否将控制权传递给下一个拦截器。

应用场景:

  • Web开发框架中的拦截器: 在Web开发中,拦截器常用于在请求到达控制器(或处理器)之前或之后执行一些公共逻辑。例如,可以用拦截器实现身份认证、权限检查、日志记录等功能。

  • AOP(面向切面编程): AOP是一种编程范式,拦截器在AOP中起到了重要作用。它可以在方法执行前、执行后、抛出异常时等切入点,执行一些横切逻辑,如事务管理、性能监控等。

  • Java中的拦截器: 在Java中,拦截器可以通过代理模式实现。例如,Java中的动态代理可以用拦截器来实现对方法调用的拦截和增强。

  • 消息中间件: 在消息中间件中,拦截器可以截获和处理消息,进行消息过滤、转换或路由等操作。

不同编程环境和框架对拦截器的实现方式和命名可能有所不同,但其核心概念都是相似的,即通过拦截器可以在请求或操作的不同阶段进行处理,增强程序的功能和灵活性。

26.慢SQL如何调优

调优慢SQL是优化数据库性能的重要一环。慢SQL通常指执行时间较长或者频繁执行的SQL语句,可能导致数据库性能下降或响应时间延长。

以下是调优慢SQL的一些常用方法:

  1. 查看执行计划: 分析SQL执行计划是调优的第一步。通过EXPLAIN命令(在MySQL中)或数据库提供的其他执行计划工具,可以查看SQL的执行计划,了解SQL语句的执行路径和操作顺序,从而找出可能导致性能问题的部分。

  2. 索引优化: 优化数据库表的索引可以显著提高SQL查询性能。确保表上的关键字段建立了适当的索引,以加快检索速度。

  3. 避免全表扫描: 尽量避免使用没有索引的字段作为查询条件,这会导致数据库执行全表扫描,而全表扫描通常是非常耗时的。

  4. 优化查询条件: 确保SQL查询条件能够充分利用索引,并且尽量限制返回的数据量,可以通过添加条件或者优化查询语句来实现。

  5. 分页查询优化: 对于大数据量的分页查询,可以使用基于游标或类似方法,避免将所有结果加载到内存中,从而减少内存消耗和查询时间。

  6. 使用连接查询: 在某些情况下,使用连接查询(JOIN)代替子查询或多次单独查询可以提高查询效率。

  7. 优化子查询: 子查询的性能通常较差,可以考虑使用临时表或其他优化手段来替代复杂的子查询。

  8. 缓存查询结果: 对于相对稳定的查询结果,可以考虑使用缓存机制,将查询结果缓存起来,减少数据库访问次数。

  9. 定期清理历史数据: 对于历史数据,可以定期清理或归档,避免数据库表过大导致查询性能下降。

  10. 数据库参数调整: 根据数据库的实际情况,调整数据库参数,如缓冲区大小、连接数等,以优化数据库的性能。

调优慢SQL是一个迭代过程,需要不断尝试不同的优化方法,并进行性能测试和监控。同时,对于复杂的SQL优化,也可以借助一些数据库性能分析工具来辅助分析和调优。

27.SQL优化的经验

SQL优化是一个复杂且需要实践经验的过程。

以下是一些SQL优化的经验和常见的优化策略:

  1. 合理使用索引: 确保表上的关键字段建立了适当的索引,以加快查询速度。避免全表扫描,尽量使用索引来优化查询条件。

  2. 避免SELECT *: 尽量避免使用SELECT *,而是明确指定需要的字段。只选择所需的字段可以减少数据库的数据传输量,提高查询性能。

  3. 优化查询条件: 确保查询条件能够充分利用索引,并且尽量限制返回的数据量。在使用LIKE操作时,避免在模式开头使用通配符,这样可以更好地利用索引。

  4. 使用连接查询: 在某些情况下,使用连接查询(JOIN)代替子查询或多次单独查询可以提高查询效率。

  5. 优化子查询: 子查询的性能通常较差,可以考虑使用临时表或其他优化手段来替代复杂的子查询。

  6. 避免使用SELECT DISTINCT: 使用SELECT DISTINCT会对查询结果进行排序和去重,性能较差。如果不是必要的,尽量避免使用。

  7. 分页查询优化: 对于大数据量的分页查询,可以使用基于游标或类似方法,避免将所有结果加载到内存中,从而减少内存消耗和查询时间。

  8. 定期清理历史数据: 对于历史数据,可以定期清理或归档,避免数据库表过大导致查询性能下降。

  9. 使用合适的数据类型: 在设计数据库时,选择合适的数据类型可以减少存储空间,提高查询性能。

  10. 缓存查询结果: 对于相对稳定的查询结果,可以考虑使用缓存机制,将查询结果缓存起来,减少数据库访问次数。

  11. 数据库参数调整: 根据数据库的实际情况,调整数据库参数,如缓冲区大小、连接数等,以优化数据库的性能。

  12. 使用批量操作: 在需要插入或更新大量数据时,使用批量操作可以减少与数据库的交互次数,提高效率。

  13. 避免频繁提交事务: 在使用事务时,避免频繁地提交事务,可以减少数据库日志写入,提高性能。

总体来说,SQL优化需要综合考虑数据库结构、查询条件、索引、数据量等因素,不同的场景可能需要不同的优化策略。最佳的优化方案往往需要通过实际测试和性能监控来验证,不断迭代改进。另外,理解数据库的执行计划和查询性能分析工具的使用也是进行SQL优化的重要技能。

28.SQL的书写顺序和执行顺序

在SQL语句中,书写顺序和执行顺序是不同的。SQL语句的书写顺序是由关键字和表达式组成的,而SQL语句的执行顺序是由数据库查询优化器决定的,它会根据语句的逻辑含义和表的索引等信息来决定最优的执行路径。

SQL语句的书写顺序:

在书写SQL语句时,通常遵循以下一般的顺序:

  1. SELECT:指定要查询的列。

  2. FROM:指定要查询的表。

  3. JOIN:指定连接查询的表(如果有)。

  4. WHERE:指定查询条件。

  5. GROUP BY:指定分组字段。

  6. HAVING:指定分组条件。

  7. ORDER BY:指定排序字段。

  8. LIMIT/OFFSET:指定查询结果的限制和偏移(可选)。

SQL语句的执行顺序:

SQL查询的执行顺序并不一定按照书写顺序执行,数据库优化器会根据查询的复杂性和表的索引情况来决定实际的执行路径。

一般来说,SQL查询的执行顺序如下:

  1. FROM:首先从FROM子句中指定的表中获取数据。

  2. JOIN:如果有连接操作,进行表的连接。

  3. WHERE:根据WHERE子句中的条件进行过滤,只保留满足条件的行。

  4. GROUP BY:如果有分组操作,按照指定的分组字段进行分组。

  5. HAVING:根据HAVING子句中的条件进行分组过滤。

  6. SELECT:根据SELECT子句中指定的列,生成查询结果。

  7. ORDER BY:如果有排序操作,按照指定的排序字段进行排序。

  8. LIMIT/OFFSET:最后根据LIMIT/OFFSET子句限制查询结果的数量和偏移。

需要注意的是,数据库优化器可能会对SQL查询进行重写或优化,以提高查询性能。因此,实际执行的顺序可能与书写顺序和上述执行顺序有所不同。了解SQL查询的执行顺序可以帮助我们更好地理解查询的效率和性能,同时也可以帮助我们进行SQL的调优。

29.有哪些关于SQL的好习惯

良好的SQL习惯有助于提高代码的可读性、可维护性和性能。

以下是一些关于SQL的好习惯:

  1. 使用格式化和缩进: 格式化SQL语句并使用适当的缩进,使代码易读。适当的缩进可以显示查询的逻辑结构,方便理解。

  2. 明确指定字段: 尽量避免使用SELECT *,而是明确指定需要查询的字段。这样可以减少数据传输量,提高查询效率。

  3. 使用合适的数据类型: 在设计数据库时,选择合适的数据类型可以减少存储空间,提高查询性能。

  4. 使用注释: 使用注释解释复杂查询的逻辑、用途和特殊情况,方便其他开发人员理解和维护代码。

  5. 避免使用SELECT DISTINCT: 使用SELECT DISTINCT会对查询结果进行排序和去重,性能较差。如果不是必要的,尽量避免使用。

  6. 优化查询条件: 确保查询条件能够充分利用索引,并且尽量限制返回的数据量。在使用LIKE操作时,避免在模式开头使用通配符,这样可以更好地利用索引。

  7. 使用连接查询: 在某些情况下,使用连接查询(JOIN)代替子查询或多次单独查询可以提高查询效率。

  8. 避免频繁提交事务: 在使用事务时,避免频繁地提交事务,可以减少数据库日志写入,提高性能。

  9. 分页查询优化: 对于大数据量的分页查询,可以使用基于游标或类似方法,避免将所有结果加载到内存中,从而减少内存消耗和查询时间。

  10. 定期清理历史数据: 对于历史数据,可以定期清理或归档,避免数据库表过大导致查询性能下降。

  11. 备份和恢复数据: 定期备份数据库,确保数据的安全性和可恢复性。

  12. 数据库参数调整: 根据数据库的实际情况,调整数据库参数,如缓冲区大小、连接数等,以优化数据库的性能。

  13. 测试和监控: 对SQL进行性能测试和监控,以确保查询性能满足需求,并及时发现和解决性能问题。

这些好习惯适用于大多数SQL代码的编写,可以帮助开发人员写出高效、易读和易维护的SQL代码。同时,根据具体情况,还可以根据数据库的特性和业务需求,采用更加针对性的优化措施。

30.&和&&的区别 ✅

&&& 都是Java中的逻辑运算符,用于执行与运算。

它们之间有以下区别:

1. 适用类型:

  • &:适用于所有的整数类型(int, short, byte, long, char) 和布尔类型 (boolean)。

  • &&:仅适用于布尔类型 (boolean)。

2. 短路特性:

  • &:不具有短路特性。无论左边的表达式结果是 true 还是 false,右边的表达式都会被计算。

  • &&:具有短路特性。如果左边的表达式结果是 false,右边的表达式将不会被计算,因为整个表达式的结果已经确定为 false。

3. 使用场景:

  • &:通常用于位运算,比如对数字的二进制进行位与操作。

  • &&:通常用于逻辑运算,比如条件判断和逻辑组合。

4. 逻辑运算结果:

  • &:无论左右两边的表达式结果如何,都会计算并返回布尔类型的结果。

  • &&:如果左边的表达式结果为 false,将不会计算右边的表达式,直接返回 false。只有左边的表达式结果为 true 时,才会计算并返回右边表达式的结果。

示例:

int a = 10;
int b = 5;
boolean condition1 = (a > b) & (a > 0);  // true & true,结果为 true
boolean condition2 = (a < b) & (b > 0);  // false & true,结果为 false
​
boolean condition3 = (a > b) && (a > 0);  // true && true,结果为 true
boolean condition4 = (a < b) && (b > 0);  // false && true,结果为 false

在实际应用中,通常推荐使用 && 运算符,因为它具有短路特性,可以提高代码的执行效率。当逻辑表达式中包含耗时较长的计算或方法调用时,短路特性可以避免不必要的计算,从而优化代码性能。只有在特定的位运算场景下,才使用 & 运算符。

31.HTTP和HTTPS ✅

HTTP(Hypertext Transfer Protocol)和HTTPS(Hypertext Transfer Protocol Secure)是用于在网络中传输数据的两种协议。

HTTP:

  • HTTP是一种无状态的、无连接的协议,基于客户端-服务器模型,用于在Web浏览器和Web服务器之间传输数据。

  • HTTP数据传输是明文的,不对数据进行加密,因此容易被窃听和篡改。

  • HTTP默认使用80端口进行通信。

HTTPS:

  • HTTPS是HTTP的安全版本,通过使用SSL(Secure Socket Layer)或TLS(Transport Layer Security)协议对传输的数据进行加密和认证,以确保数据的安全性和完整性。

  • HTTPS在HTTP的基础上增加了加密和身份验证机制,通过数字证书来验证服务器的身份,防止中间人攻击。

  • HTTPS默认使用443端口进行通信。

主要区别:

  1. 安全性: HTTPS比HTTP更安全,因为它通过加密和身份验证来保护数据的传输。HTTP传输的数据是明文的,而HTTPS传输的数据是加密的,即使被截获,也很难解密。

  2. 协议: HTTP是标准的HTTP协议,而HTTPS在HTTP的基础上添加了SSL或TLS协议。

  3. 默认端口: HTTP默认使用80端口,而HTTPS默认使用443端口。

  4. 应用场景: HTTP通常用于普通的网页浏览和一些对安全性要求不高的场景。而在涉及敏感信息(例如信用卡信息、登录凭证等)传输的场景,应该使用HTTPS来保证数据的安全。

  5. 性能: HTTPS相比HTTP会增加一定的计算和网络开销,因为要进行加密和解密操作,可能会对性能产生一定影响。但随着计算机硬件和网络技术的提升,HTTPS的性能问题逐渐减少。

在现代互联网中,保护用户数据的安全和隐私至关重要。因此,HTTPS已经成为许多网站的标准协议,特别是涉及敏感信息的网站,如银行、电子商务等。

32.常见的HTTP状态码 ✅

HTTP状态码是服务器在响应请求时返回给客户端的一种状态标识,用于表示请求的处理结果。

常见的HTTP状态码包括以下几类:

  1. 1xx:信息性状态码(Informational)

    • 100 Continue:服务器已收到请求的初始部分,客户端应继续请求。

    • 101 Switching Protocols:服务器要求客户端切换协议,例如从HTTP到WebSocket。

  2. 2xx:成功状态码(Successful)

    • 200 OK:请求成功,一般用于GET和POST请求。

    • 201 Created:请求已经被实现,并创建了新的资源。

    • 204 No Content:请求成功,但响应报文中没有实体的主体部分。

  3. 3xx:重定向状态码(Redirection)

    • 301 Moved Permanently:永久性重定向,请求的资源已被永久移动到新位置。

    • 302 Found:临时性重定向,请求的资源暂时被移动到新位置。

    • 304 Not Modified:客户端发送条件请求时,资源未改变,可使用缓存的版本。

  4. 4xx:客户端错误状态码(Client Error)

    • 400 Bad Request:请求错误,服务器不理解或无法处理请求。

    • 401 Unauthorized:未授权,需要身份验证。

    • 403 Forbidden:禁止访问,服务器拒绝请求。

    • 404 Not Found:未找到,请求的资源不存在。

  5. 5xx:服务器错误状态码(Server Error)

    • 500 Internal Server Error:服务器内部错误,无法完成请求。

    • 502 Bad Gateway:错误的网关,服务器作为网关或代理,从上游服务器收到无效响应。

    • 503 Service Unavailable:服务不可用,服务器暂时过载或维护中。

这些状态码能够提供给客户端请求的处理状态信息,帮助开发者快速诊断和解决问题。在进行Web开发时,了解常见的HTTP状态码及其含义对于调试和优化应用程序非常有帮助。

33.Get和Post的区别 ✅

GETPOST是HTTP协议中常用的两种请求方法,用于向服务器发送请求。它们之间的主要区别如下:

  1. 请求类型:

    • GET:用于从服务器获取数据。通过URL中的参数传递数据,请求的数据会附加在URL的后面,以查询字符串的形式发送给服务器。

    • POST:用于向服务器提交数据。请求的数据被包含在请求体中发送给服务器,不会显示在URL中。

  2. 数据传递:

    • GET:数据传递通过URL中的查询字符串,对数据量有限制,通常不适用于传输敏感数据,因为数据会暴露在URL中。

    • POST:数据传递通过请求体,对数据量没有限制,适合传输大量数据,也更安全,因为数据不会显示在URL中。

  3. 请求语义:

    • GET:应该用于获取数据,对服务器没有副作用,是幂等的(多次请求同样的URL,结果相同)。

    • POST:应该用于发送数据,可能对服务器产生副作用,比如提交表单,更新数据等,不是幂等的。

  4. 请求可见性:

    • GET:请求的URL会显示在浏览器地址栏中,用户可以收藏和分享链接。

    • POST:请求的URL不会显示在浏览器地址栏中,用户无法直接收藏和分享。

  5. 安全性:

    • GET:不适合传输敏感数据,因为数据暴露在URL中,容易被拦截和窃取。

    • POST:更安全,因为数据在请求体中,不会显示在URL中。

  6. 缓存:

    • GET:可以被缓存,可以使用浏览器的缓存机制来提高性能。

    • POST:不能被缓存,每次请求都会发送最新的数据。

综合来说,GETPOST适用于不同的场景。一般来说,GET用于获取数据,POST用于提交数据。如果只是获取数据,而且不需要传输敏感信息,可以使用GET请求。如果需要提交数据或者传输敏感信息,应该使用POST请求。

34.在浏览器中输入地址到显示页面的过程 ✅

在浏览器中输入地址并最终显示页面的过程通常涉及以下几个步骤:

  1. 域名解析: 当用户在浏览器中输入网址(URL)后,首先会进行域名解析。浏览器将域名部分(如www.example.com)解析成对应的IP地址,这是因为网络通信需要使用IP地址。

  2. 建立连接: 一旦浏览器获取了目标网站的IP地址,它会尝试与目标服务器建立TCP连接。这是通过三次握手(three-way handshake)来完成的,确保客户端和服务器之间的可靠连接。

  3. 发送请求: 建立连接后,浏览器会向服务器发送HTTP请求。请求中包含了需要访问的资源路径、HTTP方法(如GET、POST等)、请求头和可能的请求体(对于POST请求)。

  4. 服务器处理请求: 服务器接收到浏览器发送的HTTP请求后,开始处理请求。服务器会查找所请求的资源,处理相应的业务逻辑,然后生成HTTP响应。

  5. 接收响应: 服务器将生成的HTTP响应发送回给浏览器。响应包含了HTTP状态码、响应头和响应体。

  6. 渲染页面: 浏览器接收到服务器的响应后,开始解析HTML、CSS和JavaScript等内容,并根据解析的结果渲染页面。页面上可能包含其他资源,如图片、样式表和脚本文件,浏览器会继续发送请求获取这些资源。

  7. 显示页面: 当页面渲染完成后,浏览器将显示页面内容,并用户可以与页面进行交互。

  8. 断开连接: 最后,浏览器和服务器之间的TCP连接会在一定时间内保持打开状态,以便在后续的请求中可以复用。如果连接没有复用,则会断开连接。

整个过程涉及了DNS解析、TCP连接建立、HTTP请求和响应、页面渲染等多个步骤。每个步骤都对页面加载速度和用户体验有影响,因此网站性能优化是一个重要的考虑因素。

35.内存管理机制

内存管理是操作系统的核心功能之一,它负责管理计算机的物理内存,使得应用程序可以在内存中运行并且相互隔离,同时提高内存的利用率。

内存管理机制主要包括以下几个方面:

  1. 内存分配与回收: 内存管理器负责将可用的物理内存划分为不同大小的块,并分配给应用程序。当应用程序不再需要某个内存块时,内存管理器会回收该内存,使得其他应用程序可以继续使用。

  2. 虚拟内存: 虚拟内存是一种扩展内存的技术,它允许应用程序使用比物理内存更大的地址空间。虚拟内存机制使得应用程序感觉自己在独占整个系统的内存,而实际上只有部分数据和指令在物理内存中。当应用程序访问未加载到物理内存的虚拟内存时,操作系统会负责将相应的页面加载到物理内存中。

  3. 内存保护: 内存管理器通过设置页面权限和地址空间隔离来实现内存保护。不同的应用程序被隔离在不同的地址空间中,从而防止它们相互干扰或访问彼此的内存。同时,操作系统可以控制每个应用程序对内存的访问权限,以保护系统的安全性。

  4. 内存映射: 内存映射是一种将文件的内容映射到内存地址空间的机制。通过内存映射,应用程序可以像访问内存一样访问文件,这样可以方便地进行文件读写操作。

  5. 页面置换算法: 当物理内存不足以容纳所有的活动页面时,操作系统需要选择哪些页面置换到磁盘上,以腾出空间给新的页面。常见的页面置换算法有最近最少使用(LRU)、先进先出(FIFO)等。

  6. 内存清理与回写: 当页面被修改后,它可能需要被回写到磁盘上,以保持数据的一致性。同时,当物理内存不足时,操作系统可能需要将一些页面清理出来,以腾出空间给新的页面。

内存管理机制的设计对操作系统的性能和稳定性至关重要。一个优秀的内存管理机制应该能够高效地管理内存资源,提供良好的内存隔离和保护,以及合理地进行内存分配和页面置换,从而保证系统的稳定性和性能。

36.什么是虚拟内存IP和域名

  1. 虚拟内存: 虚拟内存是一种计算机内存管理技术,它将计算机的物理内存和磁盘空间结合起来,形成一个虚拟的、比物理内存大得多的地址空间。虚拟内存使得应用程序可以使用比物理内存更大的地址范围,而不需要实际拥有足够大的物理内存。虚拟内存的使用使得多个应用程序可以同时运行,并且相互之间不会干扰。当应用程序访问未加载到物理内存的虚拟内存时,操作系统会将相应的数据从磁盘加载到物理内存中。虚拟内存的使用使得应用程序能够在一个更大的地址空间内运行,而不必考虑物理内存的大小限制。

  2. IP(Internet Protocol)地址: IP地址是用于在网络中标识设备(如计算机、服务器、路由器等)的一组数字。它是网络中唯一的标识符,类似于房屋的门牌号。IP地址分为IPv4和IPv6两个版本。IPv4由32位二进制数组成,通常以点分十进制形式表示(例如:192.168.0.1)。IPv6由128位二进制数组成,通常以冒号分隔的十六进制形式表示(例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334)。IP地址的作用是使得网络中的设备能够相互通信和定位。

  3. 域名(Domain Name): 域名是用于标识互联网上的网站和计算机的便捷方式,它是IP地址的人类可读的别名。域名由一串字符组成,用点分隔,例如"example.com"。域名系统(Domain Name System,DNS)将域名转换为对应的IP地址,使得用户可以使用易于记忆的域名来访问网站,而无需记住复杂的IP地址。当用户在浏览器中输入域名时,浏览器会通过DNS解析将域名转换为IP地址,然后向服务器发送请求。

综上所述,虚拟内存是一种内存管理技术,用于扩展计算机内存的地址空间;IP地址用于在网络中标识设备,使得设备可以相互通信;域名是IP地址的别名,用于方便地访问互联网上的网站和计算机。

37.主键的生成策略有哪些

主键是用于唯一标识数据库表中每一行记录的字段。

在数据库设计中,主键的生成策略可以选择多种方式,常见的主键生成策略包括:

  1. 自增长主键(AUTO_INCREMENT): 数据库自动生成一个唯一的整数值作为主键,每次插入新记录时自动递增。适用于MySQL(使用AUTO_INCREMENT关键字)、SQL Server(使用IDENTITY)等数据库。

  2. UUID(Universally Unique Identifier): 使用128位的全局唯一标识符,以保证在分布式系统中每个主键都是唯一的。UUID主键可以由数据库或应用程序生成,适用于分布式系统和没有自增长主键的情况。

  3. GUID(Globally Unique Identifier): 类似于UUID,是一个全局唯一标识符。GUID通常在应用程序层生成,不依赖于数据库的自增长功能。

  4. 雪花算法(Snowflake): 一种Twitter开源的唯一ID生成算法。雪花算法的ID由一个64位整数组成,包含了时间戳、数据中心ID和机器ID等信息,保证了在分布式系统中生成全局唯一ID。

  5. 复合主键: 使用多个字段组合作为主键,确保组合字段的值唯一。适用于多列联合起来才能唯一标识一条记录的情况。

  6. 数据库序列(Sequence): 一种特殊的数据库对象,用于生成连续的整数序列。应用程序可以从序列中获取下一个值作为主键。适用于数据库支持序列的情况,如Oracle。

  7. 自定义主键生成策略: 应用程序可以根据特定需求自定义主键生成策略,例如使用时间戳和随机数组合,或者使用特定规则生成唯一ID。

选择主键生成策略要根据具体的应用场景和数据库特性进行考虑。自增长主键适用于大多数简单的单机应用,而UUID或雪花算法适用于分布式系统,需要在多个节点生成唯一ID的情况。复合主键适用于多列联合唯一标识一条记录的情况,而自定义主键生成策略适用于特定的业务需求。

38.范式了解哪些 ✅

关系数据库的范式是用于规范数据库表结构的概念。范式分为一至五个级别,每个级别都有一组规则,用于确保数据库表的结构能够最大程度地减少冗余数据,提高数据的一致性和可维护性。

常见的范式包括以下几个级别:

  1. 第一范式(1NF): 数据表中的每个字段都是不可再分的原子值,即每个字段中不能包含多个值或多个属性。确保表中每个单元格都是一个单一的值,不可再分。

  2. 第二范式(2NF): 数据表必须符合1NF,并且要求每个非主键字段完全依赖于全部主键而不是部分主键。即表中的每个非主键字段都要依赖于主键,而不能依赖于主键的部分字段。

  3. 第三范式(3NF): 数据表必须符合2NF,并且要求非主键字段之间相互独立,即非主键字段之间不能有传递依赖。任何非主键字段只依赖于主键,而不依赖于其他非主键字段。

  4. BCNF(Boyce-Codd范式): BCNF是在第三范式基础上进一步规范的范式。要求数据表中的所有函数依赖都必须是自包含的。即,表中的每个非主键字段必须完全依赖于全部主键,而不能依赖于其他非主键字段。

  5. 第四范式(4NF): 数据表必须符合BCNF,并且要求表中不存在多值依赖或多值函数依赖。即,如果一个表包含多个相同的数据,那么这些数据必须分开存储,而不是存储在同一个字段中。

  6. 第五范式(5NF): 数据表必须符合第四范式,并且要求表中不存在联合依赖。即,表中的非主键字段之间不能存在依赖关系。

每个范式都有其独特的优点和适用场景,范式的级别越高,数据表结构的规范性和数据的一致性越高。但是高级别的范式会增加数据表的复杂性和查询的复杂性,降低一些查询性能。在实际应用中,需要根据具体的业务需求和性能要求来选择适当的范式级别。

39.索引的作用和原理

索引是数据库中用于加快数据检索速度的数据结构。它类似于书籍的目录,能够帮助数据库系统快速定位和访问数据,从而提高查询性能。索引在数据库表的某个列或一组列上创建,对这些列的值进行排序和存储,使得数据库可以更快地查找和访问特定的数据行。

索引的作用:

  1. 加速数据检索: 索引允许数据库直接跳过大部分数据,只检索符合查询条件的数据,从而减少扫描的数据量,加快查询速度。

  2. 优化排序和分组: 当查询包含排序或分组操作时,索引可以按照特定的列排序或分组,提高排序和分组的效率。

  3. 加速连接操作: 当多个表进行连接查询时,索引可以加快连接操作的速度,减少连接时的数据匹配操作。

索引的原理:

索引是根据特定的算法和数据结构在数据库表的某个列或一组列上建立的。主要的索引类型有B-tree索引、Hash索引、全文索引等,其中B-tree索引是最常见和广泛使用的索引类型。

B-tree索引采用了一种树状结构来组织数据。它通过对索引列的值进行排序和分层,构建出一棵平衡二叉树。B-tree索引在每个节点上都存储了一部分索引数据,并且通过节点之间的链接进行快速搜索。根据查询条件,数据库可以从根节点开始,逐级向下遍历树,直到找到匹配的叶子节点,从而快速定位到所需的数据行。

数据库管理系统会自动维护索引的数据结构,当有新的数据插入、更新或删除时,索引也会相应地进行调整和更新,以保持索引的正确性和高效性。

需要注意的是,索引并非越多越好,索引会占用额外的存储空间,并增加写入数据的成本。不恰当的索引设计可能会导致索引失效或性能下降。因此,在设计索引时需要根据具体的查询需求和数据访问模式进行合理的规划和优化。

40.连接池的作用 ✅

连接池是数据库编程中常用的技术,它是一组数据库连接的缓存,用于在应用程序和数据库之间管理和复用数据库连接。连接池的主要作用是优化数据库连接的创建和销毁过程,从而提高数据库访问的性能和效率。

连接池的作用包括以下几个方面:

  1. 减少连接创建和销毁的开销: 数据库连接的创建和销毁通常是比较耗时的操作,特别是在频繁的数据库访问场景下。连接池将数据库连接预先创建并缓存起来,应用程序需要时直接从连接池获取可用的连接,避免了重复创建和销毁连接的开销,提高了数据库访问的效率。

  2. 复用连接: 连接池允许多个线程或多个请求共享同一个数据库连接。当一个线程完成数据库访问后,连接不会立即关闭,而是放回到连接池中,供其他线程复用。这样可以避免频繁地打开和关闭数据库连接,提高了数据库连接的复用率,减少了数据库服务器的压力。

  3. 控制连接的数量: 连接池可以限制数据库连接的数量,防止同时打开大量连接导致数据库服务器资源耗尽。通过设置最小连接数和最大连接数,连接池可以根据实际需要动态调整连接数量,保持合理的连接数以满足数据库访问需求。

  4. 连接状态的监控和维护: 连接池可以监控数据库连接的状态,检测连接是否有效和可用。对于不可用或失效的连接,连接池可以进行重新连接或释放操作,保持连接池中的连接都是可用的。

  5. 降低系统资源消耗: 通过连接池管理连接,可以降低系统的资源消耗,避免了不必要的连接创建和销毁操作,减少了内存和CPU的占用。

连接池是提高数据库访问性能和资源利用率的重要手段,特别适用于高并发的数据库访问场景,如Web应用程序和分布式系统。使用连接池能够有效地减少数据库连接的开销,提高系统的性能和稳定性。

41.Spring MVC执行流程 ✅

Spring MVC是一种基于Java的Web开发框架,用于构建MVC(Model-View-Controller)模式的Web应用程序。

它的执行流程通常包括以下几个步骤:

  1. 请求到达DispatcherServlet: 当用户发送一个HTTP请求到应用程序时,请求首先到达DispatcherServlet。DispatcherServlet是Spring MVC的核心控制器,它负责处理所有的请求。

  2. HandlerMapping查找处理器: DispatcherServlet 通过HandlerMapping找到匹配该请求的处理器(Controller)。HandlerMapping负责将请求映射到对应的Controller处理器。

  3. Controller处理请求: 一旦找到匹配的Controller,DispatcherServlet会将请求交给Controller处理。Controller是业务逻辑的处理单元,它根据请求处理业务逻辑,并返回相应的ModelAndView对象。

  4. ModelAndView处理: Controller处理请求后,会将处理结果封装为一个ModelAndView对象。ModelAndView包含了处理结果数据和视图名。数据可以是要显示在视图中的模型数据,视图名则指定了要使用的视图。

  5. ViewResolver解析视图: 一旦Controller返回了ModelAndView对象,DispatcherServlet会通过ViewResolver解析视图名,找到要使用的视图。ViewResolver根据视图名查找对应的视图对象,视图对象负责渲染输出内容。

  6. 视图渲染: 得到要使用的视图后,DispatcherServlet会将ModelAndView中的数据传递给视图,并由视图负责生成输出内容。输出内容通常是HTML页面或其他格式的数据。

  7. 响应返回给客户端: 最后,DispatcherServlet将视图渲染后的结果响应返回给客户端,完成整个请求处理过程。

需要注意的是,在整个执行流程中,Spring提供了很多扩展点和组件,如拦截器、数据绑定、数据验证等,开发者可以根据需要对执行流程进行定制和扩展。这些灵活的扩展点使得Spring MVC适用于各种复杂的Web应用程序。

42.Spring MVC常用的注解 ✅

Spring MVC提供了许多注解,用于简化控制器(Controller)、请求处理、数据绑定、参数验证等操作。

常用的Spring MVC注解包括:

  1. @Controller: 用于标识一个类为Spring MVC的控制器,处理HTTP请求。

  2. @RequestMapping: 用于将请求URL映射到控制器的处理方法上。可以用在类级别和方法级别,用于定义URL和HTTP方法(GET、POST等)的映射关系。

  3. @GetMapping、@PostMapping、@PutMapping、@DeleteMapping: 这些注解是@RequestMapping的缩写,用于定义特定HTTP方法的请求映射。

  4. @PathVariable: 用于将URL中的模板变量(例如,/user/{id})绑定到方法参数上。

  5. @RequestParam: 用于绑定请求参数到方法参数上,可以指定默认值和是否必须。

  6. @RequestBody: 用于将请求体中的数据绑定到方法参数上,适用于接收JSON或XML等格式的请求数据。

  7. @ResponseBody: 用于将方法返回值直接写入HTTP响应体中,适用于返回JSON或XML等格式的响应数据。

  8. @ModelAttribute: 用于将方法返回值添加到Model中,使其在视图中可以访问。

  9. @SessionAttributes: 用于将指定的模型属性保存到会话中,使其跨请求访问。

  10. @InitBinder: 用于定制数据绑定和格式化规则,用于处理方法参数的绑定和验证。

  11. @Valid: 用于开启参数验证功能,配合javax.validation中的注解实现参数验证。

  12. @ExceptionHandler: 用于定义全局的异常处理方法,当控制器中抛出异常时,可以统一处理并返回友好的错误页面或JSON数据。

这些注解使得开发者可以在Spring MVC中更加便捷地定义请求处理方法、绑定请求数据、处理异常等操作,简化了开发流程并提高了代码的可读性和维护性。

43.Spring中的设计模式 ✅

Spring框架是一个综合性的企业级应用开发框架,它内部运用了多种设计模式来实现各种功能和提供灵活的扩展点。

常见的Spring中使用的设计模式包括:

  1. 依赖注入(Dependency Injection): 这是Spring框架最为人熟知的一个设计模式。通过依赖注入,对象之间的依赖关系由框架在运行时动态地注入,而不是在代码中硬编码。这样可以实现松耦合,提高代码的可测试性和可维护性。

  2. 工厂模式(Factory Pattern): Spring使用工厂模式创建和管理Bean对象。Spring的容器充当了工厂,根据配置或注解的信息动态地创建和管理Bean对象。

  3. 单例模式(Singleton Pattern): Spring默认情况下,对于某个Bean,容器只会创建一个实例,即单例模式。这样可以节省资源,并确保多个地方使用同一个实例。

  4. 代理模式(Proxy Pattern): Spring AOP(面向切面编程)使用了代理模式来实现切面功能。通过动态代理,在不改变原有代码的情况下,将横切逻辑(如事务管理、日志记录等)与业务逻辑分离。

  5. 观察者模式(Observer Pattern): Spring的事件驱动机制使用观察者模式。应用程序可以发布事件,而感兴趣的观察者可以注册监听器来处理这些事件。

  6. 策略模式(Strategy Pattern): Spring的资源访问和类型转换使用了策略模式。应用程序可以根据不同的需求配置不同的策略来实现资源的访问和类型转换。

  7. 模板模式(Template Pattern): Spring的JdbcTemplate和HibernateTemplate等模板类使用了模板模式。这些模板类封装了常用的数据库操作和持久化逻辑,应用程序只需提供定制化的部分,从而实现数据库操作的简化。

  8. 装饰器模式(Decorator Pattern): Spring的Bean后处理器使用了装饰器模式。后处理器可以在Bean初始化过程中进行增强操作,如为Bean添加额外的功能或处理。

  9. 适配器模式(Adapter Pattern): Spring的Spring MVC框架使用了适配器模式,将请求映射到不同类型的处理器适配器上,实现了灵活的请求处理机制。

Spring框架中的设计模式不仅仅局限于上述几种,它还融合了其他的设计思想和模式,使得框架具有高度的灵活性和扩展性。

44.Spring Boot项目如何部署

Spring Boot项目可以以多种方式进行部署,具体的部署方式取决于你的项目需求和架构。

以下是几种常见的部署方式:

  1. Jar包部署: Spring Boot项目可以打包成可执行的Jar包。只需在项目根目录执行mvn clean package命令,然后在target目录找到生成的Jar包,使用java -jar your-application.jar命令即可启动项目。这种方式非常简单,适用于单独部署的小型项目。

  2. War包部署: Spring Boot项目也可以打包成传统的War包,用于部署到外部的Servlet容器(如Tomcat、Jetty等)。在pom.xml文件中将packaging改为war,然后执行mvn clean package命令,找到生成的War包,将其部署到Servlet容器中。

  3. Docker容器部署: 使用Docker可以将Spring Boot项目打包成一个独立的容器,包含所有运行所需的依赖和配置。首先在项目根目录编写Dockerfile文件,然后使用Docker工具构建镜像并运行容器。

  4. 云平台部署: Spring Boot项目可以部署到各种云平台,如AWS、Azure、Google Cloud等。云平台通常提供简单的部署配置和扩展能力,使得部署和管理项目更加便捷。

  5. 服务器集群部署: 对于高可用性和负载均衡需求,可以将Spring Boot项目部署到服务器集群中。通过负载均衡器将请求分发到不同的服务器上,实现高并发和容错。

无论你选择哪种部署方式,都需要确保你的部署环境符合项目的要求,并且在部署过程中注意配置文件的正确性和安全性。同时,建议在部署前进行适当的测试,确保项目能够正常运行。

45.Spring Boot的优缺点 ✅

Spring Boot是一个快速开发和轻量级的Spring应用程序框架,它带来了许多优点,但也有一些局限性。

以下是Spring Boot的主要优缺点:

优点:

  1. 快速启动和开发: Spring Boot简化了Spring应用程序的配置,提供了自动配置和快速启动的特性,使得开发者可以更快地搭建和开发应用程序。

  2. 简化配置: Spring Boot通过约定大于配置的原则,将常用的配置自动化,减少了繁琐的XML配置和注解配置,简化了开发流程。

  3. 内嵌容器: Spring Boot内嵌了Tomcat、Jetty等Servlet容器,可以直接运行和打包成Jar包,无需依赖外部容器,使得部署更加方便。

  4. 自动配置: Spring Boot根据项目的依赖和配置信息,自动配置Spring应用程序的各个组件,减少了手动配置的工作。

  5. 监控和管理: Spring Boot提供了丰富的监控和管理功能,如健康检查、性能指标、应用信息等,便于对应用程序进行管理和监控。

  6. 生态系统丰富: Spring Boot构建在Spring框架的基础上,可以直接使用Spring的各种特性和扩展,而且还支持丰富的第三方库和插件。

缺点:

  1. 学习曲线: 对于新手来说,Spring Boot的学习曲线可能相对陡峭,特别是对于Spring生态系统不太熟悉的开发者。了解Spring Boot的自动配置和约定可能需要一些时间。

  2. 自动配置冲突: 在复杂项目中,多个自动配置可能会发生冲突,导致意外的行为。解决冲突可能需要深入了解Spring Boot的自动配置原理。

  3. 隐藏细节: 虽然Spring Boot的自动配置简化了开发,但有时也会隐藏一些细节,使得开发者难以理解应用程序的实际工作原理。

  4. 过度依赖: 一些开发者可能会过度依赖Spring Boot提供的自动配置和便利性,而不深入了解底层技术和原理。

总体来说,Spring Boot是一个非常优秀的框架,它大大简化了Spring应用程序的开发和部署,提高了开发效率和便利性。然而,开发者应该根据项目的实际需求,合理选择和使用Spring Boot的功能,避免过度依赖和不必要的复杂性。

46.Spring Boot的自动配置原理

Spring Boot的自动配置原理基于条件化配置(Conditional Configuration)和Spring的@EnableAutoConfiguration注解。它允许Spring Boot根据应用程序的依赖和配置情况,自动配置Spring应用程序的各个组件,从而简化了开发者的配置工作。

以下是Spring Boot自动配置的主要原理:

  1. 条件化配置(Conditional Configuration): Spring Boot使用条件化配置来决定是否应该启用某个组件的自动配置。通过条件化配置,可以根据一些条件来动态决定是否应用某个配置。Spring Boot内置了大量的条件注解,如@ConditionalOnClass@ConditionalOnBean@ConditionalOnProperty等,可以根据类的存在、Bean的存在、配置属性的值等条件来判断是否要应用某个配置。

  2. 自动配置类(Auto-Configuration Classes): Spring Boot的自动配置是通过自动配置类实现的。每个自动配置类都包含了对某个特定组件进行配置的逻辑。这些自动配置类位于spring-boot-autoconfigure模块中,它们通过@Conditional注解来条件化配置,只有满足条件时才会生效。

  3. @EnableAutoConfiguration注解: Spring Boot应用程序通常会在主类上使用@SpringBootApplication注解,而@SpringBootApplication本身是一个复合注解,其中包含了@EnableAutoConfiguration注解。@EnableAutoConfiguration注解会自动启用Spring Boot的自动配置功能,它会扫描并加载所有在classpath下的META-INF/spring.factories文件中的自动配置类。

  4. spring.factories文件: 自动配置类的加载是通过在META-INF/spring.factories文件中指定的EnableAutoConfiguration键值对来实现的。每个自动配置类都在该文件中配置,Spring Boot在启动时会读取该文件,并根据其中的配置加载相应的自动配置类。

  5. 自定义配置: 在实际开发中,如果希望覆盖或扩展Spring Boot的自动配置,可以在应用程序中自定义配置类。通过编写自定义配置类,并使用@Configuration注解,可以覆盖或扩展自动配置类的配置逻辑。

总的来说,Spring Boot的自动配置原理通过条件化配置和@EnableAutoConfiguration注解,实现了根据项目的依赖和配置情况,动态地加载自动配置类,从而简化了Spring应用程序的配置工作。这使得开发者可以更专注于业务逻辑,而不必过多地关注繁琐的配置。

47.Spring Boot配置加载优先级

Spring Boot的配置加载优先级是一个重要的概念,它决定了配置文件在不同位置的加载顺序和覆盖规则。Spring Boot遵循一定的规则,根据配置文件的位置和类型,按照一定的优先级顺序加载配置。

以下是Spring Boot配置加载的优先级顺序(由高到低):

  1. 命令行参数(Command Line Arguments): 命令行参数是最高优先级的配置,可以通过命令行传递参数给应用程序。例如:java -jar your-application.jar --spring.profiles.active=dev,其中--spring.profiles.active=dev指定了使用dev配置文件。

  2. 系统属性(System Properties): 系统属性是通过-D参数传递给JVM的参数,可以在启动应用程序时指定系统属性。例如:-Dspring.profiles.active=dev,表示使用dev配置文件。

  3. 环境变量(OS Environment Variables): 环境变量是操作系统级别的变量,可以在操作系统中设置。Spring Boot会读取所有以SPRING_为前缀的环境变量,例如SPRING_PROFILES_ACTIVE=dev,表示使用dev配置文件。

  4. 应用程序配置文件(Application Properties/YAML): 应用程序配置文件是在类路径下的application.propertiesapplication.yml文件。这是常用的配置文件,可以配置通用的属性。

  5. 应用程序配置文件(Profile-specific Properties/YAML): 在类路径下的application-{profile}.propertiesapplication-{profile}.yml文件,其中{profile}是激活的配置文件,例如application-dev.properties。这种配置文件是特定于不同环境的配置,优先级高于通用的application.properties

  6. 外部配置文件(External Properties/YAML): 外部配置文件是在文件系统上的外部位置,可以通过spring.config.location参数指定。这种配置文件的优先级高于类路径下的配置文件,允许在不修改打包的应用程序的情况下,动态修改配置。

配置加载优先级的规则允许在不同的环境中使用不同的配置,优先级高的配置会覆盖优先级低的配置。例如,命令行参数和系统属性的优先级最高,可以用于临时调整配置;环境变量的优先级较低,可以用于不同服务器的不同配置;而应用程序配置文件的优先级是最低的,可以用于通用的配置。

在实际开发中,合理利用配置加载优先级可以实现灵活的配置管理,使得应用程序在不同环境中运行时,能够自动加载合适的配置。

48.union和union all的区别 ✅

UNIONUNION ALL是SQL中用于合并查询结果的两个关键字,

它们有以下区别:

  1. 重复记录:

    • UNIONUNION操作会合并查询结果,并去除重复的记录。如果两个查询的结果中存在相同的记录,UNION只会返回一条。

    • UNION ALLUNION ALL操作也会合并查询结果,但不去除重复的记录。即使两个查询的结果中存在相同的记录,UNION ALL会将所有的记录都返回。

  2. 性能:

    • UNION:由于UNION需要去除重复记录,它的执行时间可能比UNION ALL更长,特别是在查询结果中存在大量重复记录时。

    • UNION ALL:由于不需要去除重复记录,UNION ALL的执行时间通常会比UNION更短。

  3. 语法:

    • UNIONUNION关键字用于合并两个或多个查询的结果,并且必须保证查询的列数和数据类型相同。

    • UNION ALLUNION ALL关键字同样用于合并查询结果,不需要保证查询的列数和数据类型相同,但最终结果的列数和数据类型将与第一个查询结果一致。

使用UNION ALL可以获得更快的查询性能,但需要注意确保合并的结果不会包含重复的记录。而使用UNION可以去除重复记录,但性能可能较低。因此,在实际使用中,应根据具体的需求来选择使用哪种关键字。如果需要去除重复记录并且查询结果不会包含大量重复数据,可以使用UNION;如果不需要去除重复记录或查询结果中包含大量重复数据,可以使用UNION ALL来获得更好的性能。

49.== 和 equals的区别 ✅

==equals 是Java中用于比较对象的两种不同方式,

它们有以下区别:

  1. 比较的对象类型:

    • ====用于比较两个对象的引用是否相同,即判断两个对象是否指向同一个内存地址。

    • equalsequals方法用于比较两个对象的内容是否相同,即判断两个对象是否在逻辑上相等。

  2. 用途:

    • ==:通常用于比较基本数据类型的值,以及比较对象的引用是否相同。

    • equals:通常用于比较对象的内容,它可以被重写为自定义的逻辑,用于判断两个对象的内容是否相等。

  3. 默认实现:

    • ==:对于基本数据类型,==比较的是它们的值;对于对象引用,==比较的是它们在内存中的地址。

    • equalsequals方法在Object类中有一个默认实现,它和==的行为相同,即比较对象的引用是否相同。但是,equals方法可以被子类重写,用于自定义比较逻辑。

  4. 重写规范:

    • ==:不建议重写==操作符的行为,它应该保持默认的引用比较。

    • equals:如果一个类希望在逻辑上比较对象是否相等,通常应该重写equals方法,并根据对象的内容来比较。

示例代码如下:

String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
​
// 使用==比较字符串的引用
System.out.println(str1 == str2); // 输出 true,因为str1和str2指向同一个字符串常量池中的对象
System.out.println(str1 == str3); // 输出 false,因为str3是通过new关键字创建的新对象
​
// 使用equals比较字符串的内容
System.out.println(str1.equals(str2)); // 输出 true,因为str1和str2的内容相同
System.out.println(str1.equals(str3)); // 输出 true,因为str1和str3的内容相同

总之,==用于比较对象的引用是否相同,而equals用于比较对象的内容是否相同。在大多数情况下,我们应该使用equals来比较对象的内容,特别是当涉及到自定义类时,应该重写equals方法来定义对象的相等性逻辑。

50.线程的生命周期

线程的生命周期指的是线程从创建到销毁的整个过程,

它包括以下几个状态:

  1. 新建状态(New): 当一个线程对象被创建时,它处于新建状态。此时线程尚未启动,还没有开始执行。

  2. 就绪状态(Runnable): 当线程调用了start()方法后,线程进入就绪状态。此时线程已经准备好执行,但还没有获得CPU的执行时间。

  3. 运行状态(Running): 当线程获得CPU时间片,开始执行线程的run()方法时,线程进入运行状态。线程将执行其中的代码逻辑。

  4. 阻塞状态(Blocked): 在运行状态中,如果线程因为某些原因而暂时无法继续执行,比如等待某个资源,就会进入阻塞状态。在阻塞状态中,线程不会占用CPU时间。

  5. 等待状态(Waiting): 当线程调用了wait()方法时,线程进入等待状态。在等待状态中,线程会释放已经占有的锁,并等待其他线程通过notify()notifyAll()方法唤醒它。

  6. 计时等待状态(Timed Waiting): 当线程调用了带有超时参数的sleep()join()wait()方法时,线程进入计时等待状态。在计时等待状态中,线程会等待指定的时间,或者等待其他线程的唤醒。

  7. 终止状态(Terminated): 线程执行完run()方法中的代码或者因为异常而提前终止时,线程进入终止状态。线程在终止状态下不再运行,它的生命周期结束。

线程的生命周期是动态变化的,从新建状态开始,通过就绪状态和运行状态,可能进入阻塞状态、等待状态或计时等待状态,最后进入终止状态。线程的状态之间可能会相互转换,取决于线程的执行和外部条件的影响。

51.事务的传播方式 ✅

在数据库事务中,事务的传播方式指的是在多个事务方法相互调用的情况下,各个事务方法之间事务如何传播和协调的一种机制。

Spring框架提供了七种事务的传播方式,用于管理事务的边界和控制:

  1. PROPAGATION_REQUIRED: 默认的传播方式。如果当前存在事务,就加入到当前事务中,如果没有事务,则新建一个事务。

  2. PROPAGATION_SUPPORTS: 支持当前事务。如果当前存在事务,就加入到当前事务中,如果没有事务,则以非事务方式执行。

  3. PROPAGATION_MANDATORY: 强制要求当前存在事务。如果当前没有事务,则抛出异常。

  4. PROPAGATION_REQUIRES_NEW: 新建一个独立的事务。如果当前存在事务,将当前事务挂起,并新建一个事务执行。

  5. PROPAGATION_NOT_SUPPORTED: 以非事务方式执行。如果当前存在事务,将当前事务挂起,并以非事务方式执行。

  6. PROPAGATION_NEVER: 以非事务方式执行。如果当前存在事务,则抛出异常。

  7. PROPAGATION_NESTED: 嵌套事务。如果当前存在事务,就在当前事务的嵌套事务中执行。如果没有事务,则新建一个事务。

在使用Spring的事务管理时,我们可以在@Transactional注解中设置事务的传播方式。例如:

@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(User user) {
        // 更新用户信息的业务逻辑
        userRepository.save(user);
    }
​
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createUser(User user) {
        // 创建用户的业务逻辑
        userRepository.save(user);
    }
}

在上面的例子中,updateUser()方法的事务传播方式为REQUIRED,如果当前存在事务,则加入到当前事务中;createUser()方法的事务传播方式为REQUIRES_NEW,无论当前是否存在事务,都会新建一个独立的事务来执行。

52.声明式事务

声明式事务是通过注解或XML配置的方式来实现事务管理,而不需要在代码中显式地编写事务管理逻辑。在Spring框架中,可以使用@Transactional注解来声明式地管理事务。

使用声明式事务的好处是将事务管理与业务逻辑分离,让开发者更专注于业务代码的编写,而不需要手动管理事务的开始、提交、回滚等操作。通过声明式事务,Spring框架可以自动地为我们处理事务的开启、提交、回滚、异常处理等事务相关操作。

在使用声明式事务时,需要做以下几个步骤:

  1. 配置数据源和事务管理器: 在Spring配置文件中配置数据源和事务管理器,以便Spring框架知道如何管理事务。

  2. 开启事务注解支持: 在Spring配置文件中开启对事务注解的支持,可以使用@EnableTransactionManagement注解来实现。

  3. 添加@Transactional注解: 在需要进行事务管理的方法上添加@Transactional注解。可以根据需要设置不同的属性,如传播方式、隔离级别、只读等。

  4. 处理事务异常: 在事务中,如果发生异常,Spring会自动回滚事务。可以通过rollbackFornoRollbackFor属性来指定特定的异常是否回滚事务。

示例代码如下:

@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Transactional
    public void updateUser(User user) {
        // 更新用户信息的业务逻辑
        userRepository.save(user);
    }
​
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = {CustomException.class})
    public void createUser(User user) throws CustomException {
        // 创建用户的业务逻辑
        if (someCondition) {
            throw new CustomException("创建用户失败");
        }
        userRepository.save(user);
    }
}

在上面的例子中,updateUser()方法使用默认的事务传播方式REQUIRED,而createUser()方法使用事务传播方式REQUIRES_NEW,并指定当发生CustomException异常时回滚事务。通过声明式事务,Spring会自动为这两个方法管理事务。如果createUser()方法抛出CustomException异常,事务会回滚,数据库中的数据也会回滚到调用方法之前的状态。

53.主键的生成策略有哪些

在数据库中,主键是用于唯一标识一条记录的字段。

在关系型数据库中,常用的主键生成策略有以下几种:

  1. 自增长(AUTO_INCREMENT): 在插入数据时,数据库自动为主键字段生成唯一的递增值。适用于整数类型的主键。在MySQL中使用AUTO_INCREMENT,在Oracle中使用IDENTITY

  2. UUID(Universally Unique Identifier): 使用UUID算法生成全局唯一的主键值。适用于分布式系统,不依赖于数据库生成。缺点是主键较长,增加了索引的存储空间和查询性能。

  3. GUID(Globally Unique Identifier): 类似于UUID,是一种全局唯一标识符。在Microsoft SQL Server中使用NEWID()函数生成。

  4. 雪花算法(Snowflake): 一种Twitter开源的分布式ID生成算法,生成64位的唯一ID。包括数据中心ID、机器ID、序列号等信息,适用于分布式系统。

  5. 数据库序列(Sequence): 一种数据库提供的自增长序列,可以在插入数据时获取下一个序列值作为主键。在Oracle和PostgreSQL等数据库中支持。

  6. 自定义生成策略: 可以根据业务需求自定义生成主键的逻辑,例如通过组合其他字段、时间戳等生成唯一的主键。

在使用主键时,需要根据具体的业务需求和数据库支持来选择合适的主键生成策略。自增长主键在插入数据时性能较好,但不适用于分布式系统。UUID和GUID可以在分布式系统中保证全局唯一性,但长度较长。数据库序列适用于Oracle和PostgreSQL等数据库。雪花算法是一种比较优秀的分布式ID生成算法,适用于高并发的分布式系统。自定义生成策略可以根据具体业务场景来实现定制化的主键生成逻辑。

54.ControllerAdvice ✅

@ControllerAdvice是Spring MVC框架中的一个注解,用于统一处理Controller层的异常和全局数据绑定。它允许我们定义全局的异常处理器和全局数据绑定,让这些处理逻辑在所有Controller中共享,从而实现全局的异常处理和数据预处理。

使用@ControllerAdvice注解的类通常是一个带有特定注解的类,用于处理全局的异常和数据绑定。这些注解包括:

  • @ExceptionHandler: 用于处理Controller层的异常,可以根据不同的异常类型提供不同的处理逻辑。

  • @InitBinder: 用于数据预处理,可以在请求进入Controller之前对请求数据进行预处理,比如数据绑定、类型转换等。

  • @ModelAttribute: 用于全局数据绑定,可以在每个Controller方法执行之前将一些共享的数据添加到Model中。

使用@ControllerAdvice注解的类可以根据需要包含上述注解的方法,例如:

@ControllerAdvice
public class GlobalExceptionHandler {
​
    @ExceptionHandler(Exception.class)
    public ResponseEntity handleException(Exception ex) {
        // 处理全局异常
        return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
​
    @ModelAttribute
    public void addAttributes(Model model) {
        // 全局数据绑定,添加共享的数据到Model
        model.addAttribute("appName", "MyApp");
    }
​
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // 数据预处理
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true));
    }
}

在上面的例子中,GlobalExceptionHandler类使用了@ControllerAdvice注解,并包含了@ExceptionHandler@ModelAttribute@InitBinder注解的方法。handleException方法用于处理全局异常,addAttributes方法用于添加全局数据绑定,initBinder方法用于数据预处理。

通过使用@ControllerAdvice注解,我们可以实现全局的异常处理和数据绑定,减少了代码的重复性,同时增加了代码的可维护性和可读性。

55.Restful

RESTful是一种架构风格,用于设计网络应用程序的API。它是"Representational State Transfer"(表现层状态转化)的缩写。RESTful API基于HTTP协议,通过URL定位资源,使用HTTP方法(GET、POST、PUT、DELETE等)来对资源进行操作,以及使用HTTP状态码和响应数据来表达状态和结果。

RESTful API的设计原则包括:

  1. 资源(Resources): 将应用程序的各个实体抽象为资源,每个资源都有一个唯一的URL来标识。

  2. HTTP方法(HTTP Methods): 使用HTTP方法来表示对资源的操作。常用的HTTP方法包括GET(获取资源)、POST(创建资源)、PUT(更新资源)、DELETE(删除资源)等。

  3. 无状态(Stateless): 每个请求都应该包含足够的信息,服务端不需要保留客户端的状态。客户端的每个请求都应该包含一切需要的信息。

  4. 使用HTTP状态码(HTTP Status Codes): 使用HTTP状态码来表示请求的结果和状态。例如,200表示成功,404表示资源未找到,500表示服务器内部错误等。

  5. 标准格式(Standard Formats): RESTful API通常使用标准的数据格式,如JSON或XML,作为请求和响应的数据格式。

  6. 版本管理(Versioning): 对于API的升级和改动,应该采用版本管理,避免对现有客户端造成不兼容的影响。

RESTful API的设计使得它易于理解、易于扩展和易于与其他系统集成。它具有良好的可读性和可维护性,适用于Web开发、移动应用开发和微服务等场景。在使用RESTful API时,应遵循其设计原则,按照资源的定义、HTTP方法的使用、状态码的返回等规范进行开发。

56.#0和$0的区别 ✅

#0$0在不同的上下文中代表不同的含义。

  1. #0

    • 在数学和计算机编程中,#0通常表示数字零,即整数0。例如,int x = #0; 表示将整数变量x的值设置为0。

  2. $0

    • 在一些编程语言和正则表达式中,$0通常表示与整个匹配模式相匹配的字符串。在正则表达式中,$0用于引用整个匹配项。

    • 在一些Shell脚本和命令行工具中,$0表示脚本或命令本身的名称。在Shell脚本中,$0表示当前脚本的名称;在命令行工具中,$0表示当前执行的命令。

例如,在Java的正则表达式中,$0表示整个匹配项:

import java.util.regex.*;
​
public class RegexTest {
    public static void main(String[] args) {
        String input = "Hello, world!";
        Pattern pattern = Pattern.compile("Hello, (.*)!");
        Matcher matcher = pattern.matcher(input);
        if (matcher.find()) {
            String wholeMatch = matcher.group(0);
            System.out.println(wholeMatch); // 输出:Hello, world!
        }
    }
}

在Shell脚本中,$0表示脚本的名称:

#!/bin/bash
​
echo "当前执行的脚本名称是:$0"

假设以上内容保存为test.sh文件,执行脚本后输出结果为:当前执行的脚本名称是:test.sh

57.Mybatis中的动态SQL标签

MyBatis是一个Java持久层框架,支持动态SQL查询。在MyBatis中,可以使用动态SQL标签来构建动态的SQL语句,根据不同的条件生成不同的SQL片段。

以下是MyBatis中常用的动态SQL标签:

  1. if标签: 用于根据条件判断是否包含某段SQL语句。

  1. choose、when、otherwise标签: 类似于Java中的switch语句,根据条件选择执行不同的SQL片段。

  1. trim标签: 用于去除生成SQL语句中的多余空白字符。

  1. foreach标签: 用于遍历集合,生成IN语句或批量插入语句。


    INSERT INTO user (name, age) VALUES
    
        (#{user.name}, #{user.age})
    

这些动态SQL标签使得MyBatis的SQL查询更加灵活和可复用,可以根据不同的条件动态地生成SQL语句,提高了查询的灵活性和性能。在使用动态SQL时,要注意防止SQL注入攻击,确保输入的条件值是安全的。

58.Mybatis如何分页

MyBatis提供了两种分页方式:基于参数的分页和基于插件的分页。

  1. 基于参数的分页: 这是最常见的分页方式,通过在Mapper接口的方法中添加参数来实现分页。

// Java代码
public interface UserMapper {
    List getUserList(@Param("offset") int offset, @Param("limit") int limit);
}
​
// XML映射文件

在上述例子中,offset表示起始位置,limit表示每页的记录数。通过计算offsetlimit可以实现分页查询。

  1. 基于插件的分页: MyBatis还支持通过自定义插件来实现分页功能。这种方式更加灵活,可以实现更复杂的分页逻辑。

// Java代码
public interface UserMapper {
    List getUserList();
}
​
// XML映射文件

然后,通过自定义分页插件来实现分页逻辑:

public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
​
        // 获取分页参数
        int offset = rowBounds.getOffset();
        int limit = rowBounds.getLimit();
​
        // 重新构造分页SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        String sql = boundSql.getSql() + " LIMIT " + offset + ", " + limit;
​
        // 重新设置SQL参数
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), parameter);
        MappedStatement newMs = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
​
        args[0] = newMs;
        args[2] = RowBounds.DEFAULT;
​
        return invocation.proceed();
    }
}

需要注意的是,基于插件的分页方式需要自己实现插件来拦截SQL并重新构造分页的SQL语句。

以上两种分页方式在实际应用中都是常见的,并且根据具体的业务需求和性能要求,可以选择适合的分页方式来实现数据分页查询。

59.Mybatis有哪些执行器,区别是什么

MyBatis中有三种执行器(Executor):SimpleExecutorReuseExecutorBatchExecutor

这些执行器负责执行SQL语句并将结果映射到Java对象。

  1. SimpleExecutor: 简单执行器,每次执行SQL语句都会创建一个新的Statement对象,不进行任何缓存和复用。适用于短生命周期的小型应用或在特殊情况下使用。由于没有缓存和复用,可能导致频繁创建和销毁Statement的开销。

  2. ReuseExecutor: 可重用执行器,会对Statement进行缓存,当执行相同的SQL语句时,会直接从缓存中获取Statement对象,避免了频繁创建和销毁Statement的开销。适用于长生命周期的应用,如Web应用,可以复用Statement以提高性能。

  3. BatchExecutor: 批处理执行器,用于批量执行SQL语句,将多个SQL语句一起发送给数据库进行执行。适用于需要批量插入、更新或删除数据的场景,可以减少与数据库的通信次数,提高性能。

区别:

  • SimpleExecutor每次执行SQL都会创建新的Statement对象,没有缓存机制,适用于短生命周期的小型应用。

  • ReuseExecutor会缓存Statement对象,复用相同SQL的Statement,适用于长生命周期的应用,可以提高性能。

  • BatchExecutor用于批量执行SQL语句,适用于批量插入、更新或删除数据的场景。

MyBatis默认使用的是ReuseExecutor,这也是大多数场景下推荐的执行器,因为它可以在一定程度上避免Statement的频繁创建和销毁,提高SQL执行性能。在特定的场景下,可以根据需要切换不同的执行器,或者自定义执行器来满足具体的业务需求。

60.Mybatis如何映射Enum

在 MyBatis 中映射枚举(Enum)类型有两种方式:使用 EnumTypeHandler 和使用 @Enum 注解。

  1. 使用 EnumTypeHandler:

MyBatis 内置了 EnumTypeHandler 来处理枚举类型。如果枚举类在 Mapper 接口方法中被用作参数或返回值,MyBatis 会自动将数据库中的值与 Java 中的枚举值进行映射。

首先,在 MyBatis 的配置文件中配置 EnumTypeHandler

   
       
   

然后,在 Mapper 接口方法的参数或返回值中使用枚举类型即可:

   public interface UserMapper {
       User getUserById(@Param("id") int id);
       void updateUserStatus(@Param("userId") int userId, @Param("status") UserStatus status);
   }
  public class User {
       // 枚举类型字段
    private UserStatus status;
   
       // getter 和 setter 方法
   }

MyBatis 会自动将数据库中的枚举值转换为 Java 中的枚举类型,以及将 Java 中的枚举类型转换为数据库中的对应值。

  1. 使用 @Enum 注解:

如果不想配置全局的 EnumTypeHandler,也可以在枚举类型字段上使用 @Enum 注解指定对应的处理器。

   public enum UserStatus {
       @EnumValue("A")
       ACTIVE,
       @EnumValue("I")
       INACTIVE
   }
   
       
       
   

在上述例子中,我们使用了 EnumOrdinalTypeHandler,它将枚举映射到枚举常量的序号,即枚举中定义的顺序。

使用哪种方式取决于你的需求和习惯。第一种方式全局配置较为方便,而第二种方式更为灵活,可以对不同的字段使用不同的处理器。

61.MybatisPlus

Mybatis-Plus是 MyBatis 的增强工具库,提供了一系列增强的功能,简化了数据访问层的开发,使得 MyBatis 的使用更加方便和高效。Mybatis-Plus是对 MyBatis 的扩展,完全兼容 MyBatis 标准的注解和XML配置。

Mybatis-Plus 的主要特点和功能包括:

  1. 强大的CRUD操作: 提供了丰富的CRUD方法,包括通用的增删改查操作,无需编写XML和Mapper接口,即可进行基本的数据库操作。

  2. 分页插件: 内置了分页插件,支持多种数据库,可以方便地实现数据分页查询。

  3. 代码生成器: 提供了代码生成器,可以根据数据库表自动生成实体类、Mapper接口、Service类等代码,大大加快开发速度。

  4. 条件构造器: 提供了条件构造器,可以通过简单的API实现复杂的查询条件,避免了手写SQL。

  5. 乐观锁和逻辑删除: 支持乐观锁和逻辑删除功能,方便处理并发和软删除的场景。

  6. 性能优化: 内置了缓存功能和分析工具,可以方便地对SQL进行性能优化。

  7. 自动填充字段: 支持自动填充字段,比如创建时间、更新时间等,减少重复的代码。

使用 Mybatis-Plus 可以简化 MyBatis 的开发流程,提高开发效率,减少样板代码的编写。它适用于大多数的数据访问场景,并且和原生 MyBatis 完美集成,可以在项目中无缝使用。如果你正在使用 MyBatis,推荐尝试 Mybatis-Plus 来提升开发效率。

62.starter的作用

在Spring Boot中,Starter是一种特殊的依赖项,它可以简化项目的配置和启动过程,帮助开发者快速集成各种功能和框架。Starter通常是一个命名良好的Maven或Gradle项目,它封装了一组相关的依赖项、配置和代码,使得开发者可以通过简单地添加一个Starter依赖,就能够快速引入某个功能或框架。

Starter的作用有以下几个方面:

  1. 简化配置: Starter将复杂的配置和依赖项封装在一起,使得开发者不需要手动配置和添加各种依赖项。只需添加一个Starter依赖,Spring Boot就会自动配置相关的组件和功能。

  2. 快速集成: Starter是用于快速集成某个功能或框架的工具。通过添加相关的Starter依赖,开发者可以快速地将功能集成到项目中,而无需了解和配置过多的细节。

  3. 约定优于配置: Spring Boot的Starter遵循约定优于配置的原则,它提供了一套默认的配置和约定,使得开发者可以快速开始,同时也可以根据需要进行自定义配置。

  4. 版本管理: Starter通常会为所包含的依赖项指定特定的版本,这样可以确保这些依赖项之间的兼容性,并简化版本管理。

  5. 可插拔性: Starter可以根据需要进行添加和删除,使得项目可以灵活地增减功能,而不会影响其他部分。

Spring Boot本身提供了大量的官方Starter,覆盖了常见的功能,如Web、数据访问、安全性、测试等。同时,社区中也有很多第三方Starter,可以帮助集成更多的框架和功能。开发者可以通过搜索相应的Starter,来方便地集成所需的功能,加速项目开发。

63.MySQL有哪些数据类型 ✅

MySQL支持多种数据类型,

主要分为以下几类:

  1. 数值类型:

    • 整数类型:TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT。

    • 浮点数类型:FLOAT、DOUBLE。

    • 定点数类型:DECIMAL。

  2. 字符串类型:

    • CHAR:固定长度字符串,最多255个字符。

    • VARCHAR:可变长度字符串,最多65535个字符。

    • TEXT:可变长度字符串,最多65535个字符,适用于较长的文本数据。

  3. 日期和时间类型:

    • DATE:日期,格式为'YYYY-MM-DD'。

    • TIME:时间,格式为'HH:MM:SS'。

    • DATETIME:日期和时间,格式为'YYYY-MM-DD HH:MM:SS'。

    • TIMESTAMP:时间戳,自动记录插入或更新数据的时间。

  4. 布尔类型:

    • BOOL或BOOLEAN:布尔类型,取值为TRUE或FALSE。

  5. 二进制类型:

    • BLOB:二进制数据,最多65535个字节,适用于存储大型二进制对象。

  6. 枚举类型:

    • ENUM:枚举类型,可从预定义的值列表中选择一个值。

  7. 集合类型:

    • SET:集合类型,可从预定义的值列表中选择多个值。

此外,MySQL还支持一些JSON数据类型(JSON和JSONB),用于存储JSON格式的数据。MySQL 5.7版本之后还引入了SPATIAL数据类型,用于存储地理位置信息。

不同的数据类型有不同的存储空间和取值范围,选择合适的数据类型可以提高数据库的性能和存储效率。在设计数据库表时,需要根据实际业务需求来选择适当的数据类型。

64.跨域的解决办法✅

跨域是指在浏览器中,一个网页的Javascript代码向另一个域名下的服务器发送请求时,由于浏览器的同源策略(Same-Origin Policy)限制,请求会被阻止。同源策略是一种安全机制,它防止一个网页的Javascript代码去访问另一个域名下的资源,从而保护用户的安全和隐私。

解决跨域问题有以下几种常见的办法:

  1. 使用CORS(跨域资源共享): 在服务器端设置CORS头部信息,允许指定的域名来访问资源。在大多数现代浏览器中,支持CORS。服务器在响应中设置Access-Control-Allow-Origin头部,指定允许访问的域名。

  2. JSONP: JSONP是一种跨域请求的方式,通过在页面上动态插入一个

你可能感兴趣的:(java,后端)