性能文章>字符串字面量长度是有限制的>

字符串字面量长度是有限制的原创

2年前
719203

前言

偶然在一次单元测试中写了一个非常长的字符串字面量。

正文

在一次单元测试中,我写了一个很长的字符串字面量,大概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 bytesJava字符串的最大长度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 = 0xFFFF0xFFFF 是十进制的 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了(搞明白这个得感谢“杭州-半拍”的帮助)
分类:标签:
请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

不起眼,但是足以让你收获的JVM内存案例
今天的这个案例我觉得应该会让你涨姿势吧,不管你对JVM有多熟悉,看到这篇文章,应该还是会有点小惊讶的,不过我觉得这个案例我分享出来,是想表达不管多么奇怪的现象请一定要追究下去,会让你慢慢变得强大起来,
字符串字面量长度是有限制的
前言 偶然在一次单元测试中写了一个非常长的字符串字面量。 正文 在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 `java: constant
多次字符串相加一定要用StringBuilder而不用-吗?
今天在写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144): ``` package test; public c
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得
谨防JDK8重复类定义造成的内存泄漏
概述 如今JDK8成了主流,大家都紧锣密鼓地进行着升级,享受着JDK8带来的各种便利,然而有时候升级并没有那么顺利?比如说今天要说的这个问题。我们都知道JDK8在内存模型上最大的改变是,放弃了Perm
JDK13 GA发布:5大特性解读
JDK13 GA版本 5大新特性如下: 350: Dynamic CDS Archives 351: ZGC: Uncommit Unused Memory 353: Reimplement the
译:谁是 JDK8 中最快的 GC
我们都知道 OpenJDK8 有好几个垃圾回收算法,比如 ParallelGC,CMS,还有 G1,那么哪个才是最快的?如果 GC 算法从 Java8 中默认的 ParallelGC 切换到 G1 会
高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和