我说我精通字符串,面试官居然问我Java中的String有没有长度限制!?|附视频讲解

关于String有没有长度限制的问题,我以前单独写过一篇文章分析过,最近我又抽空回顾了一下这个问题,发现又有了一些新的认识。因而准备从新整理下这个内容。java

此次在以前那篇文章的基础上除了增长了一些验证过程外,还有些错误内容的修正。我此次在分析过程当中会尝试对Jdk的编译过程进行debug,而且会参考一些JVM规范等全方面的介绍下这个知识点。web

由于这个问题涉及到Java的编译原理相关的知识,因此经过视频的方式讲解会更加容易理解一些,视频我上传到了B站:https://www.bilibili.com/video/BV1uK4y1t7H1/。数组

String的长度限制

想要搞清楚这个问题,首先咱们须要翻阅一下String的源码,看下其中是否有关于长度的限制或者定义。ide

String类中有不少重载的构造函数,其中有几个是支持用户传入length来执行长度的:svg

public String(byte bytes[], int offset, int length)

能够看到,这里面的参数length是使用int类型定义的,那么也就是说,String定义的时候,最大支持的长度就是int的最大范围值。函数

根据Integer类的定义,java.lang.Integer#MAX_VALUE的最大值是2^31 - 1;学习

那么,咱们是否是就能够认为String能支持的最大长度就是这个值了呢?测试

其实并非,这个值只是在运行期,咱们构造String的时候能够支持的一个最大长度,而实际上,在运行期,定义字符串的时候也是有长度限制的。this

如如下代码:编码

String s = "11111...1111";//其中有10万个字符"1"

当咱们使用如上形式定义一个字符串的时候,当咱们执行javac编译时,是会抛出异常的,提示以下:

错误: 常量字符串过长

那么,明明String的构造函数指定的长度是能够支持2147483647(2^31 - 1)的,为何像以上形式定义的时候没法编译呢?

其实,形如String s = "xxx";定义String的时候,xxx被咱们称之为字面量,这种字面量在编译以后会以常量的形式进入到Class常量池

那么问题就来了,由于要进入常量池,就要遵照常量池的有关规定。

常量池限制

咱们知道,javac是将Java文件编译成class文件的一个命令,那么在Class文件生成过程当中,就须要遵照必定的格式。

根据《Java虚拟机规范》中第4.4章节常量池的定义,CONSTANT_String_info 用于表示 java.lang.String 类型的常量对象,格式以下:

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

其中,string_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是 CONSTANT_Utf8_info 结构,表示一组 Unicode 码点序列,这组 Unicode 码点序列最终会被初始化为一个 String 对象。

CONSTANT_Utf8_info 结构用于表示字符串常量的值:

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

其中,length则指明了 bytes[]数组的长度,其类型为u2,

经过翻阅《规范》,咱们能够获悉。u2表示两个字节的无符号数,那么1个字节有8位,2个字节就有16位。

16位无符号数可表示的最大值位2^16 - 1 = 65535。

也就是说,Class文件中常量池的格式规定了,其字符串常量的长度不能超过65535。

那么,咱们尝试使用如下方式定义字符串:

String s = "11111...1111";//其中有65535个字符"1"

尝试使用javac编译,一样会获得"错误: 常量字符串过长",那么缘由是什么呢?

其实,这个缘由在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的时候,就会致使编译失败。

这个地方你们能够尝试着debug一下javac的编译过程(视频中有对java的编译过程进行debug的方法),也能够发现这个地方会报错。

若是咱们尝试以65534个字符定义字符串,则会发现能够正常编译。

其实,关于这个值,在《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

运行期限制

上面提到的这种String长度的限制是编译期的限制,也就是使用String s= “”;这种字面值方式定义的时候才会有的限制。

那么。String在运行期有没有限制呢,答案是有的,就是咱们前文提到的那个Integer.MAX_VALUE ,这个值约等于4G,在运行期,若是String的长度超过这个范围,就可能会抛出异常。(在jdk 1.9以前)

int 是一个 32 位变量类型,取正数部分来算的话,他们最长能够有

2^31-1 =2147483647 个 16-bit Unicodecharacter

2147483647 * 16 = 34359738352 位
34359738352 / 8 = 4294967294 (Byte)
4294967294 / 1024 = 4194303.998046875 (KB)
4194303.998046875 / 1024 = 4095.9999980926513671875 (MB)
4095.9999980926513671875 / 1024 = 3.99999999813735485076904296875 (GB)

有近 4G 的容量。

不少人会有疑惑,编译的时候最大长度都要求小于65535了,运行期怎么会出现大于65535的状况呢。这其实很常见,如如下代码:

String s = "";
for (int i = 0; i <100000 ; i++) {
    s+="i";
}

获得的字符串长度就有10万,另外我以前在实际应用中遇到过这个问题。

以前一次系统对接,须要传输高清图片,约定的传输方式是对方将图片转成BASE6编码,咱们接收到以后再转成图片。

在将BASE64编码后的内容赋值给字符串的时候就抛了异常。

总结

字符串有长度限制,在编译期,要求字符串常量池中的常量不能超过65535,而且在javac执行过程当中控制了最大值为65534。

在运行期,长度不能超过Int的范围,不然会抛异常。

最后,这个知识点 ,我录制了视频(https://www.bilibili.com/video/BV1uK4y1t7H1/),其中有关于如何进行实验测试、如何查阅Java规范以及如何对javac进行deubg的技巧。欢迎进一步学习。