0%

能在 Switch 中使用 String 吗?且分析原理

是的,从 Java 7 开始,switch 语句可以使用 String 类型。在使用 String 作为 switch 的表达式时,Java 会根据每个 case 中的字符串计算其哈希值来做判断。

原理详细分析

在 Java 中,switch 语句从 Java 7 开始支持 String 类型。其工作原理与使用整数类型的 switch 语句不同,因为字符串是对象,字符串比较涉及到内存地址和内容的比较,不能直接作为枚举的比较项。因此,Java 在编译期间会对 Stringswitch 做一系列的转换和优化。以下是更详细的分解:

1. 编译器的转换过程

switch 使用 String 作为输入时,编译器会将它转换为使用 hashCode 方法和 equals 方法的组合。具体步骤如下:

  • 第一步:Java 编译器将 switch 语句的 String 表达式转换为 String.hashCode() 值进行处理。hashCode 返回的是一个 int 值,因此转换后的 switch 语句类似于 int 类型的 switch 语句。
  • 第二步:编译器为 switch 语句生成一个“跳转表”(即使用 hashCode 匹配的映射表),将每个 case 标签的 String 表达式计算出 hashCode 并进行相应的映射。这一表格帮助在执行时快速找到匹配项。

2. 运行时工作机制

当代码执行到 switch 语句时,会先对传入的 String 调用 hashCode 方法并将其与 case 语句的 hashCode 逐一对比:

  • 如果 hashCode 匹配,进入下一步的字符串验证,比较输入字符串和 case 中的字符串是否相同(使用 equals 方法)。
  • 如果 hashCode 匹配,但 equals 不匹配,说明产生了 哈希冲突,跳过此 case,继续寻找下一个匹配的 case 标签。
  • 如果没有任何匹配项,则执行 default 代码块(如果有 default)。

3. 示例及工作原理

以如下代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SwitchWithStringExample {
public static void main(String[] args) {
String day = "MONDAY";

switch (day) {
case "MONDAY":
System.out.println("Start of the work week!");
break;
case "FRIDAY":
System.out.println("End of the work week!");
break;
default:
System.out.println("Midweek day");
break;
}
}
}

编译后(伪代码):

编译后的代码大致等效于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int hash = day.hashCode();
switch (hash) {
case 744853702: // hash code for "MONDAY"
if (day.equals("MONDAY")) {
System.out.println("Start of the work week!");
break;
}
case -1402213467: // hash code for "FRIDAY"
if (day.equals("FRIDAY")) {
System.out.println("End of the work week!");
break;
}
default:
System.out.println("Midweek day");
break;
}

解释

  • day.hashCode() 计算的整数值会用于 switch 语句的分支选择。
  • 在每个 case 中,首先通过 hashCode 值判断是否有可能匹配。如果 hashCode 相同(744853702 对应 “MONDAY”,-1402213467 对应 “FRIDAY”),进一步使用 equals 比较字符串内容,以确认 day 确实为该 case 语句指定的字符串。
  • default 匹配 hashCodeequals 都未匹配的情况,表示该 switch 中无对应的 case 分支。

4. 优势与限制

  • 性能优化:相比一组连续的 if-else 字符串对比,switch 通过先 hashCode 匹配再 equals 校验的方式提高了速度,减少了直接调用 equals 的次数。
  • 哈希冲突:由于 hashCode 是一种散列算法,不同字符串可能具有相同的 hashCode。在这种情况下,编译后的代码会在 hashCode 相等后再调用 equals,确保得到的结果准确无误。
  • null 值限制switch 不允许 null 作为输入值,传入 null 会导致 NullPointerException。这是因为 null 没有 hashCode 值,无法进行哈希比较。

5. 总结

  • switch 使用 String 时,编译器会先通过 hashCode 匹配,再用 equals 方法校验。
  • String switch 适合用在较小的字符串集内,以减少哈希冲突。
  • 相比 if-else 的优势:这种处理方式减少了重复字符串比较(直接调用 equals),提升了性能。

二分查找通常将待查找区间分为两部分并只取一部分继续查找,可以大大减少查找的复杂度。

求开方

  1. Sqrt(x) (Easy)

题目描述

  给定一个非负整数,求它的开方,向下取整。

