1

在访问 Windows 系统资源(与音频相关)时,我发现 Windows 使用自己的字符集提供所述资源的描述字符串,而 Java 将这些字符串视为默认处理所有字符串:unicode-encoded。所以,我得到了一堆问号,而不是合理的文本:

????????? ???????? ???????

使用String .codePointAt()方法我发现这些问题实际上隐藏了一些使用 Windows-1252 编码的文本。我当然想看哪个。因此,我开始着手将这个字符串转换为可读的东西。

半天后,在我翻遍了 Stackoverflow 和 Google 的相关主题后,我取得了一些进展,但这只会引发更多问题。所以,这是我的代码:

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import javax.sound.sampled.AudioSystem;


public class Study_Encoding {
    
    //private static final Charset utf8Charset = Charset .forName ("UTF-8");
    private static final Charset win1251Charset = Charset .forName ("Windows-1251");
    private static final Charset win1252Charset = Charset .forName ("Windows-1252");
    
    public static void main(String[] args) {
        
        String str = AudioSystem .getMixerInfo () [0] .getName ();
        
        System .out .println ("Original string:");
        System .out .println (str + "\n");
        
        System .out .println ("Its code-points:");
        displayCodePointSequence (str);
        
        System .out .println ("Windows-1251-decoded byte array (wrong):");
        byte [] win1251ByteArr = str .getBytes (win1251Charset);
        displayByteSequence (win1251ByteArr);
        
        System .out .println ("Windows-1252-decoded byte array (right):");
        byte [] win1252ByteArr = str .getBytes (win1252Charset);
        displayByteSequence (win1252ByteArr);
        
        System .out .println ("Windows-1252-encoded string (wrong):");
        try {
            System .out .println (win1252Charset .newDecoder ()
                    .decode (ByteBuffer .wrap (win1252ByteArr)) .toString () + "\n");
        } catch (Exception e) {
            System .out .println ("ERROR:" + e .toString ());
        }
        
        System .out .println ("Windows-1251-encoded string (right):");
        try {
            System .out .println (win1251Charset .newDecoder ()
                    .decode (ByteBuffer .wrap (win1252ByteArr)) .toString () + "\n");
        } catch (Exception e) {
            System .out .println ("ERROR:" + e .toString ());
        }
    }
    
    private static void displayCodePointSequence (String str) {
        
        if (null == str) {
            System .out .println ("No string");
            return;
        }
        if (str .isEmpty ()) {
            System .out .println ("Empty string");
            return;
        }
        for (int k = 0; str .length () > k; ++k) {
            System .out .print (str .codePointAt (k) + " ");
        }
        System .out .println ("[" + str .length () + "]\n");
    }
    
    private static void displayByteSequence (byte [] byteArr) {
        
        if (null == byteArr) {
            System .out .println ("No array");
            return;
        }
        if (0 == byteArr .length) {
            System .out .println ("Empty array");
            return;
        }
        for (int k = 0; byteArr .length > k; ++k) {
            System .out .print ((((int) byteArr [k]) & 0xFF) + " ");
        }
        System .out .println ("[" + byteArr .length + "]\n");
    }
}

该程序产生以下输出(最后一行是我一直想要得到的):

Original string:
????????? ???????? ???????

Its code-points:
207 229 240 226 232 247 237 251 233 32 231 226 243 234 238 226 238 233 32 228 240 224 233 226 229 240 [26]

Windows-1251-decoded byte array (wrong):
63 63 63 63 63 63 63 63 63 32 63 63 63 63 63 63 63 63 32 63 63 63 63 63 63 63 [26]

Windows-1252-decoded byte array (right):
207 229 240 226 232 247 237 251 233 32 231 226 243 234 238 226 238 233 32 228 240 224 233 226 229 240 [26]

Windows-1252-encoded string (wrong):
????????? ???????? ???????

Windows-1251-encoded string (right):
Первичный звуковой драйвер

任何人都可以看到 win1251 和 win1252 编码由于某种原因混合在一起。另外,我想,有一种方法可以使 Java 程序将所有字符串视为某种本机编码中的字符串(我不想要!!!),或者至少是系统提供的。所以,...

...我的问题是:

  1. 如何转换字符串?(我想我已经解决了)
  2. 这是怎么回事?(使用混合字符集和其他所有内容)
  3. 怎么做才对?(字符串获取,如果没有,字符串转换)

编辑:

似乎我没有说清楚,但我不是在谈论文本文件的内容,而是关于系统提供的字符串,例如设备(物理和虚拟)的名称和描述,可能是文件和目录名称。在上面的示例中,字符串“Первичный звуковой драйвер”应该类似于英文 Windows 中的“默认音频设备”。

4

1 回答 1

1

这是一个复杂的问题,但基础是:

  1. 没有编码就没有字符串这样的东西。最常见的形式(c 字符串)使用 ASCII 编码。Java 本机使用 UTF16。
  2. 某些字符集之间没有完美的编码转换。例如 ASCII -> EBCDIC -> ASCII 由于这些字符集之间缺乏 1:1 的关系而导致字符串损坏。
  3. 对我来说,该文件似乎包含 1 个字符集中的数据,并且您希望将其转换为 Java 本机形式 (UTF16)。这很简单。您可以使用 FileInputStream 来读取字节数据。您可以使用 Reader 读取字符串数据。因此,您希望您的阅读器执行转换: https ://docs.oracle.com/javase/8/docs/api/java/io/InputStreamReader.html#InputStreamReader(java.io.InputStream,%20java.nio.charset .字符集)

所以基本上,你所追求的代码是这样的:

try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(myFile), StandardCharsets.CHARSETOFCHOICE)))
{
   String line;
   while ((line = br.readLine()) != null)
   {
      // Do what you want with the string.
   }
}

我将重申转换可能不完美,具体取决于源/目标字符集,并可能导致损坏。

于 2020-08-26T17:53:40.600 回答