java中的String可以有多长?

参考链接:     https://www.cnblogs.com/ibelieve618/p/6380328.html

https://www.cnblogs.com/htyj/p/8337209.html     https://blog.csdn.net/qq_36761831/article/details/99943600?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242

结论:

1、在编译期以字面量(字面量就是数据/数值,例如:1234,true,”abc”,’中’)形式放在java内存的栈中,以字节的个数代表String的长度,如果是拉丁字母,最长为65534(因为javac源码有个异常,不能为65535),如果含有中文(中文在utf-8中一般占三个字节),差别比较大,最大为65535。

2、存储的是数组和对象,凡是new建立的都是在堆中,理论上String最大长度为Integer.MAX_VALUE(2^31-1),但是有些VMs(虚拟机)需要保留一些空间给头信息,所以此时最大为Integer.MAX_VALUE-8,随意堆上的String的长度尽量小于Integer.MAX_VALUE-8。

 

概念:

栈内存:栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。

 堆内存:存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。

 

那么String在栈和堆中都是以什么样的形式存在呢?

例如:关于String str = "abc"的内部工作。
Java内部将此语句转化为以下几个步骤: 
(1)先定义一个名为str的对String类的对象引用变量:String str; 
(2)在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着在栈中创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。 
(3)将str指向对象o的地址。 
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用! 
为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。 
            String str1 = "abc"; 
            String str2 = "abc"; 
            System.out.println(str1==str2); //true 
注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。 
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。 
我们再来更进一步,将以上代码改成: 
            String str1 = "abc"; 
            String str2 = "abc"; 
            str1 = "bcd"; 
            System.out.println(str1 + "," + str2); //bcd, abc 
            System.out.println(str1==str2); //false 
这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。 
事实上,String类被设计成为不可改变(final)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。 
再修改原来代码: 
            String str1 = "abc"; 
            String str2 = "abc"; 
            str1 = "bcd"; 
            String str3 = str1; 
            System.out.println(str3); //bcd 
            String str4 = "bcd"; 
            System.out.println(str1 == str4); //true 
str3这个对象的引用直接指向str1所指向的对象(注意,str3并没有创建新对象)。当str1改完其值后,再创建一个String的引用str4,并指向因str1修改值而创建的新的对象。可以发现,这回str4也没有创建新的对象,从而再次实现栈中数据的共享。 
我们再接着看以下的代码。 
            String str1 = new String("abc"); 
            String str2 = "abc"; 
            System.out.println(str1==str2); //false 
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 
            String str1 = "abc"; 
            String str2 = new String("abc"); 
            System.out.println(str1==str2); //false 
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。 
以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。 
6. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。 
7. 结论与建议: 
(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。 
(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。 
(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。 
(4)由于String类的final性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

 

 

正文:如下图,两者的String长度最长都是多少呢?

栈内存中:

当我们使用字符串字面量直接定义String的时候,是会把字符串在常量池中存储一份的。那么上面提到的65534其实是常量池的限制。常量池中的每一种数据项也有自己的类型。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。

我们使用字面量定义的字符串在字节码class文件中,是使用CONSTANTUtf8info存储的,而CONSTANTUtf8info中有u2 length;表明了该类型存储数据的长度。u2是无符号的16位整数,因此理论上允许的的最大长度是2^16=65536。所以u1的bytes理论上最多能存储65536个字节(下文解释其实不是65536)。其运行时加载到jvm内存的方法区中的常量池内,(如果常量池太小,longString放不下就会出现oom,常量池大小可以控制,一般不会出现常量池太小的问题)

  
 

但是在测试时发现,传入65535个a会报错Error:java:constant string too long,查看编译器源码(javac源码)发现如下代码,所以这是编译器源码的一个bug,对于拉丁字符(Latin)由于一个字符占用一个字节,就会触发下图的代码,导致最多能存储65534个字节。

如果存储的字符串含有中文,中文在utf-8中一般占据三个字节,所以不会触发上图的方法,而是先将中文转成utf-8的编码,如下图。此时的代码就是正确的,最多能存储65535个字节。

所以在栈内存中String的长度总结如下:

 

 

堆内存

new的String存放在堆内存中,在读取文件时,其实就是new 一个String,这个String的背后其实就是一个char的数组,这个数组的大小受虚拟机指令newArray的限制,这个newArray接受的是一个整型的参数,理论上最大为Integer.MAX_VALUE,但是有些VMs(虚拟机)需要保留一些空间给头信息,所以此时最大为Integer.MAX_VALUE-8,随意堆上的String的长度尽量小于Integer.MAX_VALUE-8。

 

总结:

 

 

本章回顾:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HMP*

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

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

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

打赏作者

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

抵扣说明:

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

余额充值