输入输出样例

  输入是一个整数,输出一个整数。

1
2
Input: 8
Output: 2

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int mySqrt(int a) {
if (a == 0) return 0;
int l = 1, r = a, mid, sqrt;
while (l <= r) {
mid = l + (r - 1) / 2;
sqrt = a / mid;
if (sqrt == mid) {
return mid;
} else if (mid > sqrt) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return r;
}

java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//注意int强转long的用法
class Solution {
public int mySqrt(int x) {
int l = 0;
int r = x;
int ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if ((long)mid * mid <= x) {
l = mid + 1;
ans = mid;
} else {
r = mid - 1;
}
}
return ans;
}
}

查找区间

  1. Find First and Last Position of Element in Sorted Array (Medium)

题目描述

  给定一个增序的整数数组和一个值,查找该值第一次和最后一次出现的位置。

输入输出样例

  输入是一个数组和一个值,输出为该值第一次出现的位置和最后一次出现的位置(从0开始);如果不存在该值,则两个返回值都设为-1。

1
2
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3, 4]

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.empty()) return vector<int>{-1, -1};
int lower = lower_bound(nums, target);
int upper = upper_bound(nums, target) - 1;
if (lower == nums.size() ## nums[lower] != target) {
retrun vector<int>{-1, -1};
}
}

int lower_bound(vector<int>& nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
mid = (l + r) / 2;
if (nums[mid] >= target) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}

int upper_bound(vector<int>& nums, int target) {
int l = 0, r = nums.size(), mid;
while (l < r) {
mid = (l + r) / 2;
if (nums[mid] > target) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}

java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//拆解为两个二分查找,注意收敛边界
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = binarySearch(nums, target, true);
int right = binarySearch(nums, target, false);
return new int[]{left, right};
}

public int binarySearch(int[] nums, int target, Boolean leftFlag) {
int result = -1;
if (nums.length <= 0) {
return result;
}
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target && leftFlag && (0 == mid || nums[mid - 1] < nums[mid])) {
return mid;
} else if (nums[mid] == target && !leftFlag && (mid == nums.length - 1 || nums[mid + 1] > nums[mid])) {
return mid;
} else if ((leftFlag && nums[mid] >= target) || (!leftFlag && nums[mid] > target)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
}

旋转数组查找数字

  1. Search in Rotated Sorted Array (Medium)

题目描述

  一个原本增序的数组被首尾相连后按某个位置断开(如[1,2,2,3,4,5]->[2,3,4,5,1,2],在第一位和第二位断开),我们称其为旋转数组。给定一个值,判断这个值是否存在于这个为旋转数组中。

输入输出样例

  输入是一个数组和一个值,输出是一个布尔值,表示数组中是否存在于这个旋转数组中。

1
2
Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bool search(vector<int>& nums, int target) {
int start = 0, end = nums.size() - 1;
while (start < end) {
mid = (start + end) / 2;
if (nums[mid] == target) {
return true;
}
if (nums[start] == nums[mid]) {
++start;
} else if (nums[mid] <= nums[end]) {
if (target > nums[mid] && target <= nums[end]) {
start = mid + 1;
} else {
end = mid - 1;
}
} else {
if (target >= nums[start] && target < nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
}
}
return flase;
}

java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//注意区分有序空间
class Solution {
public boolean search(int[] nums, int target) {
// nums = [2,5,6,0,0,1,2]
int left = 0;
int right = nums.length - 1;
boolean res = false;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
return true;
} else if (nums[left] < nums[mid]) { //在前面升区间上
if (nums[mid] < target) {
left = mid + 1;//一定对的,因为它右边的大
} else if (nums[left] > target) {//左边的最小值的大于它,可能在右边
left = mid + 1;
} else {//否则一定在左边
right = mid - 1;
}
} else if (nums[left] > nums[mid]) {
// nums = [2,5,6,0,0,1,2]
if (nums[mid] > target) { //在后面升区间上
right = mid - 1;//一定对的,小的在左边
} else if (nums[right] < target) {
right = mid - 1;//最右边的最大值还没达到最大值,在左边
} else {
left = mid + 1;
}
} else {
left++;
}
}
return res;
}
}

