String类的创建和存储以及和StringBuilder、StringBuffer的区别

本文详细解析了Java中String对象的三种创建方式及其在内存中的表现形式,包括直接赋值、new关键字及字符串拼接,并探讨了这些操作对常量池的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 String不属于8种基本数据类型,它是种特殊对象,所以未初始化之前为null。String能通过①直接赋值字符串常量(String s = "a";))或者 ②new关键字(String s = new String("a");)或者 ③字符串常量拼接(String s = "a" + "b";或者 String a="a";String s = a +"b";)来创建String对象 。这三种创建方式在内存中产生的对象也会有区别,下面我们先来看看第①种方式。

在介绍第一种创建方式之前,先说说常量池的概念,JVM会为基本数据类型、String类型还有类,字段和方法等的符号引用创建和维护一个常量池(constant_pool)。常量池中的CONSTANT_String_info表是用来存放String类型常量信息的,表结构如下:

类型名称数量
u1tag1
u2index1

tag表示常量类型的标志位,而index是指向字符串字面量(存在常量池的CONSTANT_Utf8_info)的索引,CONSTANT_Utf8_info主要是存储类名、字段名、方法名以及相关的描述符,还有字符串字面量,它们都是用UTF-8缩略编码来表示的字符串,CONSTANT_Utf8_info的表结构如下:

类型名称数量
u1tag1
u2length1
u1byteslength

length的长度是u2即两个字节,可表达的最大值是2^16-1(65535),由于bytes是u1长度,所以CONSTANT_Utf8_info中可表示的字符串长度可达65535个字节,大概21845(65535/3:UTF-8是6个字符即3个字节,如\u0001)个汉字,如果超过最大长度将会无法编译(Error:常量字符串过长)。


前面介绍了String类型字符串常量的存储,接下来分析一下创建过程,当执行 String s = "a";的赋值时,JVM会去常量池中判断(equals())是否存在字符串对象 "a" ,如果不存在,则在常量池里创建字符串对象 "a" ,然后将引用s指向 "a",如果存在则无需创建。在常量池中创建的 字符串对象 "a"是可以被共享的,看下面的例子:

String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); //true

我们知道==是用来判断两个字符串对象的引用地址是否相等,结果表明 s1和s2的引用是相同的,都是指向常量池中的 "abc"。所以String s = "a";只创建了一个对象。


如果通过第②方式,当执行 String s = new String("a");时,JVM也会去常量池中找是否存在字符串对象"a",如果没有则创建字符串对象"a",接着就在内存堆里new一个字符串对象"a",注意:这个堆中的字符串对象"a"不同于常量池中的字符串对象,如果常量池中存在字符串对象"a",则直接在堆中创建字符串对象"a",并将引用s指向堆中创建的新对象。所以当程序运行时常量池中不存在字符串对象"a"的话,本次创建过程生成了两个字符串对象,不然的话就是一个字符串对象。现在来看一下下面的例子:

String s1 = "abc";
String s2 = new String("abc");
String s3 = new String("abc");

System.out.println( s1 == s2);  //false
System.out.println( s2 == s3);  //false



因为 s1指向常量池中的字符串对象,而s2指向的是堆中的字符串对象,两者的引用明显不一样,s2和s3分别指向堆中的两个不同的字符串对象。

现在来看看第③中字符串对象的创建方式,在编译String s = "a" + "b";时就已经在常量池中自动创建了"ab"对象,对于在编译期能确定的字符串拼接会被自动创建,而像String a="a";String s = a +"b";这种拼接则不会自动创建,因为String s =a +"b";中的a变量是不确定的,除非变量a是常量能在编译期确定其值,如:

    public class test1 {
        public final String a = "a";

        public void method1() {
            String s = a + "b";
        }
    }}

而下面的例子也是不能自动创建字符串对象只有到运行时:

    public class test2 {
        public final String a;

        test2() {
            a = "a";
        }

        public void method1() {
            String s = a + "b";
        }
    }

所以对于:

String a="a";
String s = a +"b";

就不等同于:

String s = “a” +"b";

而是等同于:

StringBuilder builder = new StringBuilder ();  
builder.append(a);  
builder.append("b");  
String s = builder.toString();  

String s = a + "b"; 这个创建过程中产生了(1).常量池中的字符串对象"b"(假设常量池中不存在字符串对象"b"的话),(2)堆中的字符串对象"ab" (3)临时对象builder

    public String toString() {
        // Create a copy, don't share the array
	return new String(value, 0, count);
    }

看到toString()的实现,可以确定赋值给s的是堆中新new出来的对象。


由于String是不可改变类(immutable class),当我们需要改变字符串的值时,通过字符串的拼接是很耗内存的,它会不断创建新的字符串对象除非是都用直接字符串来拼接而不用变量,那这样在开发中也没多大意义。这里建议使用StringBuilder的append()进行字符串的操作。StringBuilder是JDK1.5新增的类,是非线程安全的,而StringBuffer是线程安全的,所以性能StringBuilder>StringBuffer>String。


转:

    原理1:当使用任何方式来创建一个字符串对象s时,Java运行时(运行中JVM)会拿着这个X在String池中找是否存在内容相同的字符串对象,如果不存在,   则在池中创建一个字符串s,否则,不在池中添加。
    原理2:Java中,只要使用new关键字来创建对象,则一定会(在堆区或栈区)创建一个新的对象。
    原理3:使用直接指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String池中的字符串,池中没有就在池中创建一个,有则罢了!但绝不会在堆栈区再去创建该String对象。
    原理4:使用包含变量的表达式来创建String对象,则不仅会检查维护String池,而且还会在堆栈区创建一个String对象。


注:更多有关字符串累加关注,我的转载《编译器将字符串累加编译成StringBuilder》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值