如何将代码点 80 处的字符写入 Windows-1252 中的文件?

How do I write a character at codepoint 80 to a file in Windows-1252?

我正在尝试将字节写入 windows-1252 字符集的文件。下面的示例将浮点数的原始字节写入文件,与我在实际程序中所做的类似。

在给出的示例中,我将 1.0f 的原始十六进制写入 test.txt。由于 1.0f 的原始十六进制是 3f 80 00 00 我希望得到 ?€(NUL)(NUL),尽我所能看到在Windows 1252 Wikipedia article,0x3f应该对应'?',0x80应该对应到'',0x00 是'NUL'。一切顺利,直到我真正尝试写入文件;那时,我在控制台上得到一个 java.nio.charset.UnmappableCharacterException ,在程序因该异常停止后,文件只有一个 '?'在里面。完整的控制台输出在下面的代码下方。

看起来 Java 认为代码点 0x80 在 windows-1252 代码页中不可映射。然而,这似乎不对——所有代码点都应映射到该代码页中的实际字符。问题肯定是代码点 0x80,就好像我尝试使用 0.5f (3f 00 00 00) 很高兴写 ?(NUL)(NUL)(NUL) 到文件中,并且不抛出异常。尝试其他代码页似乎也不起作用;查看 Java 语言 here 支持的密钥编码,只有 UTF 系列不会给我例外,但由于它们的编码,它们没有给我代码点 0x80 在实际文件中。

我将尝试只使用字节来代替,这样我就不必担心字符串编码,但是有人能告诉我为什么我的下面的代码会出现异常吗?

代码:

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CharsetTest {
    public static void main(String[] args) {
        float max = 1.0f;
        System.out.println("Checking " + max);
        String stringFloatFormatHex = String.format("%08x", Float.floatToRawIntBits(max));
        System.out.println(stringFloatFormatHex);
        byte[] bytesForFile = javax.xml.bind.DatatypeConverter.parseHexBinary(stringFloatFormatHex);
        String stringForFile = new String(bytesForFile);
        System.out.println(stringForFile);

        String charset = "windows-1252";
        try {
            Writer output = Files.newBufferedWriter(Paths.get("test.txt"), Charset.forName(charset));
            output.write(stringForFile);
            output.close();
        } catch (IOException e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
        }
    }
}

控制台输出:

Checking 1.0
3f800000
?�  
Input length = 1
java.nio.charset.UnmappableCharacterException: Input length = 1
    at java.nio.charset.CoderResult.throwException(CoderResult.java:282)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:285)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
    at java.io.BufferedWriter.close(BufferedWriter.java:265)
    at CharsetTest.main(CharsetTest.java:21)

编辑: 问题出在指令 String stringForFile = new String(bytesForFile); 上,位于 DatatypeConverter 下方。由于我在不提供字符集的情况下构造字符串,因此它使用我的默认字符集,即 UTF-8,它没有代码点 80 的符号。但是,它只在写入文件时抛出异常。这不会在下面的代码中发生,因为我的重构(记住 Johannes Kuhn 在评论中的建议)没有使用 String(byte[]) 构造函数而不指定字符集。

Johannes Kuhn's suggestion about the String(byte[]) 构造函数给了我一些很好的线索。我最终得到了以下代码,它看起来工作正常:甚至将 符号打印到控制台并将其写入 test.txt。这表明可以使用 windows-1252 代码页翻译代码点 80

如果此时我猜测为什么这段代码有效而另一段代码无效,我仍然会感到困惑,但我猜这是围绕 javax.xml.bind.DatatypeConverter.parseHexBinary(stringFloatFormatHex); 中的转换。这看起来是主要区别,尽管我不确定为什么它很重要。

无论如何,下面的代码有效(我什至不必将它变成字符串;我可以使用 FileOutputStream fos = new FileOutputStream("test.txt"); fos.write(bytes); fos.close(); 将字节写入文件),所以我对这个代码很满意.

代码:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;

public class BytesCharsetTest {
    public static void main(String[] args) {
        float max = 1.0f;
        System.out.println("Checking " + max);
        int convInt = Float.floatToRawIntBits(max);
        byte[] bytes = ByteBuffer.allocate(4).putInt(convInt).array();

        String charset = "windows-1252";
        try {
            String stringForFile = new String(bytes, Charset.forName(charset));
            System.out.println(stringForFile);

            Writer output = Files.newBufferedWriter(Paths.get("test.txt"), Charset.forName(charset));
            output.write(stringForFile);
            output.close();
        } catch (IOException e) {
            System.err.println(e.getMessage());
            e.printStackTrace();
        }
    }
}

控制台输出:

Checking 1.0
?€  

Process finished with exit code 0