跳到主要内容
Java 核心面试知识点汇总 | 极客日志
Java java 算法
Java 核心面试知识点汇总 Java 开发核心面试知识点,涵盖 Java 基础(数据类型、面向对象、异常处理、多线程)、数据库(MySQL 索引、事务、SQL 优化)以及框架(Spring、Spring Boot)。内容包含常见面试题解答与原理分析,适合求职者复习参考。
FrontendX 发布于 2026/3/29 更新于 2026/6/3 27 浏览1. Java 基础
1.1 为什么 Java 代码可以实现一次编写、到处运行?
在程序运行前,Java 源代码(.java)需要经过编译器编译成字节码(.class)。在程序运行时,JVM 负责将字节码翻译成特定平台下的机器码并运行,也就是说,只要在不同的平台上安装对应的 JVM,就可以运行字节码文件。
1.2 一个 Java 文件里可以有多个类吗(不含内部类)?
一个 java 文件里可以有多个类,但最多只能有一个被 public 修饰的类;
如果这个 java 文件中包含 public 修饰的类,则这个类的名称必须和 java 文件名一致。
1.3 说一说你对 Java 访问权限的了解?
同一个类 同一个包 不同包的子类 不同包的非子类 private √ default √ √ protected √ √ √ public √ √ √ √
1.4 介绍一下 Java 的数据类型?
Java 数据类型包括基本数据类型和引用数据类型两大类。
基本数据类型有 8 个,可以分为 4 个小类,分别是整数类型(byte/short/int/long)、浮点类型(float/double)、字符类型(char)、布尔类型(boolean)。其中,4 个整数类型中,int 类型最为常用。2 个浮点类型中,double 最为常用。另外,在这 8 个基本类型当中,除了布尔类型之外的其他 7 个类型,都可以看做是数字类型,它们相互之间可以进行类型转换。
引用类型就是对一个对象的引用,根据引用对象类型的不同,可以将引用类型分为 3 类,即数组、类、接口类型。引用类型本质上就是通过指针,指向堆中对象所持有的内存空间,只是 Java 语言不再沿用指针这个说法而已。
扩展阅读
对于基本数据类型,你需要了解每种类型所占据的内存空间,面试官可能会追问这类问题:
byte:1 字节(8 位),数据范围是 -2^7 ~ 2^7-1。
short:2 字节(16 位),数据范围是 -2^15 ~ 2^15-1。
int:4 字节(32 位),数据范围是 -2^31 ~ 2^31-1。
long:8 字节(64 位),数据范围是 -2^63 ~ 2^63-1。
float:4 字节(32 位),数据范围大约是 -3.410^38 ~ 3.4 10^38。
double:8 字节(64 位),数据范围大约是 -1.810^308 ~ 1.8 10^308。
char:2 字节(16 位),数据范围是 \u0000 ~ \uffff。
boolean:Java 规范没有明确的规定,不同的 JVM 有不同的实现机制。
1.5 int 类型的数据范围是多少?
int 类型占 4 字节(32 位),数据范围是 -2^31 ~ 2^31-1。
1.6 请介绍全局(成员)变量和局部变量的区别?
Java 中的变量分为成员变量和局部变量,它们的区别如下:
成员变量:
成员变量是在类的范围里定义的变量;
成员变量有默认初始值;
局部变量:
局部变量是在方法里定义的变量;
局部变量没有默认初始值;
1.7 请介绍一下实例变量的默认值?
实例变量若为引用数据类型,其默认值一律为 null。若为基本数据类型,其默认值如下:
byte:0
short:0
int:0
long:0L
float:0.0F
double:0.0
char:'\u0000'
boolean:false
1.8 为啥要有包装类? Java 语言是面向对象的语言,其设计理念是'一切皆对象'。但 8 种基本数据类型却出现了例外,它们不具备对象的特性。正是为了解决这个问题,Java 为每个基本数据类型都定义了一个对应的引用类型,这就是包装类。
1.9 说一说自动装箱、自动拆箱的应用场景? 自动装箱:可以把一个基本类型的数据直接赋值给对应的包装类型;
自动拆箱:可以把一个包装类型的对象直接赋值给对应的基本类型;
1.10 如何对 Integer 和 Double 类型判断相等? Integer、Double 不能直接进行比较。整数、浮点类型的包装类,都继承于 Number 类型,而 Number 类型分别定义了将数字转换为 byte、short、int、long、float、double 的方法。所以,可以将 Integer、Double 先转为转换为相同的基本数据类型(如 double),然后使用==进行比较。
Integer i = 100 ;
Double d = 100.00 ;
System.out.println(i.doubleValue() == d.doubleValue());
1.11 int 和 Integer 有什么区别,二者在做==运算时会得到什么结果? int 是基本数据类型,Integer 是 int 的包装类。二者在做==运算时,Integer 会自动拆箱为 int 类型,然后再进行比较。届时,如果两个 int 值相等则返回 true,否则就返回 false。
1.12 说一说你对面向对象的理解? 什么是对象:
对象就是事物存在的实体,万物皆对象。举个简单的例子,比如人类就是一个对象,然而对象是有属性和方法的,那么姓名,年龄,身高,体重,性别这些是每个人都有的特征可以概括为属性,当然了我们还会思考,学习,这些行为相当于对象的方法。不过,不同的对象就会有不同的行为。
面向对象:
面向对象是一种优秀的程序设计方法,就是把数据及其操作方法放在一起,将其看成一个整体。对同类对象抽象出其共性,形成类。再答三大特征。
1.13 面向对象的三大特征是什么? 面向对象的程序设计方法具有三个基本特征:封装、继承、多态。其中,封装指的是将对象的实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能;继承是面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法;多态指的是子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。
1.14 封装的目的是什么,为什么要有封装? 封装是面向对象编程语言对客观世界的模拟,在客观世界里,对象的状态信息都被隐藏在对象内部,外界无法直接操作和修改。对一个类或对象实现良好的封装,可以实现以下目的:
隐藏类的实现细节;
限制对成员变量的不合理访问;
提高代码的可维护性。
1.15 说一说你对多态的理解? 现实中的事物通常会体现出多种形态,例如学生小明既是学生也是人,即出现了两种形态。Java 作为面向对象的语言,同样可以描述一个事物的多种形态,如 Student 类继承了 Person 类,即一个 Student 的对象既是 Student,又是 Person。
1.16 Java 中的多态是怎么实现的? 多态的实现离不开继承,在设计程序时,我们可以将参数的类型定义为父类型。在调用程序时,则可以根据实际情况,传入该父类型的某个子类型的实例,这样就实现了多态。对于父类型,可以有三种形式,即普通的类、抽象类、接口。对于子类型,则要根据它自身的特征,重写父类的某些方法,或实现抽象类/接口的某些抽象方法。
1.17 Java 为什么是单继承,为什么不能多继承? 首先,Java 是单继承的,指的是 Java 中一个类只能有一个直接的父类。Java 不能多继承,则是说 Java 中一个类不能直接继承多个父类。
Java 语言之所以摒弃了多继承的这项特征,是因为多继承容易产生混淆。比如,两个父类中包含相同的方法时,子类在调用该方法或重写该方法时就会迷惑。
准确来说,Java 是可以实现"多继承"的。因为尽管一个类只能有一个直接父类,但是却可以有任意多个间接的父类。这样的设计方式,避免了多继承时所产生的混淆。
1.18 说一说重写与重载的区别? 重载发生在同一个类中,若多个方法之间方法名相同、参数列表不同,则它们构成重载的关系。重载与方法的返回值以及访问修饰符无关,即重载的方法不能根据返回类型进行区分。
重写发生在父类子类中,若子类方法想要和父类方法构成重写关系,则它的方法名、参数列表必须与父类方法相同。另外,返回值要小于等于父类方法,抛出的异常要小于等于父类方法,访问修饰符则要大于等于父类方法。还有,若父类方法的访问修饰符为 private,则子类不能对其重写。
1.19 Jdk,Jre,JVM 的关系? JDK 中有一个名为 jre 的目录,里面包含两个文件夹 bin 和 lib,bin 就是 JVM,lib 就是 JVM 工作所需要的类库。
1.20 介绍一下 Object 类中的方法?
Class<?> getClass():返回该对象的运行时类。
boolean equals(Object obj):判断指定对象与该对象是否相等。
int hashCode():返回该对象的 hashCode 值。在默认情况下,Object 类的 hashCode() 方法根据该对象的地址来计算。但很多类都重写了 Object 类的 hashCode() 方法,不再根据地址来计算其 hashCode() 方法值。
String toString():返回该对象的字符串表示,当程序使用 System.out.println() 方法输出一个对象,或者把某个对象和字符串进行连接运算时,系统会自动调用该对象的 toString() 方法返回该对象的字符串表示。Object 类的 toString() 方法返回 运行时类名@十六进制 hashCode 值 格式的字符串,但很多类都重写了 Object 类的 toString() 方法,用于返回可以表述该对象信息的字符串。
1.21 说一说 hashCode() 和 equals() 的关系? hashCode() 用于获取哈希码(散列码),equals() 用于比较两个对象是否相等,它们应遵守如下规定:
如果两个对象相等,则它们必须有相同的哈希码。
如果两个对象有相同的哈希码,则它们未必相等。
1.22 为什么要重写 hashCode() 和 equals()? Object 类提供的 equals() 方法默认是用==来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。而实际的业务中,我们通常的需求是,若两个不同的对象它们的内容是相同的,就认为它们相等。鉴于这种情况,Object 类中 equals() 方法的默认实现是没有实用价值的,所以通常都要重写。
equals() 方法重写时,通常也要将 hashCode() 进行重写。
1.23 ==和 equals() 有什么区别?
对于基本类型,==比较的是值;
对于引用类型,==比较的是地址;
equals 不能用于基本类型的比较;
如果没有重写 equals,equals 就相当于==;
如果重写了 equals 方法,equals 比较的是对象的内容。
1.24 String 类有哪些方法? String 类是 Java 最常用的 API,它包含了大量处理字符串的方法,比较常用的有:
char charAt(int index):返回指定索引处的字符;
String substring(int beginIndex, int endIndex):从此字符串中截取出一部分子字符串;
String[] split(String regex):以指定的规则将此字符串分割成数组;
int indexOf(String str):返回子串在此字符串首次出现的索引;
int lastIndexOf(String str):返回子串在此字符串最后出现的索引。
1.25 说一说 String,StringBuffer 和 StringBuilder? 当对字符串进行修改的时候,特别是字符串对象经常改变的情况下,需要使用 StringBuffer 和 StringBuilder 类。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
1.26 使用字符串时,new 和""推荐使用哪种方式? 先看看 "hello" 和 new String("hello") 的区别:
当 Java 程序直接使用 "hello" 的字符串直接量时,JVM 将会使用常量池来管理这个字符串;
当使用 new String("hello") 时,JVM 会先使用常量池来管理 "hello" 直接量,再调用 String 类的构造器来创建一个新的 String 对象,新创建的 String 对象被保存在堆内存中。
显然,采用 new 的方式会多创建一个对象出来,会占用更多的内存,所以一般建议使用直接量的方式创建字符串。
1.27 两个字符串相加的底层是如何实现的? 如果拼接的都是字符串直接量,则在编译时编译器会将其直接优化为一个完整的字符串,和你直接写一个完整的字符串是一样的。
如果拼接的字符串中包含变量,则在编译时编译器采用 StringBuilder 对其进行优化,即自动创建 StringBuilder 实例并调用其 append() 方法,将这些字符串拼接在一起。
1.28 String a = "abc"; ,说一下这个过程会创建什么,放在哪里? JVM 会使用常量池来管理字符串直接量。在执行这句话时,JVM 会先检查常量池中是否已经存有"abc",若没有则将"abc"存入常量池,否则就复用常量池中已有的"abc",将其引用赋值给变量 a。
1.29 null 和""的区别? null 是没有地址,""是有地址但是里面的内容是空的,好比做饭 null 说明连锅都没有 而""则是有锅没米。
1.30 接口和抽象类有什么区别? 接口使用 interface 修饰;
类可以实现多个接口;
抽象类使用 abstract 修饰;
抽象类只能单继承;
如果一个类继承了抽象类,①如果实现了所有的抽象方法,子类可以不是抽象类;②如果没有实现所有的抽象方法,子类仍然是抽象类。
1.31 接口中可以有构造函数吗? 由于接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。接口里可以包含成员变量(只能是静态常量)、方法(只能是抽象实例方法、类方法、默认方法或私有方法)、内部类(包括内部接口、枚举)定义。
1.32 谈谈你对面向接口编程的理解? 面向接口编程就是先把客户的业务逻辑线提取出来,作为接口,业务具体实现通过该接口的实现类来完成。当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件 (例如 Spring 框架) 中该接口的实现类就可以完成需求,不需要改写现有代码,减少对系统的影响。
1.33 遇到过异常吗,如何处理?
捕获异常将业务代码包裹在 try 块内部,当业务代码中发生任何异常时,系统都会为此异常创建一个异常对象。创建异常对象之后,JVM 会在 try 块之后寻找可以处理它的 catch 块,并将异常对象交给这个 catch 块处理。
处理异常在 catch 块中处理异常时,应该先记录日志,便于以后追溯这个异常。然后根据异常的类型、结合当前的业务情况,进行相应的处理。比如,给变量赋予一个默认值、直接返回空值、向外抛出一个新的业务异常交给调用者处理,等等。java.lang.NullPointerException(空指针异常) 所谓的空指针异常,就是一个指针是空指针(地址都没有分配),你还要去操作它,既然它指向的是空对象,它就不能使用这个对象的方法。比如 String s 中的 s 假如为 null,你还要用 s 的方法,比如 s.equals(String x); 那么就会产生空指针异常。
1.34 说一说 Java 的异常机制? 在 Java 中,处理异常的语句由 try、catch、finally 三部分组成。其中,try 块用于包裹业务代码,catch 块用于捕获并处理某个类型的异常,finally 块则用于回收资源。当业务代码发生异常时,系统会创建一个异常对象,然后由 JVM 寻找可以处理这个异常的 catch 块,并将异常对象交给这个 catch 块处理。若业务代码打开了某项资源,则可以在 finally 块中关闭这项资源,因为无论是否发生异常,finally 块一定会执行。
当程序出现错误时,系统会自动抛出异常。除此以外,Java 也允许程序主动抛出异常。当业务代码中,判断某项错误的条件成立时,可以使用 throw 关键字向外抛出异常。在这种情况下,如果当前方法不知道该如何处理这个异常,可以在方法签名上通过 throws 关键字声明抛出异常,则该异常将交给 JVM 处理。
1.35 finally 是无条件执行的吗? 不管 try 块中的代码是否出现异常,也不管哪一个 catch 块被执行,甚至在 try 块或 catch 块中执行了 return 语句,finally 块总会被执行。
1.36 说一说你对 static 关键字的理解? 一个类的不同对象有些共享的数据,这样我们就可以使用 static 来修饰,一旦使用了 static,那么这样的内容不再属于对象,而是属于类的,所以凡是本类的对象,都共享同一份。它可以用来修饰成员变量,修饰成员方法,以及静态代码块。
1.37 static 修饰的类能不能被继承?
1.38 static 和 final 有什么区别? static:
此变量会被这个类的所有对象所共享,这些对象都可以调用、改变它的值;
无需创建对象也可以调用此方法;
静态方法只可以访问 静态的 属性/变量/方法。
final:
final 属性不可被外部更改,而且必须初始化。
1.39 说一说你对 Java 反射机制的理解? Java 程序中的对象在运行时可以表现为两种类型,即编译时类型和运行时类型。例如 Person p = new Student();,这行代码将会生成一个 p 变量,该变量的编译时类型为 Person,运行时类型为 Student。
有时,程序在运行时接收到外部传入的一个对象,该对象的编译时类型是 Object,但程序又需要调用该对象的运行时类型的方法。这就要求程序需要在运行时发现对象和类的真实信息,而解决这个问题就是反射。
1.40 介绍一下 Java 的序列化与反序列化? 序列化机制可以将对象转换成字节序列,这些字节序列可以保存在磁盘上,也可以在网络中传输,并允许程序将这些字节序列再次恢复成原来的对象。其中,对象的序列化(Serialize),是指将一个 Java 对象写入 IO 流中,对象的反序列化(Deserialize),则是指从 IO 流中恢复该 Java 对象。
若对象要支持序列化机制,则它的类需要实现 Serializable 接口,该接口是一个标记接口,它没有提供任何方法,只是标明该类是可以序列化的,Java 的很多类已经实现了 Serializable 接口,如包装类、String、Date 等。
1.41 进程,线程,多线程? 线程:线程是 CPU 调度和分派的基本单位,它被包含在进程之中,是进程中的实际运作单位。
多线程:多个线程同时运行。比如说地图导航,当油站过多时,一次查询会很耗时,所以我们可以将一段很长的路线按照路径长度分成若干个条件同时查询,或者说某个程序多个功能同时运转。
1.42 并发和并行? 并发:在同一时刻,有多个指令在单个 CPU 上交替执行
并行:在同一时刻,有多个指令在多个 CPU 上同时执行
1.43 创建线程有哪几种方式? 创建线程的方式有继承 Thread 类、实现 Runnable 接口。
通过继承 Thread 类(Thread 类实现了 Runnable 接口)来创建并启动线程的步骤如下:
定义 Thread 类的子类,并重写该类的 run() 方法。
创建 Thread 子类的实例。
调用线程对象的 start() 方法来启动该线程。
通过实现 Runnable 接口来创建并启动线程的步骤如下:
定义 Runnable 接口的实现类,并实现该接口的 run() 方法。
创建 Runnable 实现类的实例。
调用线程对象的 start() 方法来启动该线程。
采用实现 Runnable 接口的方式创建多线程的优缺点:
线程类只是实现了 Runnable 接口,还可以继承其他类。
劣势是,编程稍稍复杂,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法。
采用继承 Thread 类的方式创建多线程的优缺点:
劣势是,因为线程类已经继承了 Thread 类,所以不能再继承其他父类。
优势是,编写简单,如果需要访问当前线程,则无须使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
鉴于上面分析,因此一般推荐采用实现 Runnable 接口的方式来创建多线程。
1.44 说说 Thread 类的常用方法?
Thread():构造方法;
currentThread():返回当前正在执行的线程;
sleep(long millis):使当前执行的线程睡眠多少毫秒数;
getId():返回该线程的 id;
getName():返回该线程的名字;
getPriority():返回该线程的优先级;(默认是 5,优先级越高,只能说明抢到线程的概率会大点)
interrupt():使该线程中断;
isInterrupted():返回该线程是否被中断;
setName(String name):设置该线程的名字;
setPriority(int newPriority):改变该线程的优先级。
1.45 run() 和 start() 有什么区别? 2、run 方法只是 thread 的一个普通方法,在主线程里执行。
1.46 线程是否可以重复启动,会有什么后果? 只能对处于新建状态的线程调用 start() 方法,否则将引发 IllegalThreadStateException 异常。
1.47 介绍一下线程的生命周期? 在线程的生命周期中,它要经过新建(New)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直'霸占'着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、就绪之间切换。
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的 Java 对象一样,仅仅由 Java 虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。
当线程对象调用了 start() 方法之后,该线程处于就绪状态,Java 虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于 JVM 里线程调度器的调度。
如果处于就绪状态的线程获得了 CPU,开始执行 run() 方法的线程执行体,则该线程处于运行状态,如果计算机只有一个 CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程并行执行;当线程数大于处理器数时,依然会存在多个线程在同一个 CPU 上轮换的现象。
当一个线程开始运行后,它不可能一直处于运行状态,线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务。当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。当发生如下情况时,线程将会进入阻塞状态:
线程调用 sleep() 方法主动放弃所占用的处理器资源。
线程调用了一个阻塞式 IO 方法,在该方法返回之前,该线程被阻塞。
线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
线程在等待某个通知(notify)。
程序调用了线程的 suspend() 方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。
针对上面几种情况,当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态:
调用 sleep() 方法的线程经过了指定时间。
线程调用的阻塞式 IO 方法已经返回。
线程成功地获得了试图取得的同步监视器。
线程正在等待某个通知时,其他线程发出了一个通知。
处于挂起状态的线程被调用了 resume() 恢复方法。
run() 或 call() 方法执行完成,线程正常结束。
线程抛出一个未捕获的 Exception 或 Error。
直接调用该线程的 stop() 方法来结束该线程,该方法容易导致死锁,通常不推荐使用。
1.48 三次握手四次挥手? ①四川 8633 请求建立连接 (SYN),并且发送出序号。
②服务端接受到信号,即有确认号(ACK),此时并同样返回请求序号 Seq
③客户端接受到信号,即有确认号(ACK),连接已经建立
①客户端申请断开连接即 FIN(我这边准备断开连接了)
②服务端接收信息返回,表示我已经接收到(收到,请稍等,我这边准备一下)
③服务端发送信息表示可以断开连接 (我准备好了,你可以断开连接了)
④客户端接受信息,同时返回信息通知服务端自己收到信息,开始断开 连接 (好的,拜拜!)
1.49 HTTP 与 HTTPS 有什么区别? HTTPS 简单讲是 HTTP 的安全版,完全不同的连接方式,HTTPS 一般免费证书较少,因而需要一定费用。
1.50 如何将字符串反转? 将对象封装到 stringBuilder 中,调用 reverse 方法反转。
1.51 普通类和抽象类有哪些区别? 抽象类不能被实例化;
抽象类可以有抽象方法,只需申明,无须实现;
有抽象方法的类一定是抽象类;
抽象类的子类必须实现抽象类中的所有抽象方法,否则子类仍然是抽象类;
抽象方法不能被 static、final 修饰。
1.52 final 在 java 中有什么作用? (1)用来修饰一个引用
如果引用时类的成员变量,则必须当场赋值,其值不可变。
(2)用来修饰一个方法
当使用 final 修饰方法时,这个方法可以被继承,但无法被重写。
(3)用来修饰类
当用 final 修改类时,无法被继承。比如常用的 String 类就是最终类。
1.53 get 和 post 请求有哪些区别? get 请求因为浏览器对 url 长度有限制,所以参数个数有限制,而 post 请求参数个数没有限制;
因为 get 请求参数暴露在 url 上,所以安全方面 post 比 get 更加安全;
get 请求只能进行 url 编码,而 post 请求可以支持多种编码方式;
在浏览器进行回退操作时,get 请求是无害的,而 post 请求则会重新请求一次。
1.54 常用排序算法? ①冒泡排序:每次冒泡过程都是从数列的第一个元素开始,然后依次和剩余的元素进行比较,从左到右两两相邻的元素比大小,高的就和低的换一下位置。最后最高 (值最大) 的肯定就排到后面了。
②选择排序:首先在未排序数列中找到最小元素,然后将其与数列的首部元素进行交换,然后,在剩余未排序元素中继续找出最小元素,将其与已排序数列的末尾位置元素交换。以此类推,直至所有元素均排序完毕。
③插入排序:将待插元素,依次与已排序好的子数列元素从后到前进行比较,如果当前元素值比待插元素值大,则将移位到与其相邻的后一个位置,否则直接将待插元素插入当前元素相邻的后一位置,因为说明已经找到插入点的最终位置。
④快速排序:简单的说,就是设置一个标准值,将大于这个值的放到右边 (不管排序),将小于这个值的放到左边 (不管排序),那么这样只是区分了左小右大,没有排序,没关系,左右两边再重复这个步骤。直到不能分了为止。
1.55 队列和栈是什么?有什么区别? 栈只能从头部取数据 也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性;
队列则不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多。
2. 数据库
2.1 介绍一下数据库分页? 在 MySQL 中,SELECT 语句默认返回所有匹配的行,它们可能是指定表中的每个行。为了返回第一行或前几行,可使用 LIMIT 子句,以实现分页查询。LIMIT 子句的语法如下:
-- 在所有的查询结果中,返回前 5 行记录。
SELECT prod_name FROM products LIMIT 5;
-- 在所有的查询结果中,从第 5 行开始,返回 5 行记录。
SELECT prod_name FROM products LIMIT 5,5;
总之,带一个值的 LIMIT 总是从第一行开始,给出的数为返回的行数。带两个值的 LIMIT 可以指定从行号为第一个值的位置开始。
在偏移量非常大的时候,例如 LIMIT 10000,20 这样的查询,这时 MySQL 需要查询 10020 条记录然后只返回最后 20 条,前面的 10000 条记录都将被抛弃,这样的代价是非常高的。如果所有的页面被访问的频率都相同,那么这样的查询平均需要访问半个表的数据。要优化这种查询,要么是在页面中限制分页的数量,要么是优化大偏移量的性能。
优化此类分页查询的一个最简单的办法就是尽可能地使用索引覆盖扫描,而不是查询所有的列,然后根据需要做一次关联操作再返回所需的列。对于偏移量很大的时候,这样做的效率会提升非常大。考虑下面的查询:
SELECT film_id,description FROM sakila.film ORDER BY title LIMIT 50,5;
如果这个表非常大,那么这个查询最好改写成下面的样子:
SELECT film.film_id,film.description FROM sakila.film INNER JOIN ( SELECT film_id FROM sakila.film ORDER BY title LIMIT 50,5 ) AS lim USING(film_id);
2.2 介绍一下 SQL 中的聚合函数? 常用的聚合函数有 COUNT()、AVG()、SUM()、MAX()、MIN(),下面以 MySQL 为例,说明这些函数的作用。
COUNT() 函数统计数据表中包含的记录行的总数,或者根据查询结果返回列中包含的数据行数,它有两种用法:
COUNT(*) 计算表中总的行数,不管某列是否有数值或者为空值。
COUNT(字段名) 计算指定列下总的行数,计算时将忽略空值的行。
COUNT() 函数可以与 GROUP BY 一起使用来计算每个分组的总和。
AVG() 函数通过计算返回的行数和每一行数据的和,求得指定列数据的平均值。
AVG() 函数可以与 GROUP BY 一起使用,来计算每个分组的平均值。
SUM() 是一个求总和的函数,返回指定列值的总和。
SUM() 可以与 GROUP BY 一起使用,来计算每个分组的总和。
MAX() 也可以和 GROUP BY 关键字一起使用,求每个分组中的最大值。
MAX() 函数不仅适用于查找数值类型,也可应用于字符类型。
MIN() 也可以和 GROUP BY 关键字一起使用,求出每个分组中的最小值。
MIN() 函数与 MAX() 函数类似,不仅适用于查找数值类型,也可应用于字符类型。
2.3 表跟表是怎么关联的? 表与表之间常用的关联方式有两种:内连接、外连接,下面以 MySQL 为例来说明这两种连接方式。
内连接通过 INNER JOIN 来实现,它将返回两张表中满足连接条件的数据,不满足条件的数据不会查询出来。
外连接通过 OUTER JOIN 来实现,它会返回两张表中满足连接条件的数据,同时返回不满足连接条件的数据。外连接有两种形式:左外连接(LEFT OUTER JOIN)、右外连接(RIGHT OUTER JOIN)。
左外连接:可以简称为左连接(LEFT JOIN),它会返回左表中的所有记录和右表中满足连接条件的记录。
右外连接:可以简称为右连接(RIGHT JOIN),它会返回右表中的所有记录和左表中满足连接条件的记录。
除此之外,还有一种常见的连接方式:等值连接。这种连接是通过 WHERE 子句中的条件,将两张表连接在一起,它的实际效果等同于内连接。出于语义清晰的考虑,一般更建议使用内连接,而不是等值连接。
以上是从语法上来说明表与表之间关联的实现方式,而从表的关系上来说,比较常见的关联关系有:一对多关联、多对多关联、自关联。
一对多关联:这种关联形式最为常见,一般是两张表具有主从关系,并且以主表的主键关联从表的外键来实现这种关联关系。另外,以从表的角度来看,它们是具有多对一关系的,所以不再赘述多对一关联了。
多对多关联:这种关联关系比较复杂,如果两张表具有多对多的关系,那么它们之间需要有一张中间表来作为衔接,以实现这种关联关系。这个中间表要设计两列,分别存储那两张表的主键。因此,这两张表中的任何一方,都与中间表形成了一对多关系,从而在这个中间表上建立起了多对多关系。
自关联:自关联就是一张表自己与自己相关联,为了避免表名的冲突,需要在关联时通过别名将它们当做两张表来看待。一般在表中数据具有层级(树状)时,可以采用自关联一次性查询出多层级的数据。
2.4 谈谈你对 SQL 注入的理解? SQL 注入的原理是将 SQL 代码伪装到输入参数中,传递到服务器解析并执行的一种攻击手法。也就是说,在一些对 server 端发起的请求参数中植入一些 SQL 代码,server 端在执行 SQL 操作时,会拼接对应参数,同时也将一些 SQL 注入攻击的"SQL"拼接起来,导致会执行一些预期之外的操作。
比如我们的登录功能,其登录界面包括用户名和密码输入框以及提交按钮,登录时需要输入用户名和密码,然后提交。此时调用接口/user/login/ 加上参数 username、password,首先连接数据库,然后后台对请求参数中携带的用户名、密码进行参数校验,即 SQL 的查询过程。假设正确的用户名和密码为 ls 和 123456,输入正确的用户名和密码、提交,相当于调用了以下的 SQL 语句。
SELECT * FROM user WHERE username = 'ls' AND password = '123456'
SQL 中会将#及--以后的字符串当做注释处理,如果我们使用 ' or 1=1 # 作为用户名参数,那么服务端构建的 SQL 语句就如下:
select * from user where or 1=1 #' and password='123456'
而#会忽略后面的语句,而 1=1 属于常等型条件,因此这个 SQL 将查询出所有的登录用户。其实上面的 SQL 注入只是在参数层面做了些手脚,如果是引入了一些功能性的 SQL 那就更危险了,比如上面的登录功能,如果用户名使用这个 ' or 1=1;delete * from users; #,那么在";"之后相当于是另外一条新的 SQL,这个 SQL 是删除全表,是非常危险的操作,因此 SQL 注入这种还是需要特别注意的。
严格的参数校验参数校验就没得说了,在一些不该有特殊字符的参数中提前进行特殊字符校验即可。
SQL 预编译
2.5 WHERE 和 HAVING 有什么区别? WHERE 是一个约束声明,是在结果返回之前起作用的。
HAVING 是一个过滤声明,是在查询返回结果后对查询结果进行的过滤操作。
2.6 说一说你对 MySQL 索引的理解? 它是帮助 MySQL 高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的 IO 成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了 CPU 的消耗。
增加索引也有许多不利的方面,主要表现在如下几个方面:
创建索引和维护索引要耗费时间。
索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间。
当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。
2.7 索引有哪几种?
普通索引和唯一索引普通索引是 MySQL 中的基本索引类型,允许在定义索引的列中插入重复值和空值。唯一索引要求索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。主键索引是一种特殊的唯一索引,不允许有空值。
单列索引和组合索引单列索引即一个索引只包含单个列,一个表可以有多个单列索引。组合索引是指在表的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用。使用组合索引时遵循最左前缀集合。
2.8 MySQL 怎么判断要不要加索引? 查询比较频繁的字段和像作为查询条件,排序字段或分组的字段。
2.9 如何判断数据库的索引有没有生效? 可以使用 EXPLAIN 语句查看索引是否正在使用。
举例,假设已经创建了 book 表,并已经在其 year_publication 字段上建立了普通索引。执行如下语句:
EXPLAIN SELECT * FROM book WHERE year_publication=1990;
EXPLAIN 语句将为我们输出详细的 SQL 执行信息,其中:
possible_keys 行给出了 MySQL 在搜索数据记录时可选用的各个索引。
key 行是 MySQL 实际选用的索引。
如果 possible_keys 行和 key 行都包含 year_publication 字段,则说明在查询时使用了该索引。
2.10 如何评估一个索引创建的是否合理?
避免对经常更新的表进行过多的索引,并且索引中的列要尽可能少。应该经常用于查询的字段创建索引,但要避免添加不必要的字段。
数据量小的表最好不要使用索引,由于数据较少,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
在条件表达式中经常用到的不同值较多的列上建立索引,在不同值很少的列上不要建立索引。比如在学生表的'性别'字段上只有'男'与'女'两个不同值,因此就无须建立索引,如果建立索引不但不会提高查询效率,反而会严重降低数据更新速度。
当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需能确保定义的列的数据完整性,以提高查询速度。
在频繁进行排序或分组(即进行 group by 或 order by 操作)的列上建立索引,如果待排序的列有多个,可以在这些列上建立组合索引。
2.11 索引是越多越好吗? 索引并非越多越好,一个表中如有大量的索引,不仅占用磁盘空间,还会影响 INSERT、DELETE、UPDATE 等语句的性能,因为在表中的数据更改时,索引也会进行调整和更新。
2.12 什么是联合索引? 联合索引是指对表上的多个列进行索引,联合索引的创建方法与单个索引创建的方法一样,不同之处仅在于有多个索引列。从本质上来说,联合索引还是一棵 B+ 树,不同的是联合索引的键值数量不是 1,而是大于等于 2,参考下图。
另外,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,所以使用联合索引时遵循最左前缀集合。
2.13 MySQL 中,如何定位慢查询? 我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了 2 秒以上,因为我们当时的系统部署了运维的监控系统 Skywalking,在展示的报表中可以看到是哪一个接口比较慢,并且可以分析这个接口哪部分比较慢,这里可以看到 SQL 的具体执行时间,所以可以定位是哪个 sql 出了问题。
如果,项目中没有这种运维的监控系统,其实在 MySQL 中也提供了慢日志查询的功能,可以在 MySQL 的系统配置文件中开启这个慢日志的功能,并且也可以设置 SQL 执行超过多少时间来记录到一个日志文件中,我记得上一个项目配置的是 2 秒,只要 SQL 执行的时间超过了 2 秒就会记录到一个日志文件中,我们就可以在日志文件找到执行比较慢的 SQL 了。
2.14 那这个 SQL 语句执行很慢,如何分析呢? 如果一条 sql 执行很慢的话,我们通常会使用 mysql 自动的执行计划 explain 来去查看这条 sql 的执行情况,比如在这里面可以通过 key 和 key_len 检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况,第二个,可以通过 type 字段查看 sql 是否有进一步的优化空间,是否存在全索引扫描或全盘扫描,第三个可以通过 extra 建议来判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复。
2.15 索引的底层数据结构了解过嘛? MySQL 的默认的存储引擎 InnoDB 采用的 B+ 树的数据结构来存储索引,选择 B+ 树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价 B+ 树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是 B+ 树便于扫库和区间查询,叶子节点是一个双向链表。
2.16 B 树和 B+ 树的区别是什么呢? 第一:在 B 树中,非叶子节点和叶子节点都会存放数据,而 B+ 树的所有的数据都会出现在叶子节点,在查询的时候,B+ 树查找效率更加稳定;
第二:在进行范围查询的时候,B+ 树效率更高,因为 B+ 树都在叶子节点存储,并且叶子节点是一个双向链表。
2.17 什么是聚簇索引什么是非聚簇索引? 聚簇索引主要是指数据与索引放到一块,B+ 树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚簇索引的;
非聚簇索引值的是数据与索引分开存储,B+ 树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚簇索引。
2.18 知道什么是回表查询嘛? 其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表。
【备注:如果面试官直接问回表,则需要先介绍聚簇索引和非聚簇索引】
2.19 知道什么叫覆盖索引嘛? select 后面查询的字段都可以从这个索引的树中获取,这种情况一般可以说是用到了覆盖索引。
2.20 什么情况下索引会失效? 索引在使用的时候没有遵循最左匹配法则和模糊查询,如果%号在前面也会导致索引失效。
通常情况下,想要判断出这条 sql 是否有索引失效的情况,可以使用 explain 执行计划来分析。
2.21 sql 的优化的经验? 这个在项目还是挺常见的,当然如果直说 sql 优化的话,我们会从这几方面考虑,比如:建表的时候、使用索引、sql 语句的编写、主从复制,读写分离,还有一个是如果量比较大的话,可以考虑分库分表。
2.22 事务的特性是什么?可以详细说一下吗? ACID,分别指的是:原子性、一致性、隔离性、持久性;我举个例子:
A 向 B 转账 500,转账成功,A 扣除 500 元,B 增加 500 元,原子操作体现在要么都成功,要么都失败
在转账的过程中,数据要一致,A 扣除了 500,B 必须增加 500
在转账的过程中,隔离性体现在 A 像 B 转账,不能受其他事务干扰
在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作)
2.23 并发事务带来哪些问题? 我们在项目开发中,多个事务并发进行是经常发生的,并发也是必然的,有可能导致一些问题
第一是脏读,当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是'脏数据',依据'脏数据'所做的操作可能是不正确的。
第二是不可重复读:比如在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
第三是幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
2.24 怎么解决这些问题呢?MySQL 的默认隔离级别是? 第一个是,未提交读(read uncommitted)它解决不了刚才提出的所有问题,一般项目中也不用这个。第二个是读已提交(read committed)它能解决脏读的问题的,但是解决不了不可重复读和幻读。第三个是可重复读(repeatable read)它能解决脏读和不可重复读,但是解决不了幻读,这个也是 mysql 默认的隔离级别。第四个是串行化(serializable)它可以解决刚才提出来的所有问题,但是由于让是事务串行执行的,性能比较低。所以,我们一般使用的都是 mysql 默认的隔离级别:可重复读。
2.25 undo log 和 redo log 的区别? 其中 redo log 日志记录的是数据页的物理变化,服务宕机可用来同步数据,而 undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在 undo log 日志文件中新增一条 delete 语句,如果发生回滚就执行逆操作;
redo log 保证了事务的持久性,undo log 保证了事务的原子性和一致性。
2.26 事务中的隔离性是如何保证的呢?(你解释一下 MVCC)
2.27 MySQL 主从同步原理? MySQL 主从复制的核心就是二进制日志 (DDL(数据定义语言)语句和 DML(数据操纵语言)语句),它的步骤是这样的:
第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
第二:从库读取主库的二进制日志文件 Binlog,写入到从库的中继日志 Relay Log。
第三:从库重做中继日志中的事件,将改变反映它自己的数据。
2.28 分表分库的知识? 垂直分库:以表为依据,根据业务将不同表拆分到不同库中。
垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。
水平分表:将一个表的数据拆分到多个表中 (可以在同一个库内)。
2.29 数据库如何保证主键唯一性? 主键约束:主键列上没有任何两行具有相同值(即重复值),不允许空(NULL);
唯一性约束:保证一个字段或者一组字段里的数据都与表中其它行的对应数据不同。和主键约束不同,唯一性约束允许为 null,但是只能有一行;
唯一性索引:不允许具有索引值相同的行,从而禁止重复的索引和键值。
3. 框架
3.1 Spring 框架中的单例 bean 是线程安全的吗? 当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
3.2 什么是 AOP? aop 是面向切面编程,在 spring 中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑封装起来。一般比如可以做为公共日志保存,事务处理等。
3.3 Spring 中的事务是如何实现的? spring 实现的事务本质就是 aop 完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
3.4 Spring 中事务失效的场景有哪些? 第一个,如果方法上异常捕获处理(try catch),自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了抛出去就行了;
第二个,如果方法抛出检查异常,如果报错也会导致事务失效,最后在 spring 事务的注解上,就是@Transactional 上配置 rollbackFor 属性为 Exception,这样别管是什么异常,都会回滚事务。
3.5 Spring 的 bean 的生命周期? 首先会通过一个非常重要的类,叫做 BeanDefinition 获取 bean 的定义信息,这里面就封装了 bean 的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等这些信息。
在创建 bean 的时候,第一步是调用构造函数实例化 bean
第二步是 bean 的依赖注入,比如一些 set 方法注入,像平时开发用的@Autowire 都是这一步完成
第三步是处理 Aware 接口,如果某一个 bean 实现了 Aware 接口就会重写方法执行
第四步是 bean 的后置处理器 BeanPostProcessor,这个是前置处理器
第五步是初始化方法,比如实现了接口 InitializingBean 或者自定义了方法 init-method 标签或@PostContruct
第六步是执行了 bean 的后置处理器 BeanPostProcessor,主要是对 bean 进行增强,有可能在这里产生代理对象
3.6 Spring 中的循环引用? 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B,B 依赖于 A。
循环依赖在 spring 中是允许存在,spring 框架依据三级缓存已经解决了大部分的循环依赖。
①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的 bean 对象
②二级缓存:缓存早期的 bean 对象(生命周期还没走完)
③三级缓存:缓存的是 ObjectFactory,表示对象工厂,用来创建某个对象的
3.7 构造方法出现了循环依赖怎么解决? 由于 bean 的生命周期中构造函数是第一个执行的,spring 框架并不能解决构造函数的的依赖注入,可以使用@Lazy 懒加载,什么时候需要对象再进行 bean 对象的创建。
3.8 SpringMVC 的执行流程? 1、用户发送出请求到前端控制器 DispatcherServlet,这是一个调度中心
2、DispatcherServlet 收到请求调用 HandlerMapping(处理器映射器)。
3、HandlerMapping 找到具体的处理器 (可查找 xml 配置或注解配置),生成处理器对象及处理器拦截器 (如果有),再一起返回给 DispatcherServlet。
4、DispatcherServlet 调用 HandlerAdapter(处理器适配器)。
5、HandlerAdapter 经过适配调用具体的处理器(Handler/Controller)。
6、DispatcherServlet 响应用户。
3.9 Springboot 自动配置原理? 在 Spring Boot 项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
其中@EnableAutoConfiguration是实现自动化配置的核心注解。
该注解通过@Import注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的 Jar 包的的 classpath 路径下 META-INF/spring.factories 文件中的所配置的类的全类名。
在这些配置类中所定义的 Bean 会根据条件注解所指定的条件来决定是否需要将其导入到 Spring 容器中。
一般条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的 class 文件,如果有则加载该类,把这个配置类的所有的 Bean 放入 spring 容器中使用。
3.10 Spring 的常见注解有哪些? 第一类是:声明 bean,有@Component、@Service、@Repository、@Controller、@RestController(@RestController=@Controller+@ResponseBody)
第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse
第四类是:spring 配置相关的,比如@Configuration,@ComponentScan 和 @Bean
第五类是:跟 aop 相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut
3.11 SpringMVC 常见的注解有哪些? 有@RequestMapping:用于映射请求路径;
@RequestBody:注解实现接收 http 请求的 json 数据,将 json 转换为 java 对象;
@RequestParam:url 地址传参 参数名称=参数值;
@ResponseBody:注解实现将 controller 方法返回对象转化为 json 对象响应给客户端。
@RequestHeader:获取指定的请求头数据,还有像@PostMapping、@GetMapping 这些。
3.12 Springboot 常见注解有哪些? Spring Boot 的核心注解是@SpringBootApplication , 他由几个注解组成 :
@SpringBootConfiguration:组合了- @Configuration 注解,实现配置文件的功能;
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
@ComponentScan:Spring 组件扫描
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online