要查看 Docker 容器中的存储卷或挂载点是如何映射到主机的,可以通过以下步骤来检查:

1. 使用 docker inspect 命令

docker inspect 命令可以查看容器的详细配置信息,其中包括挂载的卷(volumes)和绑定挂载(bind mounts)。

具体命令如下:

1
docker inspect <container_name_or_id>

这个命令将返回一个包含容器所有信息的 JSON 文件。你可以从其中找到有关存储挂载的部分。

2. 查找卷和绑定挂载信息

docker inspect 返回的结果中,查找以下字段:

  • "Mounts": 列出所有卷和绑定挂载信息。

示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"Mounts": [
{
"Type": "bind",
"Source": "/host/path",
"Destination": "/container/path",
"Mode": "rw",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "my_volume",
"Source": "/var/lib/docker/volumes/my_volume/_data",
"Destination": "/container/volume",
"Driver": "local",
"Mode": "rw",
"RW": true,
"Propagation": ""
}
]

在这个例子中:

  • 绑定挂载(bind mount):
    • 容器内部的 /container/path 挂载到了主机的 /host/path
  • 卷(volume):
    • 容器内部的 /container/volume 使用了 Docker 卷 my_volume,存储在主机的 /var/lib/docker/volumes/my_volume/_data 目录下。

3. 过滤查看挂载点

如果你只想查看挂载点信息,可以使用 docker inspect--format 选项来过滤信息。比如只显示挂载点信息:

1
docker inspect --format='{{json .Mounts}}' <container_name_or_id>

这样可以直接输出容器的挂载信息,简化结果。

总结

通过 docker inspect 命令,你可以清楚地看到容器中的存储卷或者挂载的目录对应主机上的哪个路径。

第一章 序章

关于LeetCode

LeetCode主要是用在面试中,在有限的时间内考验小镇做题家的算法能力

关于作者

万和晧是读研时的舍友,互相监督刷题卷一下嘿嘿

编程语言

万擅长用java, 晧用c++各写一遍

根据时间复杂度的不同,大致分为3类:

  1. 时间复杂度O(n^2)的排序算法: 冒泡,选择,插入,希尔排序
  2. 时间复杂度O(nlogn)的排序算法: 快速,归并,堆排序
  3. 时间复杂度为线性的排序算法: 计数,桶排序,基数排序

快速排序(Quicksort)

c++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void quicksort(vector<int> &nums, int l ,int r) {
if (l + 1 >= r) {
return;
}
int first = l, last = r - 1, key = nums[first];
while (fisrt < last) {
while (fist < last && nums[last] >= key) {
--last;
}
nums[first] = nums[last];
while (fist < last && nums[first] <= key) {
++first;
}
nums[last] = nums[first];
}
nums[first] = key;
quicksort(nums, l, first);
quicksort(nums, first + 1, r);
}

java:

1
@houwanwan

为什么 3 * 0.1 == 0.3 在 Java 中会返回 false?(详细原理)

要详细解释 3 * 0.1 == 0.3 返回 false 的原因,涉及到计算机如何存储浮点数以及它们在底层的表示原理。让我们逐步解释这一点。

1. 浮点数的表示

计算机使用 IEEE 754 标准来存储浮点数。这个标准将浮点数分为三部分:

  • 符号位:决定数值是正数还是负数。
  • 指数位:用来表示数值的规模,即类似于科学计数法中的指数部分。
  • 尾数位(或称为有效数字部分):表示数值的精度。

2. 为什么 0.1 无法精确表示?

计算机使用二进制来表示数字,而某些十进制的数(例如 0.1)在二进制系统中是无法精确表示的,就像某些分数在十进制系统中是无限循环小数一样(例如,1/3 = 0.333…)。

二进制中的 0.1:

0.1 的二进制表示是一个无限循环的小数:

1
0.1 十进制 = 0.000110011001100110011001100110011...(二进制)

由于浮点数只能存储有限的位数(双精度浮点数一般是 53 位有效位),计算机会对这个值进行截断或四舍五入。因此,0.1 并不是精确存储的,而是一个近似值

类似地,0.3 也不能被精确表示为二进制浮点数。

3. 乘法操作的误差

