Java中字符串的字节码

220
在Java中,如果我有一个字符串x,如何计算该字符串中的字节数?

20
有时候,我们可能会使用字符串表示HTTP响应的主体,并使用其大小来设置“Content-Length”标头。需要注意的是,“Content-Length”标头用八位字节(即“octets”)而非字符数来指定大小。参考文献:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.13 - jacobq
4
数据库列可能有字节长度限制,例如Oracle中的VARCHAR2(4000 BYTE)。一个人可能想要知道使用所需编码的字符串的字节数,以知道该字符串是否适合。 - Somu
@iX3 正是我想要做的事情。 - MC Emperor
1
我认为这个问题有两种可能的解释,取决于意图:一种是“我的String使用了多少内存?”。答案由@roozbeh提供(也许除了VM细节,如压缩OOPS)。另一个是,“如果我将字符串转换为byte[],那么该字节数组会使用多少内存?”这就是Andrzej Doyle回答的问题。差异可能很大:“Hello World”在UTF8中是11个字节,但String(根据@roozbeh)是50个字节(如果我的数学没错的话)。 - L. Blanc
我应该补充一下,这11个字节不包括保存它们的byte[]对象的开销,因此比较有些误导性。 - L. Blanc
10个回答

342

字符串是字符(即代码点)的列表。将其转换为字节所占用的字节数完全取决于使用的编码方式

尽管如此,您可以将字符串转换为字节数组,然后按以下方式查看其大小:

// The input string for this test
final String string = "Hello World";

// Check length, in characters
System.out.println(string.length()); // prints "11"

// Check encoded sizes
final byte[] utf8Bytes = string.getBytes("UTF-8");
System.out.println(utf8Bytes.length); // prints "11"

final byte[] utf16Bytes= string.getBytes("UTF-16");
System.out.println(utf16Bytes.length); // prints "24"

final byte[] utf32Bytes = string.getBytes("UTF-32");
System.out.println(utf32Bytes.length); // prints "44"

final byte[] isoBytes = string.getBytes("ISO-8859-1");
System.out.println(isoBytes.length); // prints "11"

final byte[] winBytes = string.getBytes("CP1252");
System.out.println(winBytes.length); // prints "11"

所以,你看到了,即使是一个简单的“ASCII”字符串,在不同的编码方式下它的表示方式所占用的字节数也会不同。在你的情况下,使用你感兴趣的字符集作为getBytes()方法的参数。不要陷入误认为UTF-8将每个字符都表示为单个字节的陷阱中,因为这并不是真实情况:

final String interesting = "\uF93D\uF936\uF949\uF942"; // Chinese ideograms

// Check length, in characters
System.out.println(interesting.length()); // prints "4"

// Check encoded sizes
final byte[] utf8Bytes = interesting.getBytes("UTF-8");
System.out.println(utf8Bytes.length); // prints "12"

final byte[] utf16Bytes= interesting.getBytes("UTF-16");
System.out.println(utf16Bytes.length); // prints "10"

final byte[] utf32Bytes = interesting.getBytes("UTF-32");
System.out.println(utf32Bytes.length); // prints "16"

final byte[] isoBytes = interesting.getBytes("ISO-8859-1");
System.out.println(isoBytes.length); // prints "4" (probably encoded "????")

final byte[] winBytes = interesting.getBytes("CP1252");
System.out.println(winBytes.length); // prints "4" (probably encoded "????")

(注意,如果您没有提供字符集参数,则使用平台的默认字符集。这在某些情况下可能很有用,但通常应避免依赖默认值,并且在需要进行编码/解码时始终使用显式字符集。)


1
所以,如果我使用getBytes(),它会给我与x.length相同的长度,这样我就不确定了,因为我不确定是否正确。 - Green
6
@Green Ash:字节数组的长度 -- getBytes() -- 和 x.length 可能相等,但并不保证相等。如果所有字符都用单个字节表示,这两者将是相等的。对于使用每个字符一个字节(或更少)的字符编码,例如ISO-8859-1,这始终成立。UTF-8 使用 1 或 2 个字节,因此取决于字符串中确切的字符。然后有一些字符编码始终每个字符使用两个字节。 - Kris
我们可以说在Java中,一个字符串字符总是占用4个字节的内存空间吗?因为一个字符串字符是一个代码点。 - Koray Tugay
@KorayTugay char 不是 一个码点。一个char是16位(基本上是无符号的short),因此它在内存中始终占用2个字节。需要使用int来表示4字节的码点,这就是为什么像String.codePointAt()这样的方法返回int而不是char的原因。 - Andrzej Doyle
1
@KorayTugay 是的,或多或少。不过你可以对因果顺序进行争论。我更倾向于说一个char总是2个字节,因为它是一种原始数据类型,定义为2个字节宽。(而UTF-16表示主要是其结果,而不是反过来。) - Andrzej Doyle
显示剩余8条评论

70

如果你正在使用64位引用:

