美文网首页
谈谈String

谈谈String

作者: 粗旷的码农 | 来源:发表于2017-04-25 23:19 被阅读0次

    String它是一个引用数据类型,不是一个基础数据类型。
    先思考一个问题:String为什么是不可更改的。
    查看String类的签名如下:

    public final class String  
        implements java.io.Serializable, Comparable<String>, CharSequence {}  
    

    然后再看看String到底是怎么存储字符串的:

    /** The value is used for character storage. */  
        private final char value[];  
    

    String类的签名,和存储String的char数组都被final修饰,它们确保了String对象是永远不会被修改的。

    一、内存存储

    下面我们继续String之旅吧,先贴一段学习代码。

    
    public class LearnString {
        public static void main(String args[]) {
            String a = "abc";
            String b = "abc";
            String c = new String("abc");
            System.out.println(a == b);
            System.out.println(a == c);
            System.out.println(a.equals(c));
        }
    }
    
    

    运行结果也如大家所想:

    true
    false
    true
    

    我们先看看String的内存分布情况吧

    public class LearnString {
         String b = "abc";
        public static void main(String args[]) {
            String a = "abc";
        }
    }
    

    编译后,我们通过javap -v LearnString来查看内存的分布情况,可以看到在字节码的Constant pool区有如下一行:

    Constant pool:
       #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
       #2 = String             #24            // ab
       #3 = Fieldref           #5.#25         // com/example/learn/LearnString.d:Ljava/lang/String;
       #4 = String             #26            // abc
    

    可见不管是成员变量还是局部变量,只要String一开始就被赋值了,那么它的值就会被保存在字节码的常量池中。其实你会发现如果把之前的:

    String d = "ab" + "d";
    

    重新编译后再javap后如下:

    Constant pool:
       #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
       #2 = String             #24            // ab
       #3 = Fieldref           #5.#25         // com/example/learn/LearnString.d:Ljava/lang/String;
       #4 = String             #26            // abc
    

    这时候我们可以发现结果是一样的,也就是说compiler发现这些"+"操作完全可以在编译阶段优化掉,compiler就会进行一定的优化操作。
    接下来我们可以讨论开篇的那个例子了。“abc”字符串按照上面所说,在编译的时候就被保存在字节码的常量池中了,所以a 和 b都是拿的常量池中的“abc”值(指向了同一个堆地址),故a==b为true;c = new String("abc") ,它其实创建了两个对象,一个new出来的另一个就是常量“abc”,c的引用指向了new出来的对象,故a!=c。

        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;
        }
    

    这是String重写Object的equals方法,由此可以发现equals方法是比较的是两个String对象里头的字符数组(char[]),比较的是堆中值而非堆地址,故a.equals(c)是相等。

    二、注意的问题

    先看看下面代码

    String s = "";  
    for(int i = 0; !"end".equals(args[i]);){  
          s+=args[i];  
    }  
    

    同样用javap反编译字节码

    Classfile /F:/workspaces/JavaLearn/out/production/JavaLearn/com/example/learn/LearnString.class
      Last modified 2017-4-25; size 779 bytes
      MD5 checksum 646b7bf02cd674af0bc5e55605ee3713
      Compiled from "LearnString.java"
    public class com.example.learn.LearnString
      minor version: 0
      major version: 50
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #10.#30        // java/lang/Object."<init>":()V
       #2 = String             #31            //
       #3 = String             #32            // end
       #4 = Methodref          #33.#34        // java/lang/String.equals:(Ljava/lang/Object;)Z
       #5 = Class              #35            // java/lang/StringBuilder
       #6 = Methodref          #5.#30         // java/lang/StringBuilder."<init>":()V
       #7 = Methodref          #5.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       #8 = Methodref          #5.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;
       #9 = Class              #38            // com/example/learn/LearnString
      #10 = Class              #39            // java/lang/Object
      #11 = Utf8               <init>
      #12 = Utf8               ()V
      #13 = Utf8               Code
      #14 = Utf8               LineNumberTable
      #15 = Utf8               LocalVariableTable
      #16 = Utf8               this
      #17 = Utf8               Lcom/example/learn/LearnString;
      #18 = Utf8               main
      #19 = Utf8               ([Ljava/lang/String;)V
      #20 = Utf8               i
      #21 = Utf8               I
      #22 = Utf8               args
      #23 = Utf8               [Ljava/lang/String;
      #24 = Utf8               s
      #25 = Utf8               Ljava/lang/String;
      #26 = Utf8               StackMapTable
      #27 = Class              #40            // java/lang/String
      #28 = Utf8               SourceFile
      #29 = Utf8               LearnString.java
      #30 = NameAndType        #11:#12        // "<init>":()V
      #31 = Utf8
      #32 = Utf8               end
      #33 = Class              #40            // java/lang/String
      #34 = NameAndType        #41:#42        // equals:(Ljava/lang/Object;)Z
      #35 = Utf8               java/lang/StringBuilder
      #36 = NameAndType        #43:#44        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #37 = NameAndType        #45:#46        // toString:()Ljava/lang/String;
      #38 = Utf8               com/example/learn/LearnString
      #39 = Utf8               java/lang/Object
      #40 = Utf8               java/lang/String
      #41 = Utf8               equals
      #42 = Utf8               (Ljava/lang/Object;)Z
      #43 = Utf8               append
      #44 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #45 = Utf8               toString
      #46 = Utf8               ()Ljava/lang/String;
    {
      public com.example.learn.LearnString();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 6: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/example/learn/LearnString;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=3, args_size=1
             0: ldc           #2                  // String
             2: astore_1
             3: iconst_0
             4: istore_2
             5: ldc           #3                  // String end
             7: aload_0
             8: iload_2
             9: aaload
            10: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
            13: ifne          40
            16: new           #5                  // class java/lang/StringBuilder
            19: dup
            20: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            23: aload_1
            24: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            27: aload_0
            28: iload_2
            29: aaload
            30: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            33: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            36: astore_1
            37: goto          5
            40: return
          LineNumberTable:
            line 8: 0
            line 9: 3
            line 10: 16
            line 12: 40
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                5      35     2     i   I
                0      41     0  args   [Ljava/lang/String;
                3      38     1     s   Ljava/lang/String;
          StackMapTable: number_of_entries = 2
            frame_type = 253 /* append */
              offset_delta = 5
              locals = [ class java/lang/String, int ]
            frame_type = 250 /* chop */
              offset_delta = 34
    }
    SourceFile: "LearnString.java"
    

    在执行"+"时候

            13: ifne          40
            16: new           #5                  // class java/lang/StringBuilder
            19: dup
            20: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            23: aload_1
    

    在这个循环里每一次对String执行"+"操作,都会创建一个StringBuilder对象,可见这多么消耗性能。为了避免这种事情发生只要你在执行循环之前创建一个StringBuilder对象然后将之后的"+"操作换成StringBuilder.append()就可以。

    相关文章

      网友评论

          本文标题:谈谈String

          本文链接:https://www.haomeiwen.com/subject/ewwqzttx.html