当我们执行 3 * 0.1 时,计算机内部实际计算的是 3 * 0.000110011001100110011001... 这个近似的二进制值。由于浮点数表示的有限精度,这个结果不会精确等于 0.3,而是一个非常接近 0.3 的值。

具体的结果可能类似于:

1
3 * 0.1 ≈ 0.30000000000000004

因此,3 * 0.1 == 0.3 进行比较时,计算机发现两者在底层二进制的表示并不完全相同,最终导致返回 false

4. 精度损失的示例

我们可以通过输出 3 * 0.1 来查看实际结果:

1
2
3
4
5
6
public class FloatTest {
public static void main(String[] args) {
double result = 3 * 0.1;
System.out.println(result); // 输出:0.30000000000000004
}
}

5. 为什么会有这种误差?

误差的根源在于浮点数的有限位数表示。浮点数的尾数部分只能表示有限的位数,而像 0.1 这样的小数在二进制系统中需要无限位数才能精确表示。因为尾数的限制,浮点数只能存储某个数字的近似值,从而导致误差在数学运算(如乘法)中进一步累积。

6. 如何处理这种误差?

由于浮点数无法精确表示某些小数,直接比较浮点数的相等性可能导致不准确的结果。在实际编程中,我们可以使用以下方法来避免这种问题:

1. 使用误差范围(容差)

为了比较两个浮点数是否“近似相等”,我们可以设置一个容差值(例如 0.000001),只要两个浮点数的差值小于这个容差,就认为它们是相等的。

1
2
3
4
5
6
7
8
9
10
public class FloatTest {
public static void main(String[] args) {
double result = 3 * 0.1;
if (Math.abs(result - 0.3) < 0.000001) {
System.out.println("True");
} else {
System.out.println("False");
}
}
}

2. 使用 BigDecimal 进行精确计算

BigDecimal 提供了高精度的数值运算,并可以避免由于浮点数精度导致的问题。对于涉及到财务计算等需要高精度的场景,BigDecimal 是更好的选择。

1
2
3
4
5
6
7
8
9
import java.math.BigDecimal;

public class BigDecimalTest {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = a.multiply(new BigDecimal("3"));
System.out.println(b.equals(new BigDecimal("0.3"))); // 输出:true
}
}

注意,这里使用了字符串作为 BigDecimal 的输入,避免了浮点数近似表示的问题。

7. 总结

  • 浮点数表示的精度问题:0.1 和 0.3 不能精确表示为二进制浮点数,计算时产生误差。
  • 误差来源:有限的位数导致在表示和运算过程中累积误差。
  • 解决方案:通过误差范围比较,或者使用 BigDecimal 进行精确计算。

这种现象不仅仅是 Java 中存在,在其他使用 IEEE 754 标准的编程语言中(如 Python、C++)也会遇到同样的问题。

XA协议详细介绍一下

XA协议是由X/Open组织定义的一种分布式事务处理协议,旨在协调多个资源管理器(如数据库)之间的分布式事务,使得跨多个系统的事务能够遵循ACID(原子性、一致性、隔离性、持久性)原则。XA协议的核心机制是两阶段提交(Two-Phase Commit, 2PC),它通过协调器来管理事务在多个资源之间的提交与回滚。

XA协议的核心概念

  1. 事务管理器(Transaction Manager, TM):负责管理整个事务的生命周期。它协调各个资源管理器的事务状态,并决定是否提交或回滚事务。
  2. 资源管理器(Resource Manager, RM):资源管理器通常是数据库、消息队列或其他可以进行事务操作的系统。RM负责管理资源的本地事务,并与事务管理器进行通信。
  3. 应用程序(Application, AP):发起事务的主体,它通过事务管理器来控制整个分布式事务的开始、提交和回滚操作。

XA协议的工作流程

XA协议基于两阶段提交协议(2PC)来实现事务的一致性。它将事务的提交过程分为两个阶段:准备阶段和提交阶段。

1. 第一阶段:Prepare(准备阶段)

  • 当应用程序请求提交事务时,事务管理器会向每个资源管理器发送prepare请求。
  • 每个资源管理器在接到请求后,会尝试执行事务操作,并在本地记录事务日志,确保事务的可提交性。
  • 如果资源管理器能够成功执行,则返回prepared状态;如果某个资源管理器发现问题,返回rollback状态。

