关于android:Java深入研究String字符串

86次阅读

共计 6770 个字符,预计需要花费 17 分钟才能阅读完成。

提及 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_SUPER
    Constant 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>
签名字符 办法类型
B byte
C char
D double
F float
I int
J long
L 援用类型
S short
Z boolean
[ 数组类型
V Void 类型

</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); //false
System.out.println(testStr1.equals(testStr2)); //true
System.out.println(testStr1 == testStr3); //true
System.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/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: new #4 // class java/lang/String
10: dup
11: ldc #5 // String Test
13: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
16: invokevirtual #7 //Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: new #4 // class java/lang/String
22: dup
23: ldc #8 // String Manna
25: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
28: invokevirtual #7 //Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: new #4 // class java/lang/String
34: dup
35: ldc #9 // String Yang
37: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
40: 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_1
47: 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

正文完
 0