String最大长度是多少?涉及知识面太多,不要错过!

开发 前端
当你看到这个问题“String长度限制是多少”时是不是感觉很无聊?的确,这就是我第一眼看到时的感觉。

[[397376]]

本文转载自微信公众号「程序新视界」,作者二师兄。转载本文请联系程序新视界公众号。  

前言

当你看到这个问题“String长度限制是多少”时是不是感觉很无聊?的确,这就是我第一眼看到时的感觉。

但当深入追踪该问题时,才发现String的长度限制本身的意义并不重要,重要的是在此过程中会将大量知识点串联起来,简直是一个完美的问题。难怪在高阶段的面试中会出现类似的问题。

本篇文章就来带大家追寻String长度的限制,需要提醒读者的是,结论并不重要,重要的是分析的过程,以及涉及到的知识储备。比如,String的底层实现、int类型的范围、《Java虚拟机规范》、Java编译器源码实现等大量知识点。

String源码追踪

要看String类的长度限制,肯定要先从String的源码实现看起,这里就以目前使用最多的JDK8为例来进行说明。JDK9及以后String的底层实现有所变化,大家可参考《JDK9对String字符串的新一轮优化》一文。

我们都知道,String类提供了一个length方法,我们是否可以直接通过这个方法得知String的最大长度?

  1. /** 
  2.  * Returns the length of this string. 
  3.  * The length is equal to the number of <a href="Character.html#unicode">Unicode 
  4.  * code units</a> in the string. 
  5.  * 
  6.  * @return  the length of the sequence of characters represented by this 
  7.  *          object. 
  8.  */ 
  9. public int length() { 
  10.     return value.length; 

这里文档并没有说明最大长度是多少,但我们可以从返回的结果类型得知一些线索。结果类型为int,也就是说int的取值范围便是限制之一。

如果你知道int在正整数部分的取值范围为2^31 -1那很好,如果不知道,可以查看对应的包装类Integer:

  1. public final class Integer extends Number implements Comparable<Integer> { 
  2.     /** 
  3.      * A constant holding the minimum value an {@code int} can 
  4.      * have, -2<sup>31</sup>. 
  5.      */ 
  6.     @Native public static final int   MIN_VALUE = 0x80000000; 
  7.  
  8.     /** 
  9.      * A constant holding the maximum value an {@code int} can 
  10.      * have, 2<sup>31</sup>-1. 
  11.      */ 
  12.     @Native public static final int   MAX_VALUE = 0x7fffffff; 
  13.     // ... 

无论MIN_VALUE和MAX_VALUE的值或注释都说明了int的取值范围。此时计算一下String的最大长度应该是:

  1. 2^31 - 1 = 2147483647 

回到length方法,我们看到length的值是通过是value获得的,而value在JDK8中是以char数组实现的:

  1. public final class String 
  2.     implements java.io.Serializable, Comparable<String>, CharSequence { 
  3.     /** The value is used for character storage. */ 
  4.     private final char value[]; 
  5.     // ...    

Java中内码(运行内存)中的char使用UTF16的方式编码,一个char占用两个字节。所以,还需要从将上面计算的值乘以2。

此时的计算公式为:

  1. 2^31-1 =2147483647 个16-bit Unicodecharacter 
  2.  
  3. 2147483647 * 2 = 4294967294 (Byte) 
  4.   
  5. 4294967294 / 1024 = 4194303.998046875 (KB) 
  6.   
  7. 4194303.998046875 / 1024 = 4095.9999980926513671875 (MB) 
  8.   
  9. 4095.9999980926513671875 / 1024 = 3.99999999813735485076904296875 (GB) 

也就是说最大字符串占用内存空间约等于4GB。但此时,如果你声明一个长度为10万的字符串,你会发现编译器会抛出异常,提示信息如下:

错误: 常量字符串过长

不是说好的21亿吗?怎么10万个就异常了呢?其实这个异常是由编译期的限制决定的。

字符串常量池的编译期限制

了解过JVM虚拟机的朋友肯定知道,当通过字面量进行字符串声明时,在编译之后会以常量的形式进入到Class常量池。

  1. 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长度的字符串:

  1. String s = "8888...8888";//其中有65535万个字符"8" 

编译器还会抛出同样的异常。这又是为什么呢?

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

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

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

  1. /** Check a constant value and report if it is a string that is 
  2.  *  too large. 
  3.  */ 
  4. private void checkStringConstant(DiagnosticPosition pos, Object constValue) { 
  5.     if (nerrs != 0 || // only complain about a long string once 
  6.         constValue == null || 
  7.         !(constValue instanceof String) || 
  8.         ((String)constValue).length() < Pool.MAX_STRING_LENGTH) 
  9.         return
  10.     log.error(pos, "limit.string"); 
  11.     nerrs++; 

其中Pool.MAX_STRING_LENGTH的定义如下:

  1. public class Pool { 
  2.     public static final int MAX_STRING_LENGTH = 0xFFFF; 
  3.     //... 

再次尝试声明一个长度为65534的字符串,会发现可以正常编译了。此时,可以得出结论,在编译期字符串的最大长度为65534。

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

运行期的长度限制

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

  1. public String(char value[], int offset, int count) { 
  2.    // ... 

其中参数count就是字符串的最大长度。此时的计算与前面的算法一致,这里先转换为bit,然后再转换为GB:

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

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

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

小结

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

 

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

 

责任编辑:武晓燕 来源: 程序新视界
相关推荐

2020-11-10 13:47:29

String源码长度限制

2022-02-17 10:56:33

Redis数据系统

2009-12-15 17:19:23

架构师梁远华聚聚呀

2020-06-15 08:25:35

Linux 系统 数据

2021-11-07 07:51:01

JavaString字符串

2011-06-23 08:50:46

JavaAndroidOracle

2022-05-20 15:27:41

React工具Vue

2010-10-08 14:45:43

mysql中int

2020-07-21 08:14:13

TypeScrip

2020-12-16 15:37:19

Python编程语言开发

2023-04-11 16:31:10

开发React 库Web

2021-10-08 08:00:00

Java开发功能

2013-05-31 15:57:59

Windows 8.1

2020-07-09 07:37:06

数据库Redis工具

2021-03-08 21:57:29

手机科技数码

2023-10-20 21:16:33

物联网通讯线

2023-08-11 08:19:39

JavaStream API

2020-11-17 16:22:45

开源工具报表

2020-08-17 17:22:34

VSCode插件开发编码
点赞
收藏

51CTO技术栈公众号