前言
我是[提前退休的java猿],一名7年java开发经验的开发组长,分享工作中的各种问题!(抖音、公众号同号)
🔈PS: 最近多了一个新的写作方向,就是会把出版社寄来的【java深度调试技术】按照书的骨架提取其中干货进行丰盈写成博客,大家可以关注我的新专栏
java 深度调试技术第六七八章分别的主题是 【常见的java陷阱】、【关于数据库】、【字符集与编码】这几章书中的内容比较少,而且有些JDK的介绍几乎在实际开发中(web)遇不到的,比如常见的java陷阱中介绍了java.swing.Timer. 下面就简单概括一下书中内容以及本人的一些扩展吧。
java深度调试技术 六七八章
常见的java陷阱
书中写的内容比较少,写了java.lang.Runtime、java.util.Timer、java.swing.Timer这几个类在开发当中几乎都不会用到。Runtime 这个API 比较底层的一个API,开发当中最多也是用来获取个CPU核数,TImer 定时器也几乎不会直接用到。
所以这章节对开发来说几乎都没啥用👹可能很多年前会用吧!
对于java陷阱,可以看看我写的【幽灵代码】这篇文文章,目录如下:
- 数据库的时间精度问题
- 异常的处理
- 事务问题
- 数据库操作
- 线程上下文变量在线程池中传递
- 缓存问题
数据库死锁
这一章节写的东西也是非常的少,不如网上的八股文吧!介绍了死锁的基本形成条件,以及如何去避免死锁,如何去查询数据锁的情况。 说实话还还是看我这篇文章吧【为什么很少遇到数据库死锁】,目录如下:
- MySQL数据库的死锁检测机制 和关键配置
- 如何解决死锁问题,是看日志还是查询事务阻塞信息
- 死锁的预防
- MySQL什么场景下数据库会回滚部分SQL
字符集与编码集
这一章节就是讲了一些基础的概念,比如 字符集是字符的集合,编码指的是字符在计算机中是如何存放的是采用单字节还是多字节。Unicode是字符集名称定义了每个字符的编号,Utf-8 则是Unicode 字符集对应的计算机存储格式。
还讲了一编码的转换,算是我之前也没听过的 多字节和宽字节,大概就是写入磁盘转换成多字节(如UTF8 一个字符可能是一个字节或者两个字节表示),读入内存转成宽字节。 今天就主要介绍一下这个知识点吧
扩展阅读:宽字节与多字节
要理解宽字节、多字节,以及文本读写时的编码转换,需先明确字符编码的核心逻辑:计算机仅能存储二进制(0/1),"字符" 需通过编码规则映射为二进制,而 "宽字节""多字节" 是两种不同的字符编码设计思路;文本在 "磁盘 ↔ 内存" 的流转过程中,必然涉及编码 / 解码转换,这也是乱码问题的核心根源。
1. 多字节编码(Multi-Byte Encoding)
定义
用1~n 个字节表示一个字符的编码方式,不同字符占用的字节数不同:
- 单字节字符:如 ASCII 字符(0-127),仅占 1 字节;
- 多字节字符:如中文、日文等,占 2~4 字节(如 GBK 中汉字占 2 字节,UTF-8 中汉字占 3 字节)。
典型代表
- UTF-8(最常用):兼容 ASCII,全球通用的多字节编码;
- GBK/GB2312:中文专用多字节编码,仅支持中日韩字符;
- Shift_JIS:日文专用多字节编码。
核心特点
- 字节数不固定:字符占用 1~4 字节,节省存储空间(ASCII 字符仅 1 字节);
- 有 "变长" 解析规则:需按编码规则逐字节解析(如 UTF-8 靠字节高位标识后续是否有续字节);
- 无字节序问题:单字节 / 多字节按顺序存储,无需区分大端 / 小端。
2. 宽字节编码(Wide Character Encoding)
定义
用固定字节数表示一个字符的编码方式,无论字符类型(ASCII / 中文 / 特殊符号),均占用相同字节数(通常是 2 或 4 字节)。
典型代表
- UTF-16:固定 2 字节(BMP 字符,如常用汉字)或 4 字节(增补字符),本质是 "准固定长度",Java 内存中字符(
char类型)就是 UTF-16 编码的宽字节; - UTF-32:固定 4 字节,每个字符均占 4 字节,完全固定长度;
- UCS-2:UTF-16 的子集,仅支持 BMP 字符,固定 2 字节(已被 UTF-16 取代)。
核心特点
- 字节数固定:解析效率高(无需逐字节判断),但存储空间利用率低(ASCII 字符也占 2/4 字节);
- 有字节序问题:多字节存储时需区分大端(BE)/ 小端(LE),如 UTF-16BE、UTF-16LE;
- 直接映射 Unicode 码点:每个宽字节直接对应一个 Unicode 码点(如 U+4E2D 对应 "中")。
二、文本读写的核心:磁盘 ↔ 内存的编码转换
结论:系统 / 程序读取文本到内存、从内存写入磁盘时,必然发生编码 / 解码转换------ 因为磁盘存储的是 "字节流"(多字节编码,如 UTF-8),而内存中处理的是 "字符"(通常是宽字节编码,如 UTF-16),两者的转换是文本操作的核心步骤。
1. 完整转换流程(以 Java 为例)
plaintext
磁盘文件(UTF-8 字节流)
↓ 解码(Decode):字节 → 字符(宽字节)
内存(Java char/字符串,UTF-16 宽字节)
↓ 编码(Encode):字符 → 字节(多字节)
磁盘文件(UTF-8 字节流)
步骤拆解
(1)读取文本到内存(解码:字节 → 字符)
- 磁盘上的文本文件是字节序列,按指定多字节编码(如 UTF-8)存储;
- 程序读取时,需指定 "解码规则"(如 UTF-8),将可变长度的字节序列转换为内存中固定长度的宽字节字符(如 Java 的 UTF-16
char); - 若解码规则与文件实际编码不一致,会出现乱码(如用 GBK 解析 UTF-8 文件)。
(2)写入文本到磁盘(编码:字符 → 字节)
- 内存中的字符是宽字节编码(如 UTF-16),无法直接存储(存储效率低);
- 程序写入时,需指定 "编码规则"(如 UTF-8),将宽字节字符转换为可变长度的多字节序列;
- 若编码规则与读取时的解码规则不一致,也会出现乱码(如内存字符编码为 UTF-16,写入时用 GBK 编码)。
2. 实操示例:Java 中的编码转换(避免乱码关键)
反例:未指定编码(依赖系统默认编码,易乱码)
java
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BadEncodingDemo {
public static void main(String[] args) throws IOException {
// 读取:FileReader 用系统默认编码(如 Windows 是 GBK,Linux 是 UTF-8)解码
FileReader reader = new FileReader("test.txt"); // 若文件是 UTF-8,Windows 下会乱码
// 写入:FileWriter 用系统默认编码编码,跨平台必乱码
FileWriter writer = new FileWriter("output.txt");
}
}
正例:显式指定编码(统一用 UTF-8)
java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class GoodEncodingDemo {
public static void main(String[] args) throws IOException {
// 读取:字节流 + 显式指定 UTF-8 解码(字节→字符)
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream("test.txt"), StandardCharsets.UTF_8)) {
char[] buffer = new char[1024];
int len = reader.read(buffer); // 解码:UTF-8 字节 → UTF-16 字符
String content = new String(buffer, 0, len);
System.out.println("内存中的字符(UTF-16):" + content);
}
// 写入:字符流 + 显式指定 UTF-8 编码(字符→字节)
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream("output.txt"), StandardCharsets.UTF_8)) {
String content = "你好,UTF-8!";
writer.write(content); // 编码:UTF-16 字符 → UTF-8 字节
}
}
}
3. 关键细节:不同系统 / 语言的内存编码差异
- Java :内存中字符串(
String)和字符(char)均为 UTF-16 宽字节编码,无论输入输出,都需在 "字节↔字符" 转换时指定编码; - C/C++(Windows) :
wchar_t是宽字节(UTF-16,2 字节),char是多字节(系统默认编码,如 GBK/UTF-8); - C/C++(Linux) :
wchar_t是 UTF-32(4 字节),char是多字节(通常 UTF-8); - Python:内存中字符串是 Unicode 码点(逻辑上的宽字节),写入时需编码为 UTF-8/GBK 等多字节。
4. 无转换的特殊场景(极少)
只有当内存编码与磁盘编码完全一致,且程序直接按 "字节流" 读写时,才无编码转换:
java
// 字节流读写:仅复制字节,不做编码转换(适合非文本文件,如图片/二进制)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo {
public static void main(String[] args) throws IOException {
// 读取:直接读字节,无解码(内存中是字节数组,不是字符)
try (FileInputStream fis = new FileInputStream("test.txt")) {
byte[] buffer = new byte[1024];
int len = fis.read(buffer);
// 写入:直接写字节,无编码
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
fos.write(buffer, 0, len);
}
}
}
}
⚠️ 注意:该方式仅适用于 "复制文件",若尝试将字节数组转换为字符串(如 new String(buffer)),仍会触发编码转换(用系统默认编码),极易乱码。