2. 第二阶段:Commit/Rollback(提交/回滚阶段)

  • 如果所有资源管理器都返回prepared状态,事务管理器会向所有资源管理器发送commit命令,要求它们真正提交事务。
  • 如果有任何一个资源管理器返回失败,事务管理器会发送rollback命令,通知所有资源管理器回滚事务。
  • 资源管理器在收到commitrollback命令后,执行相应操作并返回最终状态。

XA协议的优点

  1. 一致性保证:通过两阶段提交,XA协议确保了所有参与的资源管理器要么全部提交,要么全部回滚,保证了分布式系统中事务的一致性。
  2. 标准化:XA是一个标准协议,很多数据库(如MySQL、PostgreSQL、Oracle)都原生支持XA事务,开发人员可以在多种系统间使用同样的接口进行事务处理。

XA协议的缺点

  1. 性能开销:由于两阶段提交涉及多次网络通信和磁盘操作,XA协议可能引入较高的性能开销,尤其是在大规模分布式系统中。
  2. 阻塞问题:在第二阶段,资源管理器会一直等待事务管理器的指令(提交或回滚),如果事务管理器长时间没有响应,资源管理器的事务资源可能会被锁定,导致阻塞。
  3. 单点故障:事务管理器在整个协议中扮演了核心协调角色,如果事务管理器出现故障,可能会导致分布式事务无法继续进行。

典型应用场景

  1. 跨数据库事务:当一个应用程序需要在多个数据库中同时执行事务时,可以使用XA协议确保事务的一致性。
  2. 消息队列与数据库集成:例如在金融交易系统中,消息的写入与数据库更新必须同步进行,否则可能会导致数据不一致。

常见的XA支持

许多数据库和中间件都支持XA协议:

  • 关系型数据库:Oracle、MySQL、PostgreSQL、DB2等。
  • 消息中间件:ActiveMQ、RabbitMQ等。
  • 应用服务器:Tomcat、JBoss、WebLogic等支持JTA(Java Transaction API)规范的服务器。

总结

XA协议通过两阶段提交确保了分布式事务的强一致性,但其性能开销和可能的阻塞问题使得它在某些场景下并不理想。现代分布式系统中,XA事务通常会与其他分布式一致性方案(如TCC、SAGA、最终一致性等)结合使用,以实现更好的性能与可用性。

分布式事务框架有哪些

分布式事务框架是用于管理分布式系统中跨多个数据库或服务的事务一致性的工具。以下是一些常见的分布式事务框架:

  1. XA协议:XA协议是一种标准的分布式事务协议,通过两阶段提交(Two-Phase Commit)来实现事务的一致性,常用于关系型数据库的分布式事务管理。
  2. Spring Cloud 分布式事务:Spring Cloud提供了多种分布式事务解决方案,如Spring Cloud Netflix中的Hystrix、Feign等,以及Spring Cloud Alibaba中的Seata等。
  3. Atomikos:Atomikos是一个Java事务管理器,支持JTA(Java Transaction API)标准,用于管理分布式事务的提交和回滚。
  4. Bitronix:Bitronix也是一个流行的Java事务管理器,支持JTA,并提供了用于Web容器和应用服务器的集成。
  5. Narayana(前身为JBoss Transactions):Narayana是一个开源的Java事务管理器,提供了广泛的分布式事务支持,包括XA事务和高可用性配置。
  6. Seata:Seata是阿里巴巴开源的一款分布式事务解决方案,支持AT(原子操作)、TCC(Try-Confirm-Cancel)和XA三种事务模式,适用于云原生场景。
  7. TCC-Transaction:TCC(Try-Confirm-Cancel)是一种逻辑事务模式,通过分阶段执行来保证分布式系统的事务一致性,一般在微服务架构中使用较多。

这些框架和协议各有特点,选择时需要考虑系统架构、性能需求、支持的技术栈以及对事务一致性的具体要求。

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

1
2
3
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

1
2
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

1
2
输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

思路

简单直观的思路是,两轮循环,暴力枚举所有可能的组合,相加判断是否等于目标值,但是题目要求时间复杂度小于 O(n2),因此这种 方法不行。换一种思路一次遍历值为x,如何在o(1)时间复杂度寻找target-x,使用哈希表可以做到,使用空间换时间,时间复杂度为O(n),空间复杂度也为O(n)。