sizeof(string) = 
8 + // object header used by the VM
8 + // 64-bit reference to char array (value)
8 + string.length() * 2 + // character array itself (object header + 16-bit chars)
4 + // offset integer
4 + // count integer
4 + // cached hash code

换句话说:

sizeof(string) = 36 + string.length() * 2

在32位虚拟机或启用压缩的OOPs(-XX:+UseCompressedOops)的64位虚拟机上,引用占用4个字节。因此,总数为:

sizeof(string) = 32 + string.length() * 2

这并未考虑对字符串对象的引用。


9
我想你需要翻译这段话:“I was assuming the question was about the number of bytes allocated in memory for a String object. If the question is about the number of bytes required to serialize the String, as others have pointed out, it depends on the encoding used.”如果问题是关于在内存中为String对象分配的字节数,那么我假设的答案是这个数字。但是,如果问题是关于序列化String所需的字节数,正如其他人指出的那样,这将取决于使用的编码方式。 - roozbeh
3
你的答案来源是什么?谢谢。 - mavis
1
注意:sizeof 应该是 8 的倍数。 - dieter

26

这里有一个学究式的答案(虽然并不一定是最有用的,取决于你对结果想要做什么):

string.length() * 2

Java字符串在物理上以UTF-16BE编码存储,每个代码单元使用2个字节,并且String.length()以UTF-16代码单元表示长度,因此这等价于:

final byte[] utf16Bytes= string.getBytes("UTF-16BE");
System.out.println(utf16Bytes.length);

这将告诉您内部char数组的大小,以字节为单位。

注意: "UTF-16"将与"UTF-16BE"给出不同的结果,因为前者编码将插入BOM,使数组的长度增加2个字节。


Roozbeh的答案更好,因为它也考虑了其他字节。 - Lodewijk Bogaards
@finnw 你确定编码是UTF-16BE而不是UTF-16吗?根据String类的Javadoc(https://docs.oracle.com/javase/6/docs/api/java/lang/String.html),“字符串表示UTF-16格式的字符串...”。 - entpnerd

19

但是抱歉,当我编译你的代码时,它会因为参数“UTF-8”而出现错误。当我传递一个空参数时,它会给出与x.length相同的长度。我误解了这个概念,请帮忙解释一下。 - Green
@Green Ash,你用的是哪个版本的Java? - Buhake Sindi
@Green Ash,你遇到了什么异常? - Buhake Sindi
2
请明确,这是输出:test.java:11: 未报告的异常java.io.UnsupportedEncodingException; 必须捕获或声明为抛出 byte[] b = s.getBytes("UTF-8"); ^ 1个错误处理完成。 - Green
4
@Green,请尝试使用s.getBytes(Charset.forName("UTF-8"))。此代码行的作用是将字符串编码为UTF-8格式的字节数组。 - james.garriss

10

String实例在内存中分配一定数量的字节。也许你在查看像sizeof("Hello World")这样的东西,它将返回数据结构本身分配的字节数?

在Java中,通常不需要sizeof函数,因为我们从不分配内存来存储数据结构。我们可以查看String.java文件进行粗略估计,我们看到一些'int'、一些引用和一个char[]Java语言规范定义了char的范围为0到65535,因此两个字节足以在内存中保存一个字符。但是JVM不必将一个字符存储在2个字节中,它只需要保证char的实现可以容纳定义范围内的值。

因此,在Java中,sizeof确实没有任何意义。但是,假设我们有一个大型字符串,一个char分配两个字节,则String对象的内存占用至少为2 * str.length()字节。


8

有一种方法叫做getBytes()。明智使用它。


21
明智的做法是不要使用没有字符集参数的那个。 - Thilo
为什么?如果我配置我的环境以UTF8编码运行,这是一个问题吗? - ziggy
2
getBytes方法还会创建并复制字节数组,因此如果您处理的是长字符串,这个操作可能会变得很昂贵。 - ticktock
@ticktock,如果你还在的话,是的,但是有什么替代方案吗?我来这里是希望有一个库函数可以返回所需的存储空间,以便我可以将其合并到更大的分配中。 - SensorSmith

4
尝试这个:

试试看:

Bytes.toBytes(x).length

假设您之前已经声明并初始化了 x。

4
这个类是标准的Java库吗?我找不到Bytes类。 - Kröw

3

使用apache commons来尝试此操作:

String src = "Hello"; //This will work with any serialisable object
System.out.println(
            "Object Size:" + SerializationUtils.serialize((Serializable) src).length)

3
为避免使用 try catch,可以使用以下方法:
String s = "some text here";
byte[] b = s.getBytes(StandardCharsets.UTF_8);
System.out.println(b.length);

0
如果您想引用某个标准包中的 Charset,而不是使用字符串字面量 "UTF-8",那么您可以使用 java.nio
import java.nio.charset.StandardCharsets;
..
int numBytes = myString.getBytes(StandardCharsets.UTF_8).length;

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