String,StringBuffer,StringBuilder详解

1.前言

最近想系统的进行一次知识梳理,所以对String,StringBuffer,StringBuilder做了一个总结。

2.正文

2.1 String

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."<init>":()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."<init>":(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."<init>":()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."<init>":()V
   #2 = String             #41            // abc
   #3 = Class              #42            // java/lang/String
   #4 = Methodref          #3.#43         // java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = String             #44            // ab
   #6 = Class              #45            // java/lang/StringBuilder
   #7 = Methodref          #6.#40         // java/lang/StringBuilder."<init>":()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               <init>
  #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        // "<init>":()V
  #41 = Utf8               abc
  #42 = Utf8               java/lang/String
  #43 = NameAndType        #16:#57        // "<init>":(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<String> list=new ArrayList<String>();
        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)

2.2 StringBuilder和StringBuffer

首先看下UML图
StringBuffer和StringBuilder.png
都继承了AbstractStringBuilder,所以二者的实现基本上都差不多,唯一的区别就是StringBuffer的方法加了同步锁,是线程安全的
还有一点二者都没有重写equals方法,所以比较的时候==和equals是一样的。

3.总结

(1). String和StringBuffer、StringBuilder相比,String是不可变的,String的每次修改操作都是在内存中重新new一个对象出来,而StringBuffer、StringBuilder则不用,并且提供了一定的缓存功能。
(2). StringBuffer和StringBuilder相比,StringBuffer是synchronized的,是线程安全的,而StringBuilder是非线程安全的。

猜你喜欢

转载自blog.csdn.net/qhkabuqiluo/article/details/80064980