提及String字符串,咱们更多的是用于文本的传输与存储,在JDK源码中也被申明为final类型,同时也不属于Java中根本的数据类型,例如以间接双引号申明的常量String nameStr="Manna Yang";或者采纳构造函数创立String nameStr=new String("Manna Yang");上面将逐渐揭开其神秘面纱...
class字节码文件构造
在探索String字符串常量池之前,咱们首先看下通过javap -v命令编译后的字节码
原始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"); }}
编译后的字节码
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"
- Constant pool地位之前,顺次是对以后编译的class、最初批改工夫、MD5校验、Java的主要版本、主版本52(十进制的值,对应jdk 1.8,转换为16进制为34)、标记是否是public、是否调用超类构造方法
- Constant pool地位以下,从 #1 - #37 对应常量池区域,寄存办法签名以及定义的String字面值,例如#2对应#23 即是Java代码中private String testStr="Manna Yang"; #29 NameAndType 对应#11:#12 即是Java代码中的public static int TYPE=0;
- 办法签名类型如下
<center>
签名字符 | 办法类型 | |
---|---|---|
B | byte | |
C | char | |
D | double | |
F | float | |
I | int | |
J | long | |
L | 援用类型 | |
S | short | |
Z | boolean | |
[ | 数组类型 | |
V | Void类型 |
</center>
- 字节码指令
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,倡议在初始拼接时传入指定预计字符串长度值