java中静态String的最大长度由什么决定的?

String最大长度

本文参考:

http://blog.csdn.net/roland_sun/article/details/46716965
http://denverj.iteye.com/blog/1210979
http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html

今天碰到一个字符串长度过长的报错,然后仔细去查了一下发现字符串常量的长度已经到达8万了,结果还没运行直接给我丢出来个长度过长的错,因此去查了一下资料为什么会出现这个错误。

  • 常量池
    java虚拟机为每个被装载的类维护一个常量池,比如说一个类
public class Test{
    public String = "asb";
}

这种常量,就会被记录到常量池中,在生成字节码class文件的时候,会放在COUNST_POOL中

  • 字节码
    字节码文件中,维护了一个有序集合——常量池,记录着类型所使用的所有常量信息

Entry Type Description

CONSTANT_Utf8 A UTF-8 encoded Unicode string

CONSTANT_Integer An int literal value

CONSTANT_Float A float literal value

CONSTANT_Long A long literal value

CONSTANT_Double A double literal value

CONSTANT_Class A symbolic reference to a class or interface

CONSTANT_String A String literal value

CONSTANT_Fieldref A symbolic reference to a field

CONSTANT_Methodref A symbolic reference to a method declared in a class

CONSTANT_InterfaceMethodref A symbolic reference to a method declared in an interface

CONSTANT_NameAndType Part of a symbolic reference to a field or method

其中记录String类型的常量池在:CONSTANT_String_info中,而这个里面记录的是UTF-8常量池的索引, CONSTANT_Utf8_info,即我们的最终信息是保存在这个常量池中的

  • 限制原因
    在CONSTANT_Utf8_info中,有一个记录bytes所代表的字符串的长度的变量,他的长度为2个字节,也就是说,使用的是16位的无符号整形来进行记录,因此最多可使用2^16 = 65536 - 1,但是在我的测试中,发现当字符串超过65534(不包含),也就是说length > 65534,就会报错了,然后继续去查,在说明中可以看到

represent constant string values. String content is encoded in modified UTF-8.

我们用个例子来分析一下吧
为了尽可能简单,这样写

public class Aa{

    public static void main(String[] args){

        String aaa ="aaa";

    }
}

很简单的一个例子
然后我们反编译一下class

javap -verbose Aa.class
Classfile /C:/Users/fin/Desktop/Aa.class
  Last modified 2017-7-31; size 265 bytes
  MD5 checksum aec0db218f2598d73cb809fad1e0eed5
  Compiled from "Aa.java"
public class Aa
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = String             #14            // aaa
   #3 = Class              #15            // Aa
   #4 = Class              #16            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               Aa.java
  #13 = NameAndType        #5:#6          // "<init>":()V
  #14 = Utf8               aaa
  #15 = Utf8               Aa
  #16 = Utf8               java/lang/Object
{
  public Aa();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String aaa
         2: astore_1
         3: return
      LineNumberTable:
        line 7: 0
        line 9: 3
}
SourceFile: "Aa.java"

上面就是我们得到的信息,可以看到第二个常量池指向14号常量池也就是我们的utf8常量池,因为我们的aaa很简单,因此记录的字节码一定也是重复的,所以很容易能从2进制的class文件中找到。

cafe babe 0000 0034 0011 0a00 0400 0d08
000e 0700 0f07 0010 0100 063c 696e 6974
3e01 0003 2829 5601 0004 436f 6465 0100
0f4c 696e 654e 756d 6265 7254 6162 6c65
0100 046d 6169 6e01 0016 285b 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 2956
0100 0a53 6f75 7263 6546 696c 6501 0007
4161 2e6a 6176 610c 0005 0006 0100 0361
6161 0100 0241 6101 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0021 0003 0004
0000 0000 0002 0001 0005 0006 0001 0007
0000 001d 0001 0001 0000 0005 2ab7 0001
b100 0000 0100 0800 0000 0600 0100 0000
0300 0900 0900 0a00 0100 0700 0000 2000
0100 0200 0000 0412 024c b100 0000 0100
0800 0000 0a00 0200 0000 0700 0300 0900
0100 0b00 0000 0200 0c

然后查看一下二进制文件,我们就查找所有0003的字段(因为我们字符串长度为3),并且前面要有01(CONSTANT_uft8_info中的tag标记),或者0100 03,找到两个结果,0100 0361 6161和一个01 0003 2829 5601 ,后者应该不可能了,只有可能是这个0100 0361 6161这个,因为a的ascii码对应于16进制就是61,因此长度最多记录到0xffff,但是经过测试后最大只能达到fffe,如果编译65535长度的字符串就会报错,但是实际上ffff并没有被使用到

  • 为什么只有65534
    在Android的官方Dex文件格式的文档中,对MUTF-8编码有如下描述:

1)MUTF-8使用1到3个字节对UTF-16字符进行编码;

2)对于数值为0的情况,使用两个字节对其进行编码(编码后的值为0xC0和0x80);

3)采用类似于c语言中的空字符串(NULL,单字节数值为0)作为字符串结尾的标志;

4)对于UTF-16码点范围在U+10000到U+10FFFF的情况(补充字符),数值对中的每一个数值采用3字节对其编码。也就是说,对于这种情况,表示一个字符总共需要使用6个字节。

按照我的理解,也就是说最后一位需要是空字符串,所以最大长度只能为65535 - 1,但是java的String不像C一样字符串结尾使用\0,但是这里确实找不到合理的解释,也希望看到这篇文章并且了解相关知识的能评论解答一下

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值