String字符串赋值及常量池原理
String简介
String设计为不可变类主要考虑到:效率和安全。
String常量池
由于String的不可变性,为了提高操作效率,所以设计常量池。从1.8开始,String常量池被放在了堆中,在1.8之前存放在方法区的运行时常量池。
String a = "a"做了什么
String a = "a"
这段代码首先 "a" 此字符串叫字符面值。在编译时,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量 (Literal)和 符号引用 (Symbolic References),每个class文件都有一个class常量池。在运行时,class文件加载后,对应的静态常量池被常驻到内存即运行时常量池。当代码执行到String a = "a"时,声明的变量a将存放"a"字面量对应的引用地址于栈中。
String a = new String(“a”)做了什么
对于字符面值 "a",肯定是在常量池中。然后通过new在堆中创建新的字符串对象。栈中变量a的值即为new的字符串对象的引用地址。
String intern()方法
String类的intern()方法:一个初始为空的字符串池,它由类String独自维护。当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。 对于任意两个字符串s和t,当且仅当s.equals(t)为true时,s.intern() == t.intern()才为true。所有字面值字符串和字符串赋值常量表达式都使用 intern方法进行操作。
String作为方法传递
public static String upcase(String old){
old = "aa";
return old.toUpperCase();
}
public static void main(String[] args) {
String a = "a";
String aa = upcase(a);
System.out.println(a);//a
System.out.println(aa);//AA
System.out.println(a);//a
}
在方法传递时,字符串传递的是引用,因此尽管old局部变量被upcase方法重新赋值,但不会影响到main方法中声明的a变量
重载’+'与StringBuilder
java对字符串加法进行了重载,+作为二元运算符。
当a、b变量至少一个是String对象时。
a+b <=> new StringBuilder(a).append(b).toString();
当a、b变量为基本数据类型或者为字符面值
a+b <=> ab
上面两者是完全等价的。说的有点抽象,下面看代码
情况一,常量池引用
String str1 = "a";
String str2 = "a";
System.out.println("str1 == str2: "+(str1==str2));//true
变量str1、str2都是存放常量池中String对象的相同引用。
情况二,重载“+”与字符面值
final String str1 = "aa";
String str2 = "aa"+"1";
String str3 = "aa"+1;
String str4 = str1 + 1;
System.out.println("str2 == str3: "+(str2==str3));
System.out.println("str3 == str4: "+(str3==str4));
都是true,因为字符串的重载+,两个变量是基本数据类型或字符面值是在编译时会自动的合并。同时对于final修饰的String对象且指向常量池,在编译时,依然会合并。
情况三,重载“+” 与String对象
String str1 = "aa";
String str2 = "aa1";
String str3 = str1 + 1;//与下方等效
//String str3 = new StringBuilder(str1).append(1).toString();
System.out.println("str2 == str3: "+(str2==str3));//false
System.out.println("str3.intern() == str2: "+(str3.intern()==str2));//true
实际上str3最终是通过StringBuilder的toString方法创建了String对象。因此str2和str3内存地址不同。但str3的intern与str2是相同的,因为是同一个常量池值。
通过这里我们可以知道对于对象字符串的加法,在for循环内部是避免使用的。因为会创建太多的StringBuilder对象。
StringBuilder与StringBuffer
简介
由于字符串的不可变特性,通常我们会有字符串拼接的需求,如果单纯的使用String+那么性能肯定会受到影响。因此设计出了线程不安全的StringBuilder,和线程安全的StringBuffer
AbstractStringBuilder
StringBuilder和StringBuffer都是集成了抽象类AbstractStringBuilder。AbstractStringBuilder的主要功能就是提供了对字符串的添加、移除、存储方式进行了约定。然后具体的实现对应的功能。核心是利用char[]进行数组存储,通过扩容的方式进行增加类似ArrayList。对于StringBuilder的实现基本上都是调用抽象类实现,对于StringBuffer在字符串操作的方法加入了synchronized同步代码块实现线程安全。
本文详细探讨了Java中的String字符串,包括其不可变性、常量池原理,以及不同赋值方式的影响。讲解了String a = "a"和String a = new String("a")的区别,并介绍了String的intern()方法。此外,文章还对比了使用'+'重载和StringBuilder、StringBuffer在字符串拼接时的性能差异,以及它们的内部实现。
1393

被折叠的 条评论
为什么被折叠?



