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类型常量信息的,表结构如下:
类型 名称 数量 u1 tag 1 u2 index 1
tag表示常量类型的标志位,而index是指向字符串字面量(存在常量池的CONSTANT_Utf8_info)的索引,CONSTANT_Utf8_info主要是存储类名、字段名、方法名以及相关的描述符,还有字符串字面量,它们都是用UTF-8缩略编码来表示的字符串,CONSTANT_Utf8_info的表结构如下:
类型 名称 数量 u1 tag 1 u2 length 1 u1 bytes length
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》
String类的创建和存储以及和StringBuilder、StringBuffer的区别
最新推荐文章于 2025-06-07 09:56:15 发布