java深度调试技术【第六七八章:宽字节与多字节】

前言

我是[提前退休的java猿],一名7年java开发经验的开发组长,分享工作中的各种问题!(抖音、公众号同号)

🔈PS: 最近多了一个新的写作方向,就是会把出版社寄来的【java深度调试技术】按照书的骨架提取其中干货进行丰盈写成博客,大家可以关注我的新专栏

java 深度调试技术第六七八章分别的主题是 【常见的java陷阱】、【关于数据库】、【字符集与编码】这几章书中的内容比较少,而且有些JDK的介绍几乎在实际开发中(web)遇不到的,比如常见的java陷阱中介绍了java.swing.Timer. 下面就简单概括一下书中内容以及本人的一些扩展吧。

java深度调试技术 六七八章

常见的java陷阱

书中写的内容比较少,写了java.lang.Runtimejava.util.Timerjava.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)),仍会触发编码转换(用系统默认编码),极易乱码。

相关推荐
期待のcode21 分钟前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐24 分钟前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
柳杉26 分钟前
建议收藏 | 2026年AI工具封神榜:从Sora到混元3D,生产力彻底爆发
前端·人工智能·后端
a程序小傲33 分钟前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红34 分钟前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥36 分钟前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v1 小时前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地1 小时前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209251 小时前
Guava Cache 原理与实战
java·后端·spring
yangminlei1 小时前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot