在Java中,一个字符是1个字节还是2个字节?

8

我以为java中的字符是16位,正如java文档中所建议的。那么对于字符串来说不是这种情况吗?我有一段代码将一个对象存储到文件中:

public static void storeNormalObj(File outFile, Object obj) {
    FileOutputStream fos = null;
    ObjectOutputStream oos = null;
    try {
        fos = new FileOutputStream(outFile);
        oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
        oos.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            oos.close();
            try {
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

基本上,我尝试将一个字符串 "abcd" 存储到文件 "output" 中,当我用编辑器打开 output 并删除非字符串部分后,剩下的只是字符串 "abcd",总共只有4个字节。有人知道为什么吗?Java 是否会自动使用 ASCII 而不是 UNICODE 来保存可以支持 ASCII 的字符串以节省空间?谢谢。

3
只是一个想法:Java 是否保存为 UTF-8 格式? - Rekin
是的,确切地说 - 它使用修改过的UTF-8编码存储字符串... - MJB
5个回答

8
我认为你所说的“非字符串部分”是指ObjectOutputStream在创建时发出的字节。你可能不想使用ObjectOutputStream,但我不知道你的要求。
顺便说一下,Unicode和UTF-8不是同一件事。Unicode是一个标准,其中包括指定可用的字符等内容。UTF-8是一种字符编码,它指定了如何以1和0的形式对这些字符进行物理编码。UTF-8可以使用1个字节来表示ASCII(<=127),最多使用4个字节来表示其他Unicode字符。
UTF-8是ASCII的严格超集。因此,即使您为文件指定UTF-8编码并将“abcd”写入其中,它也只包含那四个字节:它们在ASCII中与它们在UTF-8中的物理编码相同。
您的方法使用ObjectOutputStream,其编码实际上与ASCII或UTF-8有显着不同!如果仔细阅读Javadoc,如果obj是字符串并且已在流中出现,则对writeObject的后续调用将导致引用先前字符串的引用被发出,在重复字符串的情况下可能会导致写入较少的字节。

如果你真的想深入了解这个问题,你应该花费大量时间阅读关于Unicode和字符编码系统的内容。维基百科有一篇Unicode的优秀文章可以作为起点。


关于Unicode字符串的内存表示,还有一件重要的事情是,一个Unicode码点并不总是适合于16位字符。 - CodesInChaos
@CodeInChaos - 你能提供一些超过16位的场景吗? - Manimaran Selvan
任何不在基本平面的字符其码点都大于2^16-1。因此UTF-16将其编码为两个16位字符。http://en.wikipedia.org/wiki/UTF-16/UCS-2 - CodesInChaos
回答楼上的问题,这取决于字符串的编码方式... - 8bitjunkie

2
是的,在Java运行环境中,char 只是 Unicode。如果你想使用16位编码来写它,请使用 FileWriter
    FileWriter outputStream = null;

    try {
        outputStream = new FileWriter("myfilename.dat");

        int c;
        while ((c = inputStream.read()) != -1) {
            outputStream.write(c);
        }
    } finally {
        if (outputStream != null) {
            outputStream.close();
        }
    }

我认为你没有理解Pal的观点-他在问为什么Outputstream要写入单个字节。而我相信答案就是我下面的回答。 - MJB
1
@MJB - 不,编码是很重要的。如果他使用16位编码进行编写,操作系统会考虑并为单个字符分配16位。但这仍取决于操作系统。 - Manimaran Selvan
1
我不建议使用FileWriter,因为它没有指定编码的方式,仅支持默认编码。更好的选择是(虽然更冗长)new OutputStreamWriter(new FileOutputStream(file), encoding) - Joachim Sauer

1

如果你查看String的源代码,你会发现它调用了DataOutput.writeUTF来写入字符串。而且如果你阅读一下,你会发现它们被写成了"modified UTF-8"。细节很长,但是如果你不使用非7位ASCII字符,是的,它只会占用一个字节。如果你想要详细了解,请查看DataOutput.writeUTF()中极其冗长的javadoc。


0

您可能会感兴趣知道,在Java Update 21性能版本及更高版本中,有一个-XX:+UseCompressedStrings选项。这将允许String使用byte[]来表示不需要char[]的字符串。

尽管Java Hotspot VM Options指南建议默认开启此选项,但这可能仅适用于性能版本。只有在显式开启时,它才对我有效。


-1

那么你期望一个16*4=64位 = 8字节的文件吗?比UTF-8或ASCII编码更大。一旦文件被写入文件,内存(以空间为单位)管理就由操作系统控制了。你的代码对此没有控制权。


这不是真的,你的代码绝对可以控制输出的编码方式。 - sjr
我理解您的意思。但即使您进行了指定,操作系统仍然需要管理所需的空间。(请理解,我并不反对操作系统更改编码) - Manimaran Selvan
@sjr - 实际上我给你的回答点赞。它清楚地说明了,如果你将 abcd 写入文件中,操作系统(尽管编码为 UTF-8)只会分配 1 个字节(因为这已经足够)。 - Manimaran Selvan
操作系统与Java在序列化时如何编码字符串无关。 - CodesInChaos
也许你应该更好地解释一下。数据与字节序列之间的映射不是操作系统的工作。操作系统只负责将该字节序列存储在磁盘上。但它不知道也不关心任何编码方式。在这个问题的背景下,操作系统是完全无关紧要的。 - CodesInChaos
是的,也许吧!我说的是一旦这个场景被写入磁盘的情况! - Manimaran Selvan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接