提及String字符串,咱们更多的是用于文本的传输与存储,在JDK源码中也被申明为final类型,同时也不属于Java中根本的数据类型,例如以间接双引号申明的常量String nameStr="Manna Yang";或者采纳构造函数创立String nameStr=new String("Manna Yang");上面将逐渐揭开其神秘面纱...

class字节码文件构造

在探索String字符串常量池之前,咱们首先看下通过javap -v命令编译后的字节码

  1. 原始Java代码,通过javac编译为class,

    public class TestString{   private String testStr="Manna Yang";   public static int TYPE=0;      public static void main(String[] args){         System.out.println("Manna Yang");   }}
  2. 编译后的字节码

    Classfile /C:/Users/15971/Desktop/TestString.class  Last modified 2019-9-18; size 566 bytes  MD5 checksum 72f3c93ff8293c97a3da06775fa48ba0  Compiled from "TestString.java"public class TestString  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:#1 = Methodref          #8.#22         // java/lang/Object."<init>":()V#2 = String             #23            // Manna Yang#3 = Fieldref           #7.#24         // TestString.testStr:Ljava/lang/String;#4 = Fieldref           #25.#26        // java/lang/System.out:Ljava/io/PrintStream;#5 = Methodref          #27.#28        // java/io/PrintStream.println:(Ljava/lang/String;)V#6 = Fieldref           #7.#29         // TestString.TYPE:I#7 = Class              #30            // TestString#8 = Class              #31            // java/lang/Object#9 = Utf8               testStr  #10 = Utf8               Ljava/lang/String;  #11 = Utf8               TYPE  #12 = Utf8               I  #13 = Utf8               <init>  #14 = Utf8               ()V  #15 = Utf8               Code  #16 = Utf8               LineNumberTable  #17 = Utf8               main  #18 = Utf8               ([Ljava/lang/String;)V  #19 = Utf8               <clinit>  #20 = Utf8               SourceFile  #21 = Utf8               TestString.java  #22 = NameAndType        #13:#14        // "<init>":()V  #23 = Utf8               Manna Yang  #24 = NameAndType        #9:#10         // testStr:Ljava/lang/String;  #25 = Class              #32            // java/lang/System  #26 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;  #27 = Class              #35            // java/io/PrintStream  #28 = NameAndType        #36:#37        // println:(Ljava/lang/String;)V  #29 = NameAndType        #11:#12        // TYPE:I  #30 = Utf8               TestString  #31 = Utf8               java/lang/Object  #32 = Utf8               java/lang/System  #33 = Utf8               out  #34 = Utf8               Ljava/io/PrintStream;  #35 = Utf8               java/io/PrintStream  #36 = Utf8               println  #37 = Utf8               (Ljava/lang/String;)V{  public static int TYPE; descriptor: I flags: ACC_PUBLIC, ACC_STATIC  public TestString(); descriptor: ()V flags: ACC_PUBLIC Code:   stack=2, locals=1, args_size=1      0: aload_0      1: invokespecial #1                  // Method java/lang/Object."<init>":()V      4: aload_0      5: ldc           #2                  // String Manna Yang      7: putfield      #3                  // Field testStr:Ljava/lang/String;     10: return   LineNumberTable:     line 1: 0     line 2: 4  public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code:   stack=2, locals=1, args_size=1      0: getstatic     #4    // Field java/lang/System.out:Ljava/io/PrintStream;      3: ldc           #2    // String Manna Yang      5: invokevirtual #5    // Method java/io/PrintStream.println:(Ljava/lang/String;)V      8: return   LineNumberTable:     line 6: 0     line 7: 8  static {}; descriptor: ()V flags: ACC_STATIC Code:   stack=1, locals=0, args_size=0      0: iconst_0      1: putstatic     #6                  // Field TYPE:I      4: return   LineNumberTable:     line 3: 0}SourceFile: "TestString.java"
  3. Constant pool地位之前,顺次是对以后编译的class、最初批改工夫、MD5校验、Java的主要版本、主版本52(十进制的值,对应jdk 1.8,转换为16进制为34)、标记是否是public、是否调用超类构造方法
  4. Constant pool地位以下,从 #1 - #37 对应常量池区域,寄存办法签名以及定义的String字面值,例如#2对应#23 即是Java代码中private String testStr="Manna Yang"; #29 NameAndType 对应#11:#12 即是Java代码中的public static int TYPE=0;
  5. 办法签名类型如下
    <center>
