字符串字面量长度是有限制的原创
前言
偶然在一次单元测试中写了一个非常长的字符串字面量。
正文
在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 java: constant string too long
,当时我就惊呆了,惊讶于平常写的字符串字面量竟然还有长度限制???(黑人问号)。然后我来了兴致,我倒要看看字符串字面量到底能写到多少个字符不报错。于是我试了大概10分钟左右,发现 65534
这个值是上限,超过了就会报 java: constant string too long
。
那么,这个限制在JDK中是在什么地方实现的呢,我Google了一下,发现了这几篇文章:Bug 212752 - Error when there is string bigged than 65534 bytes、Java字符串的最大长度、Java “constant string too long” compile error. Only happens using Ant, not when using Eclipse
我们先来看第一篇文章,在这篇文章中(实际上是个NetBeans的issues主题)有个人提到:
Tomas Zezula 2012-05-31 20:15:53 UTC
The compiler message is:
“constant string too long”See checkStringConstant(…) method in http://hg.openjdk.java.net/jdk7/tl/langtools/file/a72412b148d7/src/share/classes/com/sun/tools/javac/jvm/Gen.java
and
MAX_STRING_LENGTH in http://hg.openjdk.java.net/jdk7/tl/langtools/file/a72412b148d7/src/share/classes/com/sun/tools/javac/jvm/Pool.java
也就是在Oracle JDK的编译工具 Javac
内部,代码层面对字符串字面量长度做了限制,我们可以看看他说的几段代码(都是Java代码,Javac
本身也是用Java语言写的,不是C++):
// langtools-b9abf5c3d057\src\share\classes\com\sun\tools\javac\jvm\Gen.java
// com.sun.tools.javac.jvm.Gen
...
/** Check a constant value and report if it is a string that is
* too large.
*/
private void checkStringConstant(DiagnosticPosition pos, Object constValue) {
if (nerrs != 0 || // only complain about a long string once
constValue == null ||
!(constValue instanceof String) ||
((String)constValue).length() < Pool.MAX_STRING_LENGTH)
return;
log.error(pos, "limit.string");
nerrs++;
}
...
// langtools-b9abf5c3d057\src\share\classes\com\sun\tools\javac\jvm\Pool.java
// com.sun.tools.javac.jvm.Pool
/** An internal structure that corresponds to the constant pool of a classfile.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class Pool {
...
public static final int MAX_STRING_LENGTH = 0xFFFF;
...
}
// src/share/classes/com/sun/tools/javac/resources/compiler.properties
...
compiler.err.limit.string=\
constant string too long
...
通过上边代码可以看到 MAX_STRING_LENGTH = 0xFFFF
而 0xFFFF
是十进制的 65535
(但是实际上只写了 65534 个就报错了,不知道为什么差一个?),这里应当就是字符串字面量的限制的来源。这些代码整体怎么运作的,我并没有仔细看,不过按照提示信息能对的上来看,因该就是在这些逻辑里处理的字符串字面量长度的限制,也就是说字符串字面量长度是在编译器 Javac
代码逻辑层面加的限制。
再看第二篇文章 Java字符串的最大长度 文章的意思是,这个限制是在 class
文件格式层面限制的:
String内部是以char数组的形式存储,数组的长度是int类型,那么String允许的最大长度就是Integer.MAX_VALUE了。又由于java中的字符是以16位存储的,因此大概需要4GB的内存才能存储最大长度的字符串。不过这仅仅是对字符串变量而言,如果是字符串字面量(string literals),如“abc"、"1a2b"之类写在代码中的字符串literals,那么允许的最大长度取决于字符串在常量池中的存储大小,也就是字符串在class格式文件中的存储格式:
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
u2是无符号的16位整数,因此理论上允许的string literal的最大长度是2^16-1=65535。然而实际测试表明,允许的最大长度仅为65534,超过就编译错误了,有兴趣可以写段代码试试,估计是length还不能为0。
再看第三篇文章Java “constant string too long” compile error. Only happens using Ant, not when using Eclipse:这个是篇爆栈网的主题,题主大致问的是用Ant编译超长字符串字面量会爆出和我遇到的同样的问题,而用eclipse编译则没问题,我自己试了试,用eclipse编译,确实没有了 65534
这个限制:
public class Test {
public static void main(String[] args) {
String veryLargeLengthStringLiteral = "111111111111111YJ....这里省略N多个字符太长了文章放不下";
System.out.println(veryLargeLengthStringLiteral.length());
}
}
// 在Eclipse中能正常编译并输出 65549 这个代码换到 idea 里编译会直接报错
那这个又是怎么回事呢?其实这个我倒是能知道个大概,因为我以前就知道,Eclipse编译Java代码并不用 Javac
(可参考Which Java compiler is used by Eclipse?
)而是用的 JDT 也就是他们自己写的一个编译器,Eclipse能正常编译超过 65534
的字面量,那只能说明他们没在自己的编译器里做长度限制。
那么,第二篇文章的观点岂不是和第三篇有些相悖了?要是在 class
文件格式层面就做了限制,那么理论上Eclipse编译出来的应该完全跑不起来或者完全就不应该编译成功啊?这里我也不是很明白,留着以后知识盲区被修补了再继续了解吧,反正我现在更倾向于这个限制只是在编译器实现层面加的。
更新与勘误
2019-08-15 更新
后来我在知乎上关于这个主题进行了提问:Java中字符串字面量的65534限制和CONSTANT_Utf8_info的length有关系吗?
- 之所以JDT能编译过,只是因为JDT优化为了StringBuilder的append
{%asset_img 1.png%}
- 之所以限制是 65534 而不是 65535 只是因为 Javac 源码中做的限制是
((String)constValue).length() < Pool.MAX_STRING_LENGTH)
注意是<
而不是<=
, 小于65535那自然最多只能是65534了(搞明白这个得感谢“杭州-半拍”的帮助)