字符串String的最大长度

4次阅读

共计 3602 个字符,预计需要花费 10 分钟才能阅读完成。

微信公众号:51 码农网
专业编程问答社区
www.51manong.com

开篇三个问题

作为 Java 的程序员,不知道在 Java 代码中定义了多少个字符串,可是看看下面 3 个问题。你是否认真思考过?是否动手实践过?

1.Java 中的字符串 String 的最大长度是多少?
2.Eclipse 使用哪种 Java 编译器?
3. 为何 Eclipse 要出自己的编译器?

对于字符串可以承受的最大长度,要分为 2 个阶段,一个是编译时期(也就是你代码定义了一个 String 字符串,String s= “xiaohu”),一个是运行时期(指在程序运行过程中)

编译期 String 字符串的限制

我们都知道 JVM 里面是包含常量池的,(是一种对字符串的性能优化,不用反复创建新的字符串了)当我们使用字符串字面量直接定义 String 的时候,是会把字符串在常量池中存储一份的。常量池中的每一项常量都是一个表,都有自己对应的类型。Java 中的 UTF- 8 编码的 Unicode 字符串在常量池中以 CONSTANT_Utf8_info 类型表, 结构如下:

CONSTANT_Utf8_info 型常量的结构
类型 名称 数量
u1 tag 1
u2 length 1
u1 bytes length

u2 类型的 length 的值就表明了这个 UTF- 8 编码字符串长度是多少字节。所以 CONSTANT_Utf8_info 型常量对应的最大长度也就是 java 中 UTF- 8 编码的字符串的长度,顺便提一下 Class 文件中的方法和字段也是引用 CONSTANT_Utf8_info 型常量来描述名称的。u2 是无符号的 16 位整数,因此理论上允许的的最大长度是 2^16-1=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 写的。

/** 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++;
}
...

在看看 Pool.MAX_STRING_LENGTH

public class Pool {

    ...
    
    public static final int MAX_STRING_LENGTH = 0xFFFF;
    
    ...
}

通过上边代码可以看到 MAX_STRING_LENGTH = 0xFFFF 而 0xFFFF 是十进制的 65535。但是上面我们得出的结果是 Javac 编译下最大长度是 65534,是因为 Javac 源码中做的限制是((String)constValue).length() < Pool.MAX_STRING_LENGTH) 注意是 < 而不是 <=,小于 65535 那自然最多只能是 65534 了。

但是 U2 类型能表达的最大值是 65535。上面 65535 个长度的字符串在 javac 下报错了是受到了 javac 编译器的限制了。如果你在上面 65534 长度生成的 TestStr.class 中手动在添加一个字符串 (注意是在 javac 编译后的 class 文件中添加) 是可以得到 65535 长度的结果。

总结一下:在 Javac 编译器下,字符串 String 的最大长度限制也即是 U2 类型所能表达的最大长度 65534。避开 javac 最大长度是 65535?

Eclise 的 JDT 编译器下 String 的长度

Eclipse 有自己的 Java 编译器,称为 [JDT Core] [1](org.eclipse.jdt.core)。并不是用的 javac 编译器。
创建一个测试类

public class TestStr {public static void main(String[] args) {
             String LongStr ="aaaa..."// 一共 65540 个 a
             System.out.println(LongStr.length());
     }
}

发现 Eclipse 执行可正常执行。这肯定是 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 的内存才能存储最大长度的字符串。

总结一下

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 不支持增量编译。

这篇文章特别感谢 since1986、Holis
参考:https://since1986.github.io/d…
参考:https://juejin.im/post/5d5365…
微信公众号:51 码农网

正文完
 0