String字符串的最大长度是多少?
在学习和开发过程中,我们能够记住int等基本数据类型的长度,但是类似String等数据的长度的讨论少之又少。那么对于 String 类型,它到底有没有长度限制呢?
很多网上的文章说,关于 String 的长度限制要从编译时限制和运行时限制两方面考虑,Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示,其中null 值使用两个字节来表示,因此只剩下 65536- 2 = 65534个字节。确实如此吗?我们来测试一下,实践才是检验真理的唯一标准!
public class test {
public static void main(String[] args) {
String s = "ddd...dddd" //这里经过复制粘贴得到65535长
System.out.println(s.length());
}
}
可以看到,65535长度的字符串确实超出限制了,如果是65534的结果如下
此时可以成功创建字符串
JVM规范对常量池有所限制。常量池中的每一种数据项都有自己的类型,Java中的UTF-8编码的Unicode字符串在常量池中CONSTANT_Utf8类型表示
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
length 的类型是u2,u2是无符号的16位整数,因此理论上允许的的最大长度是2^16-1=65535
但是如果我们是采用下面这两种的方式生成字符串:
StringBuilder sb = new StringBuilder();
char[] cs = new char[65535];
for(int i = 0; i < 65535; i++){
sb.append('d');
cs[i] = 'd';
}
String s = sb.toString();
System.out.println(s.length());
String s2 = new String(cs);
System.out.println(s2.length());
那么结果就不一样了,它们是可以跑出结果的,如下图
其实这样在非字符串常量的堆上生成的字符串只会受到运行期限制,此时的字符串最大长度取决于堆的大小,理论上说,如果是Integer.MAX_VALUE长度的字符串,在JDK8下占据的字节数为2*Integer.MAX_VALUE,也就是4GB。一般实际情况由于各种因素影响,会比MAX_VALUE这个值要小一点。
JVM内存区域中的字符串常量池
其实,这个问题来源于字符串常量池在内存中的位置区域,《深入理解Java虚拟机》P46中有一句话说到:”JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出。”另外,在P62可以看到,字符串常量池被移动到了Java堆之中,也就是说,JDK6和JDK7的字符串常量池已经在不同位置,就如下两图所示:
JDK6:
JDK7:
本次实验的时候采用的是JDK8,故字符串常量池是在堆中的!!这也就说明了为什么超过65534的字符串还能够在常量池中出现。因此,此时的字符串最大长度取决于堆的大小,理论上说,如果是Integer.MAX_VALUE长度的字符串,在JDK8下占据的字节数为2*Integer.MAX_VALUE,也就是4GB。一般实际情况由于各种因素影响,会比MAX_VALUE这个值要小一点。
简单总结
String 的长度是有限制的。在编译期间的字符串长度受到Class类文件中的CONSTANT_Utf8限制,u2是无符号的16位整数,加上null 值使用两个 字节来表示,因此只剩下 65536- 2 = 65534个字节。
运行期间字符串长度与字符串常量池所在位置有关,JDK7(不包括)之前字符串常量池在方法区(永久代),因此可以用-XX:MaxPerSize限制,注意这里限制的长度是给定最大内存的1/2(因为一个char占两个字节),移动到堆上,此时字符串的最大长度就只跟堆的大小有关了
正确的说法应该是,字符串常量池不管是在哪里都会受到编译时限制,最大长度就是65534,运行时限制主要限制的是一些在堆上生成的字符串和字符数组
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
后续补充
后来又继续稍微看了看《深入理解Java虚拟机》关于类文件结构中的常量池部分P220-221,发现书中说了CONSTANT_Utf8_info限制了字符串内容的长度,但是65535是采用UTF-8缩略编码(其中ASCII码内的字符char占用一个字节而非两个)的字节数,所以最大字节数就是最大字符串长度