对于字符串可以承受的最大长度,要分为2个阶段,一个是编译时期(也就是你代码定义了一个String字符串,String s= “xiaofang”),一个是运行时期(指在程序运行过程中)
编译期String字符串的限制
我们都知道JVM里面是包含常量池的,(是一种对字符串的性能优化,不用反复创建新的字符串了)当我们使用字符串字面量直接定义String的时候,是会把字符串在常量池中存储一份的。常量池中的每一项常量都是一个表,都有自己对应的类型。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8_info类型表,结构如下:
CopyCONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
u2类型的length的值就表明了这个UTF-8编码字符串长度是多少字节。所以CONSTANT_Utf8_info型常量对应的最大长度也就是java中UTF-8编码的字符串的长度,顺便提一下Class文件中的方法和字段也是引用CONSTANT_Utf8_info型常量来描述名称的。u2是无符号的16位整数,因此理论上允许的的最大长度是2^16-1=65535
我们重点关注下长度为 length 的那个bytes数组,这个数组就是真正存储常量数据的地方,而 length 就是数组可以存储的最大字节数。length 的类型是u2,u2是无符号的16位整数,因此理论上允许的的最大长度是2^16-1=65535。所以上面byte数组的最大长度可以是65535。
编译器javac下String的长度
创建一个测试类
public class TestStr {
public static void main(String[] args) {
String LongStr ="aaaa...";//一共65535个a
System.out.println(LongStr.length());
}}
使用javac命令编译它。编译报错。相应目录没有生成对应的TestStr.class文件
去除一个字符串,使用65534个字符串。
public class TestStr {
public static void main(String[] args) {
String LongStr ="aaaa...";//一共65534个a
System.out.println(LongStr.length());
}}
javac命令编译它。编译正常。相应目录生成对应的TestStr.class文件
我们在看看Oracle JDK的编译工具Javac内部,javac也是java写的。
Copyprivate void checkStringConstant(DiagnosticPosition var1, Object var2) {
if (this.nerrs == 0 && var2 != null && var2 instanceof String && ((String)var2).length() >= 65535) {
this.log.error(var1, "limit.string", new Object[0]);
++this.nerrs;
}}
虽然U2类型能表达的最大值是65535。但是在编译时会受到编译器的限制,上面65535个长度的字符串在javac下报错了就是受到了javac编译器的限制了。如果你在上面65534长度生成的TestStr.class中手动在添加一个字符串(注意是在javac编译后的class文件中添加)是可以得到65535长度的结果。
总结一下:在Javac编译器下,字符串String的最大长度限制也即是U2类型所能表达的最大长度65534。避开javac最大长度是65535?
Eclise的JDT编译器下String的长度
Eclipse有自己的Java编译器,称为[JDT Core] [2](org.eclipse.jdt.core)。并不是用的javac编译器。
创建一个测试类
发现Eclipse执行可正常执行。同样的代码在IDEA中是会报错的。
这是因为Eclise的JDT编译器问题。在Eclipse工作空间下找到其编译生成的TestStr.class。使用javap命令查看
6: invokespecial #20; //Method java/lang/StringBuilder."<init>":(Ljava/la
ng/String;)V
9: ldc #23; //String QyNDAbAgIGqQIBAQ1
11: invokevirtual #25; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder;
14: invokevirtual #29; //Method java/lang/StringBuilder.toString:()Ljava/l
ang/String;
17: invokevirtual #33; //Method java/lang/String.intern:()Ljava/lang/Strin
g;
20: astore_1
21: getstatic #38; //Field java/lang/System.out:Ljava/io/PrintStream;
24: aload_1
25: invokevirtual #44; //Method java/lang/String.length:()I
28: invokevirtual #48; //Method java/io/PrintStream.println:(I)V
31: return
}
上面我们就明白了之所以JDT能编译过,只是因为JDT优化为了StringBuilder的append。
Eclipse编译器本身包含在org.eclipse.jdt.core插件中。Eclipse不会使用任何用户安装的JDK。相反,由于以下主要原因,它使用自己的JDT核心来编译Java程序:
主要原因是JDT核心具有渐进式编译的能力,这意味着它会逐步编译代码中的更改(这也是Eclipse不需要编译按钮的原因,因为它会在检测到更改时自动编译)。但Oracle的JDK不支持增量编译。
运行期String的字符串限制
String内部是以char数组的形式存储,数组的长度是int类型,那么String允许的最大长度就是Integer.MAX_VALUE了。又由于java中的字符是以16位存储的,因此大概需要4GB的内存才能存储最大长度的字符串。
int 是一个 32 位变量类型,取正数部分来算的话,他们最长可以有
2^31-1 =2147483647 个 16-bit Unicodecharacter
2147483647 * 16 = 34359738352 位
34359738352 / 8 = 4294967294 (Byte)
4294967294 / 1024 = 4194303.998046875 (KB)
4194303.998046875 / 1024 = 4095.9999980926513671875 (MB)
4095.9999980926513671875 / 1024 = 3.99999999813735485076904296875 (GB)
总结一下
1.Java中的字符串String最大长度,编译期如果是javac编译就是65534。如果绕过javac编译的限制,其最大长度可以达到u2类型变达的最大值65535。
2.Java中的字符串String最大长度运行期大约4G。
3.Eclise编译超过65534长度的字符串不报错,是Eclipse有自己的Java编译器。JDT优化为了StringBuilder的append。
4.Eclise使用自己的编译器。主要原因是JDT核心具有渐进式编译的能力,这意味着它会逐步编译代码中的更改(这也是Eclipse不需要编译按钮的原因,因为它会在检测到更改时自动编译)。但Oracle的JDK不支持增量编译。
4.Eclise使用自己的编译器。主要原因是JDT核心具有渐进式编译的能力,这意味着它会逐步编译代码中的更改(这也是Eclipse不需要编译按钮的原因,因为它会在检测到更改时自动编译)。但Oracle的JDK不支持增量编译。