字符串长度限制问题
- 前言
- 分析
- 字符串常量池
- 运行时的字符串大小
- 总结
前言
通过阅读JVM规范和源码,我们可以知道,String无论是字面量定义的形式还是运行时生成的方式都是有限制的。
Javac(eclipse编译方式可能作了些修改)编译阶段,字面量定义的字符串形式需要小于65535,运行时阶段大概小于2^31,4个G左右。
分析
如图所示,先动态的输出10w个1,然后copy出来,以字面量的形式定义一个字符串s,然后输出,此时会报错。
报错原因为字符串过长,即编译阶段,字面量形式定义的字符串是会有限制的。
有小伙伴可能说为啥我的就没有报错呢,可能这里需要将编译方式修改为javac的方式1。
字符串常量池
通过阅读java虚拟机规范,我们可以知道字符串常量池的形式如下图表示:
首先,字符串常量池的实现和Map有着异曲同工直面,以哈希表和链表的方式进行实现。其中,tag是一个固定值8(常量池有很多种,每种都有特定数字进行表示)
而关于string_index则为索引值,指向相应的字符串序列,其结构可以用Constant_Utf8.info进行表示。其中,u2为无符号的2字节,2^15+…+ 2^0=65535。即通过字面量定义的字符串最为65534.
通过对编译时的代码进行调试,我们可以找到一个checkStringConstant
方法,在编译阶段,进入常量池的字符串会在这里做成判断,如果大于等于65535,会输出错误日志。
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;
}
}
运行时的字符串大小
当我们绕过编译器,来到运行时的阶段,我们的字符串大小的限制就远远超过65535了,我们可以看如下的代码,它的输出是没有问题的:
public static void main(String[] args) {
String s = "";
for (int i = 0; i < 100000; i++) {
s+=i;
}
System.out.println(s);
}
为什么呢?原来通过“+”运行,我们的编译器会优化成:
new StringBuilder().append("XX")...append("XX").toString()
再看看StringBuilder源码中的toString方法,它会调用String的构造方法public String(char value[], int offset, int count)
:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
关于该构造方法,代码如下所示:
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
看到这里,机制的小伙伴应该发觉原因了吧,其实在StringBuild阶段我们大概就可以猜出来了,因为count表示了字符串的字符个数,而这个count是有长度限制的,它用int进行表示,通过查看它的“装箱类”Integer
源码:
/**
* A constant holding the maximum value an {@code int} can
* have, 2<sup>31</sup>-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
我们可以知道它的最大值为2^31-1,这个数字的字符串大概有多大呢?
首先字符串在其底层,也是用一个char数组进行存储的,而char,一个字符大小相当于2个字节,16位。2^31-1个字符为:
2^31-1 * 16 / 8 /1024 /1024/1024 = 3.99....
大小约等于4G。
总结
字符串的大小限制问题,常见于前后端对接参数时,字符串传参而产生的问题。或者,在对接甲方接口时,你根本不知道它给你响应的东西到底有多个,也不做申明,这时总会产生莫名其妙的问题。