在Windows电脑上,Java字符串的码点会得到一些奇怪的结果

前端开发 2026-07-10
package edu.practice.zapper;

import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

public class Zapper {
    private static final String UNICODE_START = "&#";
    private static final String UNICODE_END = ";";
    private String input;

    public Zapper() {
    }

    public String unicodePrint(String input) {
        if (input != null && !input.isEmpty()) {
            StringBuilder sbTitle = new StringBuilder();
            System.err.println(input);
            input.codePoints().forEach(codepoint->{
                if (codepoint > 127) {
                    sbTitle.append(UNICODE_START);
                    sbTitle.append(codepoint);
                    sbTitle.append(UNICODE_END);
                } else {
                    sbTitle.appendCodePoint(codepoint);
                }
            });
            input = sbTitle.toString();
            System.err.println(input);
        }
        return input;
    }

    public static void main(String[] args) {
        Zapper z = new Zapper();
        z.input = "挑战";
        if(args.length > 0) {
            System.out.println("Restarting:%s :%d :%d".formatted(z.input,z.input.codePointAt(0),z.input.codePointAt(1)));
        }
        String encodeString = Base64.getEncoder().encodeToString(z.input.getBytes());
        byte[] decodeBytes = Base64.getDecoder().decode(encodeString);
        String decodeStringU = new String(decodeBytes,Charset.forName("UTF-8"));
        String decodeString = new String(decodeBytes);
        z.unicodePrint(decodeStringU);
        z.unicodePrint(decodeString);
        if(args.length < 1) {
            restartApplication();
        }
    }
    private static Process restartApplication() {
        Process process = null;
        while (process == null) {
            List<String> command = new ArrayList<>();
            command.add("java");
            command.add("-Ddebugging=false");
            command.add("-DdebugUnit=None");
            command.add("-jar");
            command.add("zapper-1-jar-with-dependencies.jar");
            command.add("restarted");
            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectOutput(Redirect.INHERIT);
            pb.redirectError(Redirect.INHERIT);
            try {
                process = pb.start();
                Thread.sleep(5_000);
            } catch (IOException | InterruptedException e) {
                System.out.println(e.getMessage());
            }
        }
        return process;
    }
}

我搭了一个示例以便更好地进行调查。这大致就是我现在在做的事情,但也可以按实际情况来定。无论如何,原始问题的答案已经很明显。可是,现在重新启动时,所有的??都变成了坏的输出,我也不清楚为什么。似乎应该有一个解码能起作用。

Results:

挑战
&#25361;&#25112;
挑战
&#25361;&#25112;
Restarting:?? :25361 :25112
??
??
??
??

解决方案

那些 ? 是替换字符。当编码时,替换字符用于字符集不支持的字符。当解码时,替换字符用于字符集认为格式错误或无法映射到受支持字符的字节子序列。每种字符集都可以有自己的一组替换字符。

在Java中,CharsetEncoderCharsetDecoder 都可以让你修改替换字符。下表显示了标准字符集的默认替换字符(至少在Java 25中的实现是这样)。

Charset Encoder Replacement Bytes Decoder Replacement Character
US-ASCII 3F (?)
ISO-8859-1 3F (?)
UTF-8 3F (?)
UTF-16 FF FD ()
UTF-32 00 00 FF FD ()

你也可以配置一个 CharsetEncoderCharsetDecoder 来对不可映射或格式错误的输入抛出错误。但大多数,如果不是全部的话,单纯接收一个 Charset 的Java API会把它的编码器/解码器配置为简单地用替换字符来替换不可映射或格式错误的输入。

使用一个能够支持你字符串中可能出现的所有字符的字符集非常重要。也同样重要的是,在解码时使用与编码时相同的字符集,反之亦然。如果你不这样做,你可能看到替换字符,或者在某些情况下,一个字符集把另一个字符集编码的字节序列解读为有效但映射到不同的字符,甚至是乱码。

同样,最好避免使用在Java中会隐式使用JVM默认字符集的方法。改用允许你指定 Charset 的方法。这样你就能保证一致性。這并不一定包括那些有明确且一致默认字符集的方法。例如,大多数现代Java API在不另外指定时会使用UTF-8。即便Java 18之后,JVM的默认字符集也会默认是UTF-8,尽管仍可以通过 file.encoding 系统属性改为使用基于主机平台的字符集。

还需要考虑的另外几件事包括:

  1. 当把字符串发送给另一个应用(本地或远程)时,请确保你的应用和对方应用在某种程度上就字符集达成一致。
  2. 使用 System.outSystem.err 时,请注意它们俩都是一个具有内在字符集的 PrintStream。这个字符集取决于你的环境。若从终端启动,应该与终端配置使用的字符集相同。若通过其他方式启动,如 ProcessBuilder,字符集将是某个平台相关的默认值。

自Java 18起,你可以使用 stdout.encodingstderr.encoding 系统属性来强制指定特定的字符集。

none java -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 ...

或者,你也可以通过编程方式用一个使用不同字符集的 PrintStream 来替换标准输出和错误流。这种做法同样适用于Java 17及更早版本。

```java PrintStream newUtf8PrintStream(FileDescriptor fd) { var fos = new FileOutputStream(fd); return new PrintStream(fos, true, StandardCharsets.UTF_8); }

System.setOut(newUtf8PrintStream(FileDescriptor.out)); System.setErr(newUtf8PrintStream(FileDescriptor.err)); ```

或者你也可以始终自行对一个 String 进行编码,然后通过它的某些 writewriteBytes 方法把 byte[] 写入到 PrintStream

再次强调第一点,请记住,Java会把字符串编码成字节并发送到标准流。读取该流的应用(如终端)必须使用一个适合解码这些字节的字符集。否则即使Java应用在它端一切正确,你仍然可能看到替换字符或乱码。

综上所述,在你的场景中你可能应该在所有地方使用Unicode编码。具体来说,UTF-8更合适,当然根据你的具体需求,UTF-16也可能更合适。既然你的问题似乎是在创建HTML文档,如果你以UTF-8保存并恰当地设置 <meta charset="UTF-8">,你就不需要对Unicode字符进行转义。对于HTTP(S),也请在头部指定UTF-8。

我不太确定你在本例中使用Base64的具体目的,因此不便多言。只是要说,当用Base64编码字符串时,用来把字符串编码成字节的字符集(以及反向)与Base64编码本身是相互独立的。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章