前言
Java 25 于 2025 年 9 月 16 日正式发布,作为继 Java 17 和 Java 21 之后的第三个 LTS(长期支持)版本,它带来了 18 个 JEP(JDK Enhancement Proposals)提案,涵盖了语言、库、性能和监控等多个方面的重大改进。本文将深入解析 Java 25 的核心特性,帮助开发者全面了解这一版本的技术突破。
版本概览
| 版本 |
|---|
Java 25 作为继 17 和 21 之后的第三个 LTS 版本,带来了 18 个 JEP 提案。语言层面引入紧凑源文件降低入门门槛,支持灵活构造器体执行前置逻辑,新增模块导入声明简化模块化开发,并增强原语类型模式匹配。库层面提供结构化并发简化线程管理,作用域值替代 ThreadLocal,向量 API 提升 SIMD 性能,以及密钥派生函数和 PEM 编码 API。性能方面,紧凑对象头减少内存占用,AOT 命令行优化加速启动。监控方面增强 JFR CPU 时间分析和协作采样。此外还有 HttpClient 增强、ForkJoinPool 改进及 G1 GC 优化。详细解析了这些特性及迁移指南。
Java 25 于 2025 年 9 月 16 日正式发布,作为继 Java 17 和 Java 21 之后的第三个 LTS(长期支持)版本,它带来了 18 个 JEP(JDK Enhancement Proposals)提案,涵盖了语言、库、性能和监控等多个方面的重大改进。本文将深入解析 Java 25 的核心特性,帮助开发者全面了解这一版本的技术突破。
| 版本 |
|---|
| 发布时间 |
|---|
| 主要特性 |
|---|
| Java 17 LTS | 2021 年 9 月 | 记录类、模式匹配预览、封装类 |
| Java 21 LTS | 2023 年 9 月 | 虚拟线程 (正式)、序列化集合、作用域值 (预览) |
| Java 25 LTS | 2025 年 9 月 | 紧凑源文件、灵活构造器体、模块导入声明、原语类型模式匹配 |
这是 Java 25 中最令人兴奋的特性之一,它极大地简化了初学者的入门门槛,同时也让有经验的开发者能够更简洁地编写小程序。
传统写法:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Java 25 紧凑写法:
void main() {
System.out.println("Hello, World!");
}
完整特性演示:
// 无需类声明,直接写方法
String getGreeting(String name) {
return "Hello, " + name;
}
void main() {
System.out.println(getGreeting("Java 25"));
}
演进路径:
优势分析:
Java 25 允许在构造器体中,在显式构造器调用(super(...)或 this(...))之前执行语句。这使得许多构造器的表达更加自然。
传统限制:
public class Parent {
private final String name;
private final int age;
public Parent(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Child extends Parent {
private final String displayName;
public Child(String name, int age) {
// 传统写法:无法在 super() 之前进行初始化计算
String computedName = name.toUpperCase();
super(computedName, age); // 编译错误!
this.displayName = computedName;
}
}
Java 25 改进:
public class Child extends Parent {
private final String displayName;
public Child(String name, int age) {
// 可以在 super() 之前执行初始化逻辑
String computedName = name.toUpperCase();
this.displayName = computedName;
super(computedName, age);
}
// 更复杂的场景
public Child(String firstName, String lastName, int age) {
// 可以进行复杂的初始化计算
String fullName = computeFullName(firstName, lastName);
this.displayName = fullName;
// 甚至可以验证参数
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
super(fullName, age);
}
private String computeFullName(String first, String last) {
return (first + " " + last).trim();
}
}
安全性保证:
Java 25 引入了模块导入声明,允许简洁地导入模块导出的所有包,大大简化了模块化库的重用。
传统导入方式:
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.Stream;
import java.util.stream.Collectors;
public class Example {
// 使用这些类
}
Java 25 模块导入:
import module java.base; // 导入 java.base 模块的所有导出包
public class Example {
// 可以直接使用 java.base 模块的所有类
public void process() {
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
Set<Integer> set = new HashSet<>();
Stream<String> stream = list.stream();
}
}
实际应用场景:
import module java.sql;
public class DatabaseExample {
public void queryDatabase() throws SQLException {
// 直接使用 java.sql 模块的所有类
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
}
优势:
Java 25 进一步增强了模式匹配,允许在所有模式上下文中使用原语类型,并扩展了 instanceof和 switch以支持所有原语类型。
基本用法:
// instanceof 支持所有原语类型
public void processNumber(Object obj) {
if (obj instanceof int i) {
System.out.println("整数:" + i);
} else if (obj instanceof long l) {
System.out.println("长整数:" + l);
} else if (obj instanceof double d) {
System.out.println("双精度:" + d);
}
}
// switch 支持原语类型
public String getType(Object value) {
return switch (value) {
case int i -> "整数类型:" + i;
case long l -> "长整型:" + l;
case float f -> "浮点型:" + f;
case double d -> "双精度型:" + d;
default -> "其他类型";
};
}
复杂模式匹配:
// 记录类与原语类型结合
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
public void processShape(Object shape) {
if (shape instanceof Circle(Point(int x, int y), int radius)) {
System.out.printf("圆心在 (%d,%d),半径为%d%n", x, y, radius);
}
}
// switch 中的守卫条件
public String describeNumber(Object num) {
return switch (num) {
case int i when i > 0 -> "正整数:" + i;
case int i when i < 0 -> "负整数:" + i;
case int i -> "零";
case double d when d > 0 -> "正浮点数:" + d;
default -> "其他数值";
};
}
结构化并发将运行在不同线程中的相关任务组视为单个工作单元,从而简化了错误处理和取消操作,提高了可靠性和可观测性。
基本概念:
代码示例:
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.Future;
import java.util.function.Supplier;
public class StructuredConcurrencyExample {
// 传统方式:手动管理多个线程
public String fetchUserDataOldWay(String userId) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);
try {
Future<String> userFuture = executor.submit(() -> fetchUser(userId));
Future<List<Order>> ordersFuture = executor.submit(() -> fetchOrders(userId));
Future<List<Review>> reviewsFuture = executor.submit(() -> fetchReviews(userId));
String user = userFuture.get();
List<Order> orders = ordersFuture.get();
List<Review> reviews = reviewsFuture.get();
return combineData(user, orders, reviews);
} finally {
executor.shutdown();
}
}
// Java 25 结构化并发方式
public String fetchUserDataNewWay(String userId) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> userTask = scope.fork(() -> fetchUser(userId));
Supplier<List<Order>> ordersTask = scope.fork(() -> fetchOrders(userId));
Supplier<List<Review>> reviewsTask = scope.fork(() -> fetchReviews(userId));
// 等待所有任务完成,任一失败则取消其他任务
scope.join().throwIfFailed();
return combineData(userTask.get(), ordersTask.get(), reviewsTask.get());
}
}
private String fetchUser(String userId) {
return "User: " + userId;
}
private List<Order> fetchOrders(String userId) {
return List.of(new Order("order1"), new Order("order2"));
}
private List<Review> fetchReviews(String userId) {
return List.of(new Review("review1"), new Review("review2"));
}
private String combineData(String user, List<Order> orders, List<Review> reviews) {
return user + ", Orders: " + orders.size() + ", Reviews: " + reviews.size();
}
}
优势:
作用域值允许方法在其线程内与被调用者共享不可变数据,也可以与子线程共享。相比 ThreadLocal,作用域值更易于理解,空间和时间开销更低。
对比 ThreadLocal:
// 传统 ThreadLocal 方式
public class ThreadLocalExample {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public void processRequest(String requestId) {
CONTEXT.set(requestId);
try {
process1();
process2();
} finally {
CONTEXT.remove(); // 必须手动清理
}
}
private void process1() {
String id = CONTEXT.get(); // 需要显式获取
System.out.println("Process1: " + id);
}
private void process2() {
String id = CONTEXT.get();
System.out.println("Process2: " + id);
}
}
// Java 25 作用域值方式
public class ScopedValueExample {
private static final ScopedValue<String> REQUEST_ID = new ScopedValue<>();
public void processRequest(String requestId) {
ScopedValue.where(REQUEST_ID, requestId).run(() -> {
process1();
process2();
}); // 自动清理,无需手动 remove
}
private void process1() {
String id = REQUEST_ID.get(); // 隐式访问
System.out.println("Process1: " + id);
}
private void process2() {
String id = REQUEST_ID.get();
System.out.println("Process2: " + id);
}
}
与虚拟线程结合:
public class VirtualThreadWithScopedValue {
private static final ScopedValue<String> CONTEXT = new ScopedValue<>();
public void handleRequest(String requestId) {
ScopedValue.where(CONTEXT, requestId).run(() -> {
// 主线程访问
System.out.println("Main: " + CONTEXT.get());
// 虚拟线程自动继承作用域值
Thread.startVirtualThread(() -> {
System.out.println("Virtual Thread: " + CONTEXT.get());
});
});
}
}
嵌套作用域:
public class NestedScopes {
private static final ScopedValue<String> LEVEL1 = new ScopedValue<>();
private static final ScopedValue<String> LEVEL2 = new ScopedValue<>();
public void nestedExample() {
ScopedValue.where(LEVEL1, "Outer").run(() -> {
System.out.println("Level 1: " + LEVEL1.get()); // Outer
ScopedValue.where(LEVEL1, "Inner").run(() -> {
System.out.println("Level 1: " + LEVEL1.get()); // Inner
ScopedValue.where(LEVEL2, "Deep").run(() -> {
System.out.println("Level 1: " + LEVEL1.get()); // Inner
System.out.println("Level 2: " + LEVEL2.get()); // Deep
});
});
System.out.println("Level 1: " + LEVEL1.get()); // Outer
});
}
}
稳定值 API 引入了保存不可变数据的对象,JVM 将稳定值视为常量,从而实现与声明 final 字段相同的性能优化。相比 final 字段,稳定值在初始化时机上更加灵活。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class StableValueExample {
// 传统 final 字段:必须在构造器或初始化块中初始化
private final String fixedValue = "Fixed";
// 稳定值:可以延迟初始化
private static final StableValue<String> LAZY_VALUE = StableValue.of(() -> {
// 复杂的初始化逻辑
return "Computed: " + System.currentTimeMillis();
});
// 使用 VarHandle 创建稳定值
private static final VarHandle STABLE_HANDLE = MethodHandles.lookup().findVarHandle(StableValueExample.class, "stableField", String.class);
private volatile String stableField;
public void demonstrateStableValue() {
// 获取稳定值
String value = LAZY_VALUE.get();
System.out.println("Stable value: " + value);
// JIT 编译器可以将稳定值优化为常量
for (int i = 0; i < 1000; i++) {
processValue(value); // 可能被内联优化
}
}
private void processValue(String value) {
// 使用稳定值
}
}
应用场景:
向量 API 允许开发者表达向量计算,这些计算在运行时可靠地编译为支持 CPU 上的最优向量指令,从而实现优于等效标量计算的性能。
import jdk.incubator.vector.*;
import java.util.Arrays;
public class VectorAPIExample {
// 传统标量计算
public void scalarMultiply(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = a[i] * b[i];
}
}
// 使用向量 API
public void vectorMultiply(float[] a, float[] b, float[] c) {
int speciesLength = FloatVector.SPECIES_PREFERRED.length();
int i = 0;
// 向量化循环
for (; i < a.length - speciesLength; i += speciesLength) {
var va = FloatVector.fromArray(FloatVector.SPECIES_PREFERRED, a, i);
var vb = FloatVector.fromArray(FloatVector.SPECIES_PREFERRED, b, i);
var vc = va.mul(vb);
vc.intoArray(c, i);
}
// 处理剩余元素
for (; i < a.length; i++) {
c[i] = a[i] * b[i];
}
}
// 更复杂的向量运算
public float[] vectorCompute(float[] data) {
int length = data.length;
float[] result = new float[length];
int speciesLength = FloatVector.SPECIES_PREFERRED.length();
int i = 0;
for (; i < length - speciesLength; i += speciesLength) {
var v = FloatVector.fromArray(FloatVector.SPECIES_PREFERRED, data, i);
// 复杂运算:每个元素平方后加 1
var squared = v.mul(v);
var plusOne = squared.add(1.0f);
// 求和
var sum = plusOne.reduceLanes(VectorOperators.ADD);
// 将结果广播到整个向量
var broadcast = FloatVector.broadcast(FloatVector.SPECIES_PREFERRED, sum);
broadcast.intoArray(result, i);
}
return result;
}
}
性能对比:
| 指标 | 标量计算 | 向量计算 |
|---|---|---|
| CPU 占用 | 100% | 100% SIMD 并行 |
| 耗时 | 基准 | 0.25x-0.5x |
Java 25 引入了密钥派生函数(KDF)API,这些是从密钥和其他数据派生其他密钥的加密算法。
import javax.crypto.*;
import javax.crypto.spec.*;
public class KDFExample {
public void deriveKeys() throws Exception {
// 主密钥
SecretKey masterKey = generateMasterKey();
// 派生多个子密钥
SecretKey encryptionKey = deriveKey(masterKey, "encryption", 256);
SecretKey authKey = deriveKey(masterKey, "authentication", 256);
SecretKey hmacKey = deriveKey(masterKey, "hmac", 512);
System.out.println("Encryption Key: " + encryptionKey.getAlgorithm());
System.out.println("Auth Key: " + authKey.getAlgorithm());
System.out.println("HMAC Key: " + hmacKey.getAlgorithm());
}
private SecretKey deriveKey(SecretKey masterKey, String purpose, int length) throws Exception {
// 使用 HKDF (HMAC-based Key Derivation Function)
KDF kdf = KDF.getInstance("HKDF-SHA256");
// 配置派生参数
KDFParameters params = new KDFParameters.Builder()
.setKey(masterKey)
.setSalt(purpose.getBytes()) // 使用 purpose 作为盐值
.setInfo(purpose.getBytes())
.setLength(length / 8)
.build();
// 派生密钥
return kdf.deriveKey("AES", params);
}
private SecretKey generateMasterKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
return keyGen.generateKey();
}
}
应用场景:
引入了 API,用于将代表加密密钥、证书和证书吊销列表的对象编码为广泛使用的隐私增强邮件(PEM)传输格式,并从该格式解码回对象。
import java.security.cert.*;
import java.security.*;
import java.util.Base64;
public class PEMExample {
public void encodeKeyToPEM() throws Exception {
// 生成密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
// 编码私钥为 PEM 格式
PEMEncoder encoder = new PEMEncoder();
String privateKeyPEM = encoder.encode(keyPair.getPrivate());
System.out.println("Private Key PEM:\n" + privateKeyPEM);
// 编码公钥为 PEM 格式
String publicKeyPEM = encoder.encode(keyPair.getPublic());
System.out.println("Public Key PEM:\n" + publicKeyPEM);
}
public void decodeCertificateFromPEM(String pemCertificate) throws Exception {
PEMDecoder decoder = new PEMDecoder();
// 从 PEM 格式解码证书
Certificate cert = decoder.decodeCertificate(pemCertificate);
System.out.println("Certificate Subject: " + ((X509Certificate) cert).getSubjectDN());
}
public void encodeCertificateToPEM(X509Certificate cert) throws Exception {
PEMEncoder encoder = new PEMEncoder();
String pem = encoder.encode(cert);
System.out.println("Certificate PEM:\n" + pem);
}
}
Java 25 将紧凑对象头从实验特性转为产品特性,显著减少 Java 堆内存占用,并提供潜在的性能提升。
对象头结构对比:
启用紧凑对象头:
# 启用紧凑对象头
java -XX:+UseCompactObjectHeaders MyApp
# 禁用紧凑对象头(默认)
java -XX:-UseCompactObjectHeaders MyApp
内存节省示例:
public class CompactObjectExample {
// 小对象的内存占用显著减少
private static class SmallObject {
int value;
String name;
}
public void memoryComparison() {
// 创建大量小对象
List<SmallObject> objects = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
objects.add(new SmallObject());
}
// 使用紧凑对象头可以节省数 MB 内存
// 对于 1,000,000 个对象,每个节省 4-8 字节
// 总共节省 4-8MB 堆内存
}
}
简化了创建 AOT 缓存所需的命令,这些缓存可以加速 Java 应用的启动。
# 传统方式:复杂的 AOT 编译流程
java -XX:ArchiveClassesAtExit=app.jsa -cp myapp.jar MyApp
java -XX:SharedArchiveFile=app.jsa -cp myapp.jar MyApp
# Java 25 简化方式
java -aot:create -o myapp.aot -cp myapp.jar MyApp
java -aot:use -i myapp.aot -cp myapp.jar MyApp
实际应用:
# 创建 AOT 缓存
java -aot:create -o cache.aot -cp lib/*:myapp.jar com.example.Main
# 使用 AOT 缓存启动
java -aot:use -i cache.aot -cp lib/*:myapp.jar com.example.Main
通过在 HotSpot JVM 启动时立即可用上一次运行的方法执行配置文件,改善预热时间,使 JIT 编译器能够在应用启动时立即生成本地代码。
# 第一次运行:收集性能分析数据
java -XX:ProfilesAtExit=profile.jfr MyApp
# 第二次运行:使用性能分析数据
java -XX:ProfilesDumpFile=profile.jfr MyApp
效果对比:
增强了 JDK Flight Recorder(JFR),在 Linux 上捕获更准确的 CPU 时间性能分析信息。
# 启用 JFR CPU 时间性能分析
java -XX:StartFlightRecording=filename=recording.jfr,duration=60s,cpu-profiling=true MyApp
# 分析记录
jfr print --events cpu,jdk.CPUInformation recording.jfr
改进 JFR 在异步采样 Java 线程堆栈时的稳定性,通过仅在安全点遍历调用堆栈,同时最小化安全点偏差。
import jdk.jfr.*;
@Name("custom.event")
@Label("自定义事件")
public class CustomEvent extends Event {
@Label("消息") String message;
@Label("持续时间") long duration;
}
public class JFRCooperativeSampling {
public void recordEvent() {
CustomEvent event = new CustomEvent();
event.message = "操作完成";
event.duration = System.nanoTime();
event.commit(); // JFR 会协作地采样这个事件
processWork();
}
private void processWork() {
// 复杂的计算逻辑
}
}
通过字节码增强扩展 JFR,提供方法计时和跟踪功能。
# 启用方法跟踪
java -XX:StartFlightRecording=method-profiling=true,stack-depth=16 MyApp
# 分析方法执行时间
jfr print --events jdk.ExecutionSample recording.jfr
自定义方法跟踪:
import jdk.jfr.*;
@Name("method.timing")
@Label("方法计时")
public class MethodTimingEvent extends Event {
@Label("类名") String className;
@Label("方法名") String methodName;
@Label("执行时间 (纳秒)") long duration;
}
public class MethodProfiling {
@StackTrace(false)
public void profileMethod() {
long start = System.nanoTime();
try {
// 方法逻辑
doWork();
} finally {
long duration = System.nanoTime() - start;
MethodTimingEvent event = new MethodTimingEvent();
event.className = this.getClass().getName();
event.methodName = "profileMethod";
event.duration = duration;
event.commit();
}
}
private void doWork() {
// 实际工作
}
}
添加了限制响应体字节数的功能,提高了安全性。
import java.net.http.*;
import java.net.URI;
public class HttpClientEnhancements {
public void fetchWithSizeLimit() throws Exception {
HttpClient client = HttpClient.newHttpClient();
// 创建限制响应体大小的处理器
BodyHandler<String> limitedHandler = BodyHandlers.limiting(BodyHandlers.ofString(), 1024 * 1024); // 限制为 1MB
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.build();
HttpResponse<String> response = client.send(request, limitedHandler);
// 如果响应超过限制,会抛出 IOException
System.out.println("Response body length: " + response.body().length());
}
public void getConnectionInfo() throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request1 = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/endpoint1"))
.build();
HttpRequest request2 = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/endpoint2"))
.build();
HttpResponse<String> response1 = client.send(request1, BodyHandlers.ofString());
HttpResponse<String> response2 = client.send(request2, BodyHandlers.ofString());
// 检查两个请求是否使用相同的连接
Object label1 = response1.connectionLabel();
Object label2 = response2.connectionLabel();
if (label1.equals(label2)) {
System.out.println("两个请求使用相同的连接");
} else {
System.out.println("两个请求使用不同的连接");
}
}
}
ForkJoinPool 现在实现了 ScheduledExecutorService 接口,提高了延迟任务处理的性能。
import java.util.concurrent.*;
public class ForkJoinPoolImprovements {
public void scheduleWithForkJoinPool() {
ForkJoinPool pool = new ForkJoinPool();
// 使用新的调度功能
ScheduledFuture<?> future = pool.schedule(() -> {
System.out.println("延迟任务执行");
}, 5, TimeUnit.SECONDS);
// 使用新的 submitWithTimeout 方法
CompletableFuture<String> result = pool.submitWithTimeout(() -> {
// 长时间运行的任务
Thread.sleep(3000);
return "任务完成";
}, 2, TimeUnit.SECONDS); // 2 秒超时
result.exceptionally(ex -> {
System.out.println("任务超时:" + ex.getMessage());
return "默认值";
});
}
}
G1 垃圾收集器进一步减少了记忆集内存开销和暂停时间,通过允许多个区域在混合 GC 期间共享单个内部结构。
优化方案:
性能提升:
添加了 stdin.encoding 系统属性,用于指定从 System.in 读取字符数据时的推荐字符集。
import java.util.Scanner;
public class EncodingExample {
public void readUserInput() {
// 获取 stdin 编码
String stdinEncoding = System.getProperty("stdin.encoding");
System.out.println("stdin.encoding: " + stdinEncoding);
// 使用推荐的编码读取输入
Scanner scanner = new Scanner(System.in, stdinEncoding);
System.out.print("请输入:");
String input = scanner.nextLine();
System.out.println("输入内容:" + input);
}
}
兼容性检查:
# 检查代码兼容性
javac --release 21 -Xlint:unchecked YourClass.java
主要注意事项:
UseCompressedClassPointers 选项已弃用Java 25 作为一个重要的 LTS 版本,带来了诸多令人兴奋的改进:
语言层面:
库层面:
性能层面:
监控层面:
Java 25 不仅保持了 Java 的稳定性和向后兼容性,还通过这些新特性为开发者提供了更强大的工具,使得 Java 在现代应用开发中继续保持竞争力。无论是初学者还是经验丰富的开发者,都能从这些改进中受益。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online