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

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

3年前
757105

前言

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

正文

在一次单元测试中,我写了一个很长的字符串字面量,大概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了(搞明白这个得感谢“杭州-半拍”的帮助)
点赞收藏
分类:标签:
since1986

爱编程、爱旅行、爱家人~

请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

JAVA使用CompletableFuture实现流水线并行处理,加速你的接口响应

JAVA使用CompletableFuture实现流水线并行处理,加速你的接口响应

讲透JAVA Stream的collect用法与原理,远比你想象的更强大

讲透JAVA Stream的collect用法与原理,远比你想象的更强大

没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!

没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!

【译】记一次数据库连接泄漏导致的响应迟缓

【译】记一次数据库连接泄漏导致的响应迟缓

【全网首发】微服务10:系统服务熔断、限流

【全网首发】微服务10:系统服务熔断、限流

【全网首发】MQ-消息堆积-JDK Bug导致线程阻塞案例分析

【全网首发】MQ-消息堆积-JDK Bug导致线程阻塞案例分析

5
0