字符串长度的判断,字符串长度是指

  

  前言   

  

  看到“弦的长度极限是多少”这个问题,你会觉得无聊吗?的确,这是我第一次看的时候的感受。   

  

  但当问题深入追溯,发现字符串本身长度限制的意义并不重要。重要的是在这个过程中大量的知识点会串联起来,这是一个完美的问题。难怪高层面试也会出现类似的问题。   

  

  本文将带你追寻字符串长度的极限。需要提醒读者的是,结论不重要,重要的是分析过程和涉及的知识储备。比如String的底层实现,int类型的范围,《Java虚拟机规范》,Java编译器源代码实现等诸多知识点。   

  

  字符串溯源依赖于String类的长度限制,所以首先要看String的源代码实现。这里以目前使用最多的JDK8为例进行说明。在JDK9和更高版本中,String的底层实现发生了变化。请参考文章《JDK9对String字符串的新一轮优化》。   

  

  众所周知,String类提供了一个长度方法。通过这个方法可以直接知道字符串的最大长度吗?   

  

  /** *返回该字符串的长度。*长度等于字符串中a href=' character . html # unicode ' unicode *代码单元/a的数量。* * @返回由这个*对象表示的字符序列的长度。*/public int length() {返回值.长度;}这里的文档没有说最大长度是多少,但是我们可以从返回的结果类型中得到一些线索。结果是int,也就是说int的范围是限制条件之一。   

  

  如果你知道int在正整数部分的取值范围是2 ^ 31-1,那太好了。如果不知道,可以查看相应的包装类整数:   

  

  public final class Integer extends Number实现ComparableInteger { /** *保存{@code int}可以*拥有的最小值的常数,-2sup31/sup。*/@ Native public static final int MIN _ VALUE=0x 80000000;/** *保存{@code int}可以*拥有的最大值的常数,2sup31/sup-1。*/@ Native public static final int MAX _ VALUE=0x 7 fffffff;//.}无论MIN_VALUE和MAX_VALUE的取值或注释,都说明了int的取值范围。此时,计算字符串的最大长度应为:   

  

  2 31-1=2147483647回到length方法,我们看到length的值是作为一个值获得的,该值在JDK8中实现为一个char数组:   

  

  public final类String实现java.io.Serializable,ComparableString,CharSequence { /**该值用于字符存储。*/private最终字符值;//.} Java内部代码(运行内存)中的char采用UTF16编码,一个char占用两个字节。因此,还需要将上述计算值乘以2。   

  

  这时,计算公式是:   

  

  31-1=2147483647 16位Unicode字符2147483647 * 2=4294967294(字节)/1024=49496724.478478476一百二十四(MB)=4095.4771875/1024=3。(GB)也就是说,最大字符串占用的内存空间约为4 GB.40875.08000000844但此时,如果声明一个长度为100,000的字符串,你会发现编译器会抛出一个异常,提示如下:   

  

  错误:常量字符串太长。你不是说21亿吗?10万为什么不正常?实际上,这个异常是由编译时间的限制决定的。   

  

  字符串池的编译时限制。了解过JVM虚拟机的朋友一定知道,字符串声明用字面量的时候,编译后会输入Cl作为常量。   

ass常量池。

  

String s = "程序新视界";而常量池对String的长度是有限制的。常量池中的每一种数据项都有自己的类型。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。

  

在《Java虚拟机规范》中可以看到对String是通过CONSTANT_String_info来定义的。

  

  

可以看到“string_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构”。

  

继续看对CONSTANT_Utf8_info的定义:

  

  

length则指明了bytes<>数组的长度,类型为u2。同样是在《Java虚拟机规范》中可以找到对u2的定义:

  

  

u2表示两个字节的无符号数,1个字节有8位,2个字节就有16位。因此,u2可表示的最大值为2^16 - 1= 65535。

  

到这里,已经得出了第二个限制,也就是Class文件中常量池的格式规定了,其字符串常量的长度不能超过65535。

  

此时,如果尝试通过字面量声明一个65535长度的字符串:

  

String s = "8888...8888";//其中有65535万个字符"8"编译器还会抛出同样的异常。这又是为什么呢?

  

这个问题我们同样可以从《Java虚拟机规范》(4.7.3节)中找到答案:

  

  

原来是为了弥补早期设计时的一个bug,“长度刚好65535个字节,且以1个字节长度的指令结束,这条指令不能被异常处理器处理”,因此就将数组的最大长度限制到了65534了。

  

如果你能够查看JVM中编译器部分的源码,可以在Gen类中看到对此限制的代码实现:

  

/** 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; //...}再次尝试声明一个长度为65534的字符串,会发现可以正常编译了。此时,可以得出结论,在编译期字符串的最大长度为65534。

  

我们知道,Java是区分编译期和运行期的,那么在运行期是否有长度限制呢?

  

运行期的长度限制String运行期的限制主要体现在String的构造函数上。String的一个构造函数如下:

  

public String(char value<>, int offset, int count) { // ...}其中参数count就是字符串的最大长度。此时的计算与前面的算法一致,这里先转换为bit,然后再转换为GB:

  

(2^31-1)*16/8/1024/1024/1024 = 4GB也就是说,运行时理论上可以支持4GB大小的字符串,超过这个限制就会抛出异常的。JDK9对String的存储进行了优化,底层使用byte数组替代了char数组,对于纯Latin1字符来说可以节省一半的空间。

  

当然,这个4GB的限制是基于JVM能够分配这么多可用的内存的前提下的。

  

小结通过上述的分析,可以得出结论:第一,在编译期字符串的长度不能超过65534;第二,在运行期,字符串的长度不能超过2^31-1,占用内存(4GB)不能超过虚拟机所分配的最大内存。

  

结论很简单,但本篇文章分析时所使用的知识和思路你学到了吗?如果没有,赶紧补一补吧。

相关文章