String 最多能存储多少个字符/String 字符串的最大长度/String 字符串有没有长度限制
为什么最大长度是 65534 而不是 65535 呢?这是 Java 虚拟机历史上的一个设计缺陷:如果 Java 虚拟机中的一个方法的 code 属性的长度刚好是 65535 个字节,并且以一个 1 个字节长度的指令结束,那么这条指令将不能被异常处理器所处理。不过编译器可以通过限制任何方法、实例初始化方法或类初始化方法的 code[] 数组最大长度为 65534,这样可以间接弥补这个 BUG。St
基于 JDK8 进行分析
1.编译时字节限制
String s = "11111...1111"; // 其中有 10 万个字符 "1"
当我们使用如上形式定义一个字符串的时候,当我们执行 javac
编译时,是会抛出异常的,提示 java: constant string too long
javac
是用于将 Java 源文件编译成 class 字节码二进制文件,那么在 class 文件生成过程中,就需要遵守一定的规范。
根据《Java 虚拟机规范》中对常量池的定义,CONSTANT_String_info
用于表示 java.lang.String
类型的常量对象,格式如下:
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
其中,string_index
项的值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_Utf8_info
结构,表示一组 Unicode 码点序列,这组 Unicode 码点序列最终会被初始化为一个 String 对象。
CONSTANT_Utf8_info
结构用于表示字符串常量的值:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
其中,length 则指明了 bytes[] 数组的长度,其类型为 u2
,即两个字节的无符号数,那么其可表示的最大值为
2
16
−
1
=
65535
2^{16} - 1 = 65535
216−1=65535。
也就是说,编译时生成的 class 文件中的字符串常量的字节数不能超过 65535。
但即使我们定义了一个由 65535 个 “1” 组成的字符串,还是会允许报错,报错原因还是 java: constant string too long
这个原因可以在 javac
的 tools.jar
包中找到,在 com.sun.tools.javac.jvm.Gen
类中有如下方法:
private void checkStringConstant(JCDiagnostic.DiagnosticPosition var1, Object var2) {
// String.length() >= 65535 就抛出异常
if (this.nerrs == 0 && var2 != null && var2 instanceof String && ((String)var2).length() >= 65535) {
this.log.error(var1, "limit.string", new Object[0]);
++this.nerrs;
}
}
当参数类型为 String,并且长度大于等于 65535 的时候,就会导致编译失败。
为什么最大长度是 65534 而不是 65535 呢?这是 Java 虚拟机历史上的一个设计缺陷:如果 Java 虚拟机中的一个方法的 code 属性的长度刚好是 65535 个字节,并且以一个 1 个字节长度的指令结束,那么这条指令将不能被异常处理器所处理。不过编译器可以通过限制任何方法、实例初始化方法或类初始化方法的 code[] 数组最大长度为 65534,这样可以间接弥补这个 BUG。
所以,综合 CONSTANT_Utf8_info
中的 length
大小限制与 javac
检查机制,字符串常量的最大字节数为 65535,最大长度为 65534。
Java 中的字符常量都是使用 UTF8 编码的,UTF8 编码使用 1~4 个字节来表示具体的 Unicode 字符。所以有的字符占用一个字节,而我们平时所用的大部分中文都需要 3 个字节来存储。
// 65534 个字母,编译通过
String s1 = "dd..d";
// 21845 个中文 “自”,编译通过
String s2 = "自自...自";
// 一个英文字母 d 加上 21845 个中文 “自”,编译失败
String s3 = "d自自...自";
- 对于 s1,一个字母 d 的 UTF8 编码占用 1 个字节,65534 字母占用 65534 个字节,长度是 65534,字节数与长度都没有超出限制,所以可以编译通过。
- 对于 s2,一个中文占用 3 个字节,21845 个正好占用 65535 个字节,而且字符串长度是 21845,字节数与长度都没有超出限制,所以可以编译通过。
- 对于 s3,一个英文字母 d 加上 21845 个中文“自”占用 65536 个字节,超过了最大字节数,编译失败。
2.运行时长度限制
public int length() {
return value.length;
}
String 类提供了一个 length() 方法,返回值为有符号的 32 位的 int 类型,所以理论上 String 的最大长度为 2 31 − 1 2^{31} -1 231−1,因此在最大长度情况下占用内存约 ( 2 31 − 1 ) ∗ 2 ∗ 16 / 8 / 1024 / 1024 / 1024 = 4 G B (2^{31}-1)*2*16/8/1024/1024/1024 = 4GB (231−1)∗2∗16/8/1024/1024/1024=4GB。如果 JVM 不能分配这么多内存的话,会出现 OOM 内存不足错误。
// success
public class Main {
public static void main(String[] args) throws IOException {
String s = "";
for (int i = 0; i < 100000; i++) {
s += "1";
}
// 100000
System.out.println(s.length());
System.out.println(s);
}
}
3.小结
String 在不同的时期,具有不同的长度限制。
- 编译时限制:UTF8 编码的字符串常量的最大字节数为 65535,最大长度为 65534
- 运行时限制:字符串的长度不能超过 2 31 − 1 2^{31}-1 231−1,且占用的内存不能超过当前虚拟机还能够提供的最大值。
4.补充
JDK9 以后对 String 的存储进行了优化。底层不再使用 char 数组存储字符串,而是使用 byte 数组。对于 LATIN1 字符的字符串可以节省一倍的内存空间。
更多推荐
所有评论(0)