解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; i++) {
if (!hashMap.containsKey(target - nums[i])) {
hashMap.put(nums[i], i);
} else {
return new int[]{hashMap.get(target - nums[i]), i};
}
}
return new int[]{};
}
}

java中将json字符串转化成对象

在 Java 中将 JSON 转化为对象,通常使用 JSON 解析库来实现。最常用的 JSON 解析库是 Jackson 和 Gson。下面介绍如何使用 Jackson 和 Gson 将 JSON 字符串转换为 Java 对象。

1. 使用 Jackson 将 JSON 转化为对象

Jackson 是一个功能强大的 JSON 处理库。它的 ObjectMapper 类可以轻松地将 JSON 转化为 Java 对象。

步骤:

  1. 添加 Jackson 依赖(Maven 项目):
1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
  1. 定义 Java 类(假设 JSON 表示一个 Person 对象):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
private String name;
private int age;

// Getters and Setters
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
  1. 使用 Jackson 的 ObjectMapper 将 JSON 字符串转换为 Java 对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
public static void main(String[] args) {
String json = "{\"name\":\"John\", \"age\":30}";

ObjectMapper objectMapper = new ObjectMapper();
try {
Person person = objectMapper.readValue(json, Person.class);
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
} catch (Exception e) {
e.printStackTrace();
}
}
}

输出:

1
2
Name: John
Age: 30

2. 使用 Gson 将 JSON 转化为对象

Gson 是由 Google 提供的轻量级 JSON 处理库,它可以很方便地将 JSON 转化为 Java 对象。

步骤:

  1. 添加 Gson 依赖(Maven 项目):
1
2
3
4
5
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
  1. 定义 Java 类(与 Jackson 示例中的 Person 类相同)。
  2. 使用 Gson 将 JSON 字符串转换为 Java 对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
import com.google.gson.Gson;

public class Main {
public static void main(String[] args) {
String json = "{\"name\":\"John\", \"age\":30}";

Gson gson = new Gson();
Person person = gson.fromJson(json, Person.class);

System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
}
}

输出:

1
2
Name: John
Age: 30

总结:

  • Jackson:功能强大、广泛使用,支持更多的高级功能(如流处理、注解等)。
  • Gson:轻量级,简单易用,适合基础的 JSON 解析。

两者都可以轻松将 JSON 转换为 Java 对象,具体使用哪一个可以根据项目需求来选择。

3. 使用 fastjson将 JSON 转化为对象

fastjson 是阿里巴巴开发的一款高效 JSON 解析库,它比 Jackson 和 Gson 更加轻量级和高效。使用 fastjson 可以快速将 JSON 字符串转换为 Java 对象。

  1. 使用 fastjson 将 JSON 转化为对象

步骤:

  1. 添加 fastjson 依赖(Maven 项目):
1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
  1. 定义 Java 类(与之前示例中的 Person 类相同):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
private String name;
private int age;

// Getters and Setters
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
  1. 使用 fastjson 将 JSON 字符串转换为 Java 对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
import com.alibaba.fastjson.JSON;

public class Main {
public static void main(String[] args) {
String json = "{\"name\":\"John\", \"age\":30}";

// 使用 Fastjson 的 JSON 类将 JSON 字符串转换为 Java 对象
Person person = JSON.parseObject(json, Person.class);

System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
}
}

输出:

1
2
Name: John
Age: 30
  1. 使用 fastjson 将对象转换为 JSON 字符串

Fastjson 还支持将 Java 对象转换为 JSON 字符串,类似于其他 JSON 解析库的 toJson() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.alibaba.fastjson.JSON;

public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("John");
person.setAge(30);

// 将对象转换为 JSON 字符串
String jsonString = JSON.toJSONString(person);

System.out.println("JSON String: " + jsonString);
}
}

输出:

1
JSON String: {"age":30,"name":"John"}

总结:

  • Fastjson 提供了高效的 JSON 解析能力,并且使用方式简洁易懂。
  • 和 Jackson、Gson 一样,Fastjson 也可以快速将 JSON 字符串转换为 Java 对象,并且能将对象转换回 JSON 字符串。