Java中的String的长度限制问题

Java 中的 String 有没有长度限制?

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

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

String 的长度限制

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

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

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

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

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

那么,我们是不是就可以认为 String 能支持的最大长度就是这个值了呢?

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

如以下代码:

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 的范围,否则会抛异常。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Micrle_007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值