杰瑞科技汇

Java中UTF-8编码中文为何乱码?

Java 内部编码与外部编码

理解这一点是解决所有编码问题的关键。

Java中UTF-8编码中文为何乱码?-图1
(图片来源网络,侵删)
  1. Java 内部编码 (内存中)

    • Java 虚拟机(JVM)内部使用 UTF-16 编码来处理所有字符串(String 对象)。
    • 当你写 String str = "你好"; 时,"你好" 这个字符串在内存中就是以 UTF-16 的形式存储的。
    • 你通常不需要关心这个内部编码,因为 JVM 会帮你处理好,你只需要在数据进入 JVM(读入)和离开 JVM(写出)时,正确指定外部编码即可。
  2. 外部编码 (文件、网络、控制台等)

    • 这是指数据在文件、网络传输、数据库、控制台显示等场景下所使用的编码格式。
    • UTF-8 是目前最主流的外部编码,因为它兼容 ASCII,并且对中文字符比较高效。
    • 如果外部编码指定错误,就会出现乱码。

常见场景下的 UTF-8 中文处理

下面我们通过代码示例来演示在不同场景下如何正确地使用 UTF-8。

控制台输出

问题:在 Windows 的某些旧版 CMD 或 PowerShell 中直接输出中文可能会乱码。

Java中UTF-8编码中文为何乱码?-图2
(图片来源网络,侵删)

解决方案:确保你的控制台程序使用的编码是 UTF-8。

public class ConsoleOutput {
    public static void main(String[] args) {
        // 方法一:强制设置 System.out 的编码为 UTF-8(推荐)
        // 这行代码应该尽可能早地执行,比如在 main 方法的开头
        System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
        String chineseStr = "你好,世界!Hello, World!";
        System.out.println(chineseStr);
        // 方法二:对于 System.err (错误输出) 同理
        System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
        System.err.println("这是一条错误信息:" + chineseStr);
    }
}

注意:你的 IDE(如 IntelliJ IDEA, Eclipse)和终端(如 Windows Terminal, iTerm2)本身也需要配置为支持 UTF-8 才能正确显示。

文件读写

这是最经典的场景,使用 java.nio.file 包中的 Files 类是现代 Java 中处理文件的首选方式,因为它更简洁且不易出错。

写入文件

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
public class FileWrite {
    public static void main(String[] args) {
        String content = "这是第一行,\n这是第二行,包含中文,This is the second line.";
        Path filePath = Paths.get("output.txt");
        try {
            // 1. 将字符串按行分割成 List
            List<String> lines = Arrays.asList(content.split("\n"));
            // 2. 使用 Files.write 写入文件,并明确指定 StandardCharsets.UTF_8
            // StandardCharsets.UTF_8 是一个预定义的常量,比 "UTF-8" 字符串更安全高效
            Files.write(filePath, lines, StandardCharsets.UTF_8);
            System.out.println("文件 'output.txt' 写入成功!");
        } catch (IOException e) {
            System.err.println("写入文件时出错: " + e.getMessage());
        }
    }
}

读取文件

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class FileRead {
    public static void main(String[] args) {
        Path filePath = Paths.get("output.txt");
        try {
            // 读取所有行到 List 中,并指定 UTF-8 编码
            List<String> lines = Files.readAllLines(filePath, StandardCharsets.UTF_8);
            // 打印读取的内容
            System.out.println("文件内容:");
            for (String line : lines) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("读取文件时出错: " + e.getMessage());
        }
    }
}

网络请求 (以 HttpClient 为例)

在 Web 开发中,处理 HTTP 请求和响应的编码至关重要。

发送 POST 请求(带 JSON 中文)

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
public class HttpClientExample {
    public static void main(String[] args) {
        String jsonPayload = "{\"name\": \"张三\", \"message\": \"你好,Java!\"}";
        // 创建 HttpClient
        HttpClient client = HttpClient.newHttpClient();
        // 创建 HttpRequest,并设置 BodyPublisher
        // 注意:这里我们直接传入字符串,HttpClient 会帮我们处理编码
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com/api"))
                .header("Content-Type", "application/json; charset=UTF-8") // 明确告诉服务器我发的是 UTF-8
                .POST(BodyPublishers.ofString(jsonPayload))
                .build();
        try {
            // 发送请求并获取响应
            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
            // 检查响应状态码
            if (response.statusCode() == 200) {
                // 假设服务器返回的也是 UTF-8 编码的 JSON
                String responseBody = response.body();
                System.out.println("服务器响应: " + responseBody);
            } else {
                System.err.println("请求失败,状态码: " + response.statusCode());
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

关键点

  • Content-Type 头部加上 ; charset=UTF-8 是一个好习惯,可以明确告知服务器请求体的编码。
  • HttpClient 在处理 String 类型的请求体和响应体时,默认会使用 UTF-8 编码,但显式声明总是更安全的。

数据库连接 (JDBC)

乱码问题经常出现在 JDBC 的 URL 连接字符串中。

错误示例(可能导致乱码)jdbc:mysql://localhost:3306/mydb?user=root&password=123456

正确示例(明确指定字符集)jdbc:mysql://localhost:3306/mydb?user=root&password=123456&useUnicode=true&characterEncoding=UTF-8

  • useUnicode=true: 启用 Unicode 字符集。
  • characterEncoding=UTF-8: 指定连接使用的字符集为 UTF-8,这确保了从 Java 应用程序发送到 SQL 语句的字符串,以及从数据库读取到 Java 应用程序的字符串,都经过了正确的编码转换。

代码示例

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JdbcExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8";
        String user = "root";
        String password = "your_password";
        try (Connection conn = DriverManager.getConnection(url, user, password);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT name, message FROM test_table")) {
            while (rs.next()) {
                // 从数据库读取数据,JDBC 会自动将 UTF-8 转换为 Java 内部的 UTF-16
                String name = rs.getString("name");
                String message = rs.getString("message");
                System.out.println("Name: " + name + ", Message: " + message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结与最佳实践

  1. 明确内外之分:JVM 内部是 UTF-16,所有与外部世界的交互(文件、网络、数据库)都要显式指定编码为 UTF-8。
  2. 优先使用 java.nio.file:进行文件 I/O 时,Files.readAllLines()Files.write() 配合 StandardCharsets.UTF_8 是最简单、最不容易出错的方式。
  3. 不要用 byte[] 手动转换:除非有特殊需求,否则不要自己用 String.getBytes("ISO-8859-1") 这种方式去转换,让 Java 库(如 HttpClient, JDBC, Files)为你处理。
  4. 在关键地方声明编码
    • 文件读写时指定 StandardCharsets.UTF_8
    • HTTP 请求/响应时,在 Content-Type 头部加上 ; charset=UTF-8
    • JDBC 连接 URL 中加上 useUnicode=true&characterEncoding=UTF-8
  5. IDE 和环境的配置:确保你的开发环境(IDE、终端、操作系统)也配置为使用 UTF-8,否则你看到的可能就是乱码,但这不是 Java 代码的问题。

遵循以上原则,你就能在 99% 的情况下避免中文乱码问题。

分享:
扫描分享到社交APP
上一篇
下一篇