微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Windows 上的字符编码混乱

如何解决Windows 上的字符编码混乱

我有一个简单的 Java 程序,它接受十六进制并将其转换为 ASCII。 使用 Java 8,我编译了以下内容

import java.nio.charset.Charset;
import java.util.Scanner;

public class Main 
{
    public static void main(String[] args) 
    {
        System.out.println("Charset: " + Charset.defaultCharset());
        Scanner in = new Scanner(system.in);
        System.out.print("Type a HEX string: ");
        String s = in.nextLine();
        String asciiStr = new String();
        
        //  Split the string into an array
        String[] hexes = s.split(":");
        
        //  For each hex
        for (String hex : hexes) {
            //  Translate the hex to ASCII
            System.out.print(" " + Integer.parseInt(hex,16) + "|" + (char)Integer.parseInt(hex,16));
            asciiStr += ((char) Integer.parseInt(hex,16));
        }
        
        System.out.println("\nthe ASCII string is " + asciiStr);
        
        in.close();
    }
}

我正在将 C0:A8:96:FE 的十六进制字符串传递给程序。我主要关心的是 0x96 值,因为它被定义为控制字符(128 - 159 范围内的字符)。

当我在没有任何 JVM 标志的情况下运行程序时的输出如下:

Charset: windows-1252
Type a HEX string: C0:A8:96:FE
 192|À 168|¨ 150|? 254|þ
the ASCII string is À¨?þ

当我使用 JVM 标志 -Dfile.encoding=ISO-8859-1 设置字符编码时的输出如下:

Charset: ISO-8859-1
Type a HEX string: C0:A8:96:FE
 192|À 168|¨ 150|– 254|þ
the ASCII string is À¨–þ

我想知道为什么当字符编码设置为 ISO-8859-1 时,字符 128 - 159 会得到额外的 Windows-1252 字符?这些字符不应在 ISO-8859-1 中定义,而应在 Windows-1252 中定义,但在这里似乎是倒退的。在 ISO-8859-1 中,我认为 0x96 字符应该被编码为空白字符,但事实并非如此。相反,Windows-1252 编码会这样做,当它应该将其正确编码为 时。有什么帮助吗?

解决方法

tl;博士

我的猜测:虽然您的 JVM 的默认 Charset 可能是“windows-1252”,但您的 System.out 实际上使用的是 Unicode。

你说:

当我使用JVM标志-Dfile.encoding=ISO-8859-1来设置字符编码时

我下面的实验让我怀疑你所做的一切没有实际上影响了 System.out 使用的字符集。我相信在您的两次运行中,当您认为 System.out 使用“windows-1252”或“ISO-8859-1”时,您的 System.out 实际上使用的是 Unicode,可能是 UTF-8。

我希望我知道how to get the Charset of System.out

这种行为将来可能会发生变化,跨平台的 proposal (JEP 400) to use UTF-8 by default

详情

实际上,您是在询问 Unicode 而不是 ASCII。 ASCII 只有 128 个字符。

你说:

我主要关心的是 0x96 值,因为它被定义为控制字符(128 - 159 范围内的字符)。

实际上,该范围的控制字符在 Unicode(和 ASCII)中从 127 开始,而不是 128。代码点 127 是 DELETE character。所以127-159是控制字符。

首先,让我们拆分输入的十六进制代码字符串。

        final List < String > hexInputs = List.of( "C0:A8:96:FE".split( ":" ) );
        System.out.println( "hexInputs = " + hexInputs );

运行时。

hexInputs = [C0,A8,96,FE]

现在将每个十六进制文本转换为十六进制整数。我们将该整数用作 Unicode code point

与其依赖某些默认字符编码,不如明确设置我们的 CharsetSystem.out。我不是这方面的专家,但一些网络搜索发现了下面的代码,我们将 System.out 包装在一个新的 PrintStream 中,同时按其名称设置 Charset。我找不到获取Charset的{​​{1}}的方法,所以I asked

UTF-8

PrintStream

运行时。

        // UTF-8
        System.out.println( "----------|  UTF-8  |--------------------------" );
        try
        {
            PrintStream printStream = new PrintStream( System.out,true,StandardCharsets.UTF_8.name() ); // "UTF-8".

            for ( String hexInput : hexInputs )
            {
                int codePoint = Integer.parseInt( hexInput,16 );
                String string = Character.toString( codePoint );
                printStream.println( "hexInput: " + hexInput + " = codePoint: " + codePoint + " = string: [" + string + "] = isLetter: " + Character.isLetter( codePoint ) + " = name: " + Character.getName( codePoint ) );
            }
        }
        catch ( UnsupportedEncodingException e )
        {
            e.printStackTrace();
        }

Windows-1252

接下来,我们执行相同的操作,但将 "windows-1252" 设置为我们包​​装的 ----------| UTF-8 |-------------------------- hexInput: C0 = codePoint: 192 = string: [À] = isLetter: true = name: LATIN CAPITAL LETTER A WITH GRAVE hexInput: A8 = codePoint: 168 = string: [¨] = isLetter: false = name: DIAERESIS hexInput: 96 = codePoint: 150 = string: [] = isLetter: false = name: START OF GUARDED AREA hexInput: FE = codePoint: 254 = string: [þ] = isLetter: true = name: LATIN SMALL LETTER THORN Charset。在进行包装之前,我们验证这样的字符编码在我们当前的 JVM 上确实可用。