签名字符办法类型
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
L援用类型
Sshort
Zboolean
[数组类型
VVoid类型

</center>

  1. 字节码指令
public TestString();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=2, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: aload_0         5: ldc           #2                  // String Manna Yang         7: putfield      #3                  // Field testStr:Ljava/lang/String;        10: return      LineNumberTable:        line 1: 0        line 2: 4
  • descriptor : 形容办法类型
  • flags : 形容修饰符
  • stack : 操作数堆栈大小
  • locals : 局部变量数大小
  • agrs_size : 办法参数个数
  • load类型指令
    常见的有aload,fload,iload,dload,此处aload_0示意将本地变量推送到栈顶,a示意援用类型,i\d\f别离对应根本类型,构造根本遵循 : 类型|动作
  • const类型指令
    常见有iconst,iconst,fconst,dconst,例如定义int testType=2;在父类构造方法中就会存在iconst_0(下划线前面为index,示意变量地位),示意将int型常量推送到栈顶;
  • ldc : 将int,float或String型常量从常量池中推送至栈顶
  • putfield : 赋值操作,对应还有getfield
  • return : 返回void,对应还有ireturn、freturn,示意返回int\float类型
  • invokespecial : 调用父类无参无返回值构造方法
  • putstatic : 动态变量赋值,对应还有getstatic

<br/>

String字符串equals、hashcode、intern办法

1.理解上述字节码构造之后,再来看看罕用的字符串比拟

public boolean equals(Object anObject) {        if (this == anObject) {            return true;        }        if (anObject instanceof String) {            String anotherString = (String) anObject;            int n = length();            if (n == anotherString.length()) {                int i = 0;                while (n-- != 0) {                    if (charAt(i) != anotherString.charAt(i))                            return false;                    i++;                }                return true;            }        }        return false;    }

默认还是比拟常量池援用地址是否相等,否则比照类型,接着调用charAt()一一字符比拟,上面举例一些常见的比拟场景,加深了解

String testStr1="Manna Yang";String testStr2=new String("Manna Yang");String testStr3="Manna Yang";System.out.println(testStr1 == testStr2);           //falseSystem.out.println(testStr1.equals(testStr2));      //trueSystem.out.println(testStr1 == testStr3);           //trueSystem.out.println(testStr1.equals(testStr3));      //true依照jdk中equals办法,此时==比照为false(地址不一样),则持续采纳charAt形式一一比拟字符,new关键字创立的对象寄存在heap堆,双引号""申明的常量放在常量池,testStr2援用指向常量池"Manna Yang"字符地址

持续往下看 + 号的魅力

String testStr0 = new String("Test")+new String("Manna")+new String("Yang");编码后如下0: new           #2             // class java/lang/StringBuilder3: dup4: invokespecial #3             // Method java/lang/StringBuilder."<init>":()V7: new           #4             // class java/lang/String10: dup11: ldc           #5            // String Test13: invokespecial #6            // Method java/lang/String."<init>":(Ljava/lang/String;)V16: invokevirtual #7            //Method  java/lang/StringBuilder.append:                                (Ljava/lang/String;)Ljava/lang/StringBuilder;19: new           #4            // class java/lang/String22: dup23: ldc           #8            // String Manna25: invokespecial #6            // Method java/lang/String."<init>":(Ljava/lang/String;)V28: invokevirtual #7            //Method  java/lang/StringBuilder.append:                                (Ljava/lang/String;)Ljava/lang/StringBuilder;31: new           #4            // class java/lang/String34: dup35: ldc           #9            // String Yang37: invokespecial #6            // Method java/lang/String."<init>":(Ljava/lang/String;)V40: invokevirtual #7            //Method  java/lang/StringBuilder.append:                                (Ljava/lang/String;)Ljava/lang/StringBuilder;43: invokevirtual #10       //Method  java/lang/StringBuilder.toString:()Ljava/lang/String;46: astore_147: return

在字节码中能够看到+号 StringBuilder对象也参加一次创立,而后调用父类初始化办法,接着调用append办法,最初再调用toString(),字节码中new的指令蕴含4次,ldc指令蕴含3次;实际上jdk优化后的+号,在解决字符串拼接时提供很大便当,例如String testStr1="Manna"+" Yang";那么在字节码外面曾经拼接成一个字符串常量"Manna Yang";还有常见的在new String(""+"")这种形式,字符串也是会拼接,对应只new一次String对象;

2.持续看下hashcode, hash值(哈希)次要用于散列存储构造中确认对象的地址,像罕用的HashMap\HashTable,如果两个对象雷同则它们的hash值肯定雷同;反之hash值雷同的两个对象不肯定雷同;在进行hash计算时咱们冀望hash值的碰撞越少越好,进步查问效率,上面看下String的hashCode()办法源码

public int hashCode() {    int h = hash;    final int len = length();    if (h == 0 && len > 0) {        for (int i = 0; i < len; i++) {            h = 31 * h + charAt(i);        }        hash = h;    }    return h;}

对于31这个系数我了解的更多是散列散布的更为平均,产生hash碰撞的几率更小,在源码阐明外面也有计算公式推导 : s[0]31^(n-1) + s[1]31^(n-2) + ... + s[n-1],charAt字符数组中字符对应的value值为ASCII值,null的ASCII值为0;

3.对于String类中的intern(),源码办法里有具体正文,来源于jdk1.8

When the intern method is invoked, if the pool already contains a* string equal to this {@code String} object as determined by* the {@link #equals(Object)} method, then the string from the pool is* returned. Otherwise, this {@code String} object is added to the* pool and a reference to this {@code String} object is returned.//源码办法public native String intern();

String、StringBuffer、StringBuilder应用场景

字符串拼接效率,如果是字面常量拼接,则间接应用""+""+""这种形式,+号优化后只会生成一个对象,如果是字符串对象之间拼接,在多线程中应用时应采纳StringBuffer,大部分办法线程平安;否则可应用StringBuilder,后两者StringBuffer、StringBuilder的扩容机制为array.length+16,均继承形象父类AbstractStringBuilder中的构造函数,源码如下

AbstractStringBuilder(int var1) {    this.value = new char[var1];}...

每次都是从新new,而后再进行array copy,倡议在初始拼接时传入指定预计字符串长度值

以上波及JDK源码局部均来自 JDK 1.8