最近想系统的进行一次知识梳理,所以对String,StringBuffer,StringBuilder做了一个总结。
String是一个不可变类,不可变类不是单纯的因为String是final类,而是String的成员变量value是一个final变量,所以String的操作基本上都会生成一个新的对象,而且String的值会存在常量池中
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
下面先看一个问题
public class demo1 {
public static void main(String[] args) {
String s1="abc";
String s2="abc";
String s3=new String("abc");
String s4="a"+"b"+"c";
String s5="ab";
String s6=s5+"c";
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s1.equals(s3));
System.out.println(s1==s4);
System.out.println(s1==s6);
}
}
在看结果之前,先看下编译后的代码
执行命令javap -c demo1.class
public class string.demo1 {
public string.demo1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/String
9: dup
10: ldc #2 // String abc
12: invokespecial #4 // Method java/lang/String."":(Ljava/lang/String;)V
15: astore_3
16: ldc #2 // String abc
18: astore 4
20: ldc #5 // String ab
22: astore 5
24: new #6 // class java/lang/StringBuilder
27: dup
28: invokespecial #7 // Method java/lang/StringBuilder."":()V
31: aload 5
33: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: ldc #9 // String c
38: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore 6
}
看过编译后的文件,我们了解到
1.”a”+”b”+”c”;会被编译器优化变成abc
2.s5+”c”;在编译器优化后,实际上是new一个StringBuilder对象,然后进行apend()操作,再toString()
再看一下String的equals方法
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
运行命令javap -verbose demo1.class
查看demo1.class的常量池(用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放)
Constant pool:
#1 = Methodref #15.#40 // java/lang/Object."":()V
#2 = String #41 // abc
#3 = Class #42 // java/lang/String
#4 = Methodref #3.#43 // java/lang/String."":(Ljava/lang/String;)V
#5 = String #44 // ab
#6 = Class #45 // java/lang/StringBuilder
#7 = Methodref #6.#40 // java/lang/StringBuilder."":()V
#8 = Methodref #6.#46 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#9 = String #47 // c
#10 = Methodref #6.#48 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#11 = Fieldref #49.#50 // java/lang/System.out:Ljava/io/PrintStream;
#12 = Methodref #51.#52 // java/io/PrintStream.println:(Z)V
#13 = Methodref #3.#53 // java/lang/String.equals:(Ljava/lang/Object;)Z
#14 = Class #54 // com/jdk/analyse/String/demo1
#15 = Class #55 // java/lang/Object
#16 = Utf8
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 LocalVariableTable
#21 = Utf8 this
#22 = Utf8 Lcom/jdk/analyse/String/demo1;
#23 = Utf8 main
#24 = Utf8 ([Ljava/lang/String;)V
#25 = Utf8 args
#26 = Utf8 [Ljava/lang/String;
#27 = Utf8 s1
#28 = Utf8 Ljava/lang/String;
#29 = Utf8 s2
#30 = Utf8 s3
#31 = Utf8 s4
#32 = Utf8 s5
#33 = Utf8 s6
#34 = Utf8 StackMapTable
#35 = Class #26 // "[Ljava/lang/String;"
#36 = Class #42 // java/lang/String
#37 = Class #56 // java/io/PrintStream
#38 = Utf8 SourceFile
#39 = Utf8 demo1.java
#40 = NameAndType #16:#17 // "":()V
#41 = Utf8 abc
#42 = Utf8 java/lang/String
#43 = NameAndType #16:#57 // "":(Ljava/lang/String;)V
#44 = Utf8 ab
#45 = Utf8 java/lang/StringBuilder
#46 = NameAndType #58:#59 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#47 = Utf8 c
#48 = NameAndType #60:#61 // toString:()Ljava/lang/String;
#49 = Class #62 // java/lang/System
#50 = NameAndType #63:#64 // out:Ljava/io/PrintStream;
#51 = Class #56 // java/io/PrintStream
#52 = NameAndType #65:#66 // println:(Z)V
#53 = NameAndType #67:#68 // equals:(Ljava/lang/Object;)Z
#54 = Utf8 com/jdk/analyse/String/demo1
#55 = Utf8 java/lang/Object
#56 = Utf8 java/io/PrintStream
#57 = Utf8 (Ljava/lang/String;)V
#58 = Utf8 append
#59 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#60 = Utf8 toString
#61 = Utf8 ()Ljava/lang/String;
#62 = Utf8 java/lang/System
#63 = Utf8 out
#64 = Utf8 Ljava/io/PrintStream;
#65 = Utf8 println
#66 = Utf8 (Z)V
#67 = Utf8 equals
#68 = Utf8 (Ljava/lang/Object;)Z
以上我们知道
1.String重写equals方法比较的时字面值
2.s1返回的是常量池的地址,s2初始化时,常量池中已经有abc了,所以直接返回常量池中的地址,而new操作会在堆上面分配内存,所以s3返回的是堆地址
综上,我们不难得出程序的结果
true
false
true
true
false
String常量池
JDK1.7中JVM把String常量区从方法区中移除了;JDK1.8中JVM把String常量池移入了堆中,同时取消了“永久代”,改用元空间代替(Metaspace)
public static void main(String[] args) {
String s1="abc";
char[] c1={'a','b','c'};
String s2=new String(c1);
s2=s2.intern();
System.out.println(s1==s2);
ArrayList list=new ArrayList();
for (int i = 0; i < 100000000; i++) {
String temp = String.valueOf(i).intern();
list.add(temp);
}
}
设置运行时参数,限制下”永久代”的大小
-XX:PermSize=10M -XX:MaxPermSize=10M
使用jdk1.6运行
true
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.jdk.analyse.String.demo2.main(demo2.java:35)
使用jdk1.7/1.8运行
运行参数(-XX:PermSize=10M -XX:MaxPermSize=10M在1.8上已经不适用了)
-XX:PermSize=10M -XX:MaxPermSize=10M -Xmx50M
true
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.lang.Integer.toString(Integer.java:331)
at java.lang.String.valueOf(String.java:2954)
at com.jdk.analyse.String.demo2.main(demo2.java:35)
首先看下UML图
都继承了AbstractStringBuilder,所以二者的实现基本上都差不多,唯一的区别就是StringBuffer的方法加了同步锁,是线程安全的
还有一点二者都没有重写equals方法,所以比较的时候==和equals是一样的。
(1). String和StringBuffer、StringBuilder相比,String是不可变的,String的每次修改操作都是在内存中重新new一个对象出来,而StringBuffer、StringBuilder则不用,并且提供了一定的缓存功能。
(2). StringBuffer和StringBuilder相比,StringBuffer是synchronized的,是线程安全的,而StringBuilder是非线程安全的。