System.out

运行时。

        // windows-1252
        System.out.println( "----------|  windows-1252  |--------------------------" );

        // Verify windows-1252 charset is available on the current JVM.
        String windows1252CharSetName = "windows-1252";
        boolean isWindows1252CharsetAvailable = Charset.availableCharsets().keySet().contains( windows1252CharSetName );
        if ( isWindows1252CharsetAvailable )
        {
            System.out.println( "isWindows1252CharsetAvailable = " + isWindows1252CharsetAvailable );
        } else
        {
            System.out.println( "FAIL - No charset available for name: " + windows1252CharSetName );
        }

        try
        {
            PrintStream printStream = new PrintStream( System.out,windows1252CharSetName );

            for ( String hexInput : hexInputs )
            {
                int codePoint = Integer.parseInt( hexInput,16 );
                String string = Character.toString( codePoint );
                printStream.println( "hexInput: " + hexInput + " = codePoint: " + codePoint + " = string: [" + string + "] = isLetter: " + Character.isLetter( codePoint ) + " = name: " + Character.getName( codePoint ) );
            }
        }
        catch ( UnsupportedEncodingException e )
        {
            e.printStackTrace();
        }

Latin-1

我们也可以尝试 Latin-1,产生不同的结果。

----------|  windows-1252  |--------------------------
isWindows1252CharsetAvailable = true
hexInput: C0 = codePoint: 192 = string: [�] = isLetter: true = name: LATIN CAPITAL LETTER A WITH GRAVE
hexInput: A8 = codePoint: 168 = string: [�] = isLetter: false = name: DIAERESIS
hexInput: 96 = codePoint: 150 = string: [?] = isLetter: false = name: START OF GUARDED AREA
hexInput: FE = codePoint: 254 = string: [�] = isLetter: true = name: LATIN SMALL LETTER THORN

运行时。

        // ISO-8859-1
        System.out.println( "----------|  Latin-1  |--------------------------" );

        // Verify that  charset is available on the current JVM.
        String latin1CharsetName = "ISO-8859-1"; // Also known as "Latin-1".
        boolean isLatin1CharsetNameAvailable = Charset.availableCharsets().keySet().contains( latin1CharsetName );
        if ( isLatin1CharsetNameAvailable )
        {
            System.out.println( "isLatin1CharsetNameAvailable = " + isLatin1CharsetNameAvailable );
        } else
        {
            System.out.println( "FAIL - No charset available for name: " + latin1CharsetName );
        }

        try
        {
            PrintStream printStream = new PrintStream( System.out,latin1CharsetName );

            for ( String hexInput : hexInputs )
            {
                int codePoint = Integer.parseInt( hexInput,16 );
                String string = Character.toString( codePoint );
                printStream.println( "hexInput: " + hexInput + " = codePoint: " + codePoint + " = string: [" + string + "] = isLetter: " + Character.isLetter( codePoint ) + " = name: " + Character.getName( codePoint ) );
            }
        }
        catch ( UnsupportedEncodingException e )
        {
            e.printStackTrace();
        }

结论

因此您可以看到,当对我们包装的 ----------| Latin-1 |-------------------------- isLatin1CharsetNameAvailable = true hexInput: C0 = codePoint: 192 = string: [�] = isLetter: true = name: LATIN CAPITAL LETTER A WITH GRAVE hexInput: A8 = codePoint: 168 = string: [�] = isLetter: false = name: DIAERESIS hexInput: 96 = codePoint: 150 = string: [�] = isLetter: false = name: START OF GUARDED AREA hexInput: FE = codePoint: 254 = string: [�] = isLetter: true = name: LATIN SMALL LETTER THORN Charset 进行硬编码时,我们确实看到了差异。使用 UTF-8,我们得到实际字符 System.out,而使用 windows-1252,我们得到三个时髦的问号字符和一个常规问号 [À],[¨],[],[þ]。请记住,我们在代码中添加了方括号。

我的代码的这种行为符合我的期望,显然也符合您的期望。这四个十六进制/十进制整数中的两个是 Unicode 中的字母,而它们都不是 Windows 1252 字符集中的字母,也不是 Latin-1 中的字母。对我来说唯一神秘的是十六进制 96 十进制 150 数字有两种不同的表示形式,一个是 UTF-8 的空格,一个是 windows-1252 的问号,然后是拉丁语 1 下的一个时髦的问号。>

结论:您的 [�],[�],[?],[�] 没有使用您认为它正在使用的 System.out。我怀疑虽然您的 JVM 的 JVM 的默认 Charset 可能被命名为“windows-1252”,但您的 Charset 实际上是 Unicode 字符集,可能带有 UTF-8编码。


读者注意事项:如果不熟悉字符集和字符编码,我推荐有趣且易读的帖子 The Absolute Minimum Every Software Developer Absolutely,Positively Must Know About Unicode and Character Sets (No Excuses!)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?