java基础---String长度限制透彻解析

文章目录

  • 前言
  • 一、String源码分析
  • 二、编译期常量池限制
    • 什么是码点?
  • 三、运行时期限制
  • 总结:

前言

在我们工作和学习中基本离不开Sring的数据类型,虽然我们经常接触到它但是一般很少会有人关心String是否会像int,long一样有范围限制。如果有的话又会是多少呢?首先我们直接揭晓答案,String类型是有长度限制的并且它在变异环境和运行时环境限制是不同的。下面我们会详细展开分析。

一、String源码分析

首先我们进入到String源码中看看是否能找到一些有用的线索,是否有直接对长度的限制或定义。String类中有很多重载的构造函数,其中有几个是支持用户传入length来定义长度的。可以看到,这里面的参数length是使用int类型定义的,那么也就是说,String定义的时候,最大支持的长度就是int的最大范围值。那么,我们是不是就可以认为String能支持的最大长度就是,java.lang.Integer#MAX_VALUE的最大值是2^31 - 1这个值了呢?事情当然没有那么简单,这个值只是在运行期,我们构造String的时候可以支持的一个最大长度,而实际上,在编译期,定义字符串的时候也是有长度限制的。
java基础---String长度限制透彻解析_第1张图片
下面我就事实说话,这个字符串是我随便输出到控制台长度为10万的一个字符串。此时此刻它远远没有达到2^31 - 1但是却编译报错提示我们常量字符串过长。这又是为什么呢?
java基础---String长度限制透彻解析_第2张图片
我们都知道,如我们所定义的“0123456789…”俗称字面量,在编译之后会以常量的形式进入到Class常量池。而常量池又是有所限制的,以至于间接性的限制了编译时期String的长度。

二、编译期常量池限制

我们知道,javac是将Java文件编译成class文件的一个命令,那么在Class文件生成过程中,就需要遵守一定的格式。根据 《Java虚拟机规范》中第4.4章节常量池的定义,CONSTANT_String_info 用于表示 java.lang.String 类型的常量对象,格式如下:
java基础---String长度限制透彻解析_第3张图片
地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.3

Stirng 长度之所以会受限制,是因JVM规范对常量池有所限制。常量池中的每一种数据项都有自己的类型。其中,string_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是 CONSTANT_Utf8_info 结构,表示一组 Unicode 码点序列,这组 Unicode 码点序列最终会被初始化为一个 String 对象。
CONSTANT_Utf8的数据结构如下:

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

什么是码点?

java基础---String长度限制透彻解析_第4张图片
上图出自:https://www.cnblogs.com/benbenalin/p/6921553.html

继续回到CONSTANT_Utf8_info其中,length则指明了 bytes[]数组的长度,其类型为u2,通过翻阅《规范》,我们可以获悉。u2表示两个字节的无符号数,那么1个字节有8位,2个字节就有16位。16位无符号数可表示的最大值位2^16 - 1 = 65535。也就是说,Class文件中常量池的格式规定了,其字符串常量的长度不能超过65535。

//65535个字符,编译报错
String s = "111111111......";

//65534个字符,编译通过
String s1 = "11111111......";

下图字符串s中包含65535个1编译出错,当我删除掉一个1时编译通过。这也直接验证了String编译时期的长度限制为65534
java基础---String长度限制透彻解析_第5张图片
其实,这个原因在javac的代码中是可以找到的,在Gen类中有如下代码:

private 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;

    }
}

代码中可以看出,当参数类型为String,并且长度大于等于65535的时候,就会导致编译失败。有兴趣的可以自己下一份源码翻阅下。其实,关于这个值,在《Java虚拟机规范》也有过说明:

if the Java Virtual Machine code for a method is exactly 65535 bytes long and ends with an instruction that is 1 byte 
long, then that instruction cannot be protected by an exception handler. A compiler writer can work around this bug by 
limiting the maximum size of the generated Java Virtual Machine code for any method, instance initialization method, or 
static initializer (the size of any code array) to 65534 bytes

如果方法的Java虚拟机代码恰好是65535字节长,并且以一条1字节长的指令结束,那么该指令就不能被异常处理程序保护。编译器编者可以通过将为任何方
法、实例初始化方法或静态初始化器(任何代码数组的大小)生成的Java虚拟机代码的最大大小限制为65534字节来解决这个问题

三、运行时期限制

上面我们提到在编译时期定义String s = “xxx”;最大为65534,那运行时期如果继续给这个变量追加字符串进去是否会报错呢?答案是不会,这里我就不进行演示了。那么它的边界限定又会是多少呢,总不能是无限大吧。当然不可能它的最大值就是我们前文提到的那个Integer.MAX_VALUE ,这个值约等于4G,在运行期,如果String的长度超过这个范围,就可能会抛出异常。(在jdk 1.9之前)

(2^31-1)*2*16/8/1024/1024/1024 = 4GB

所以在最坏的情况下,一个最大的字符串要占用4GB的内存。如果你的虚拟机不能分配这么多内存的话,会直接报错的。JDK9以后对String的存储进行了优化。底层不再使用char数组存储字符串,而是使用byte数组。对于LATIN1字符的字符串可以节省一倍的内存空间。

具体可以通过下图了解:
java基础---String长度限制透彻解析_第6张图片
地址:https://www.cnblogs.com/flydean/p/jdk9-string-compact.html

总结:

字符串有长度限制,在编译期,要求字符串常量池中的常量不能超过65535,并且在javac执行过程中控制了最大值为65534。在运行期,长度不能超过Int的范围,否则会抛异常。

你可能感兴趣的:(java基础,java,字符串,jdk,jvm)