0%

String字符串

一、 String 简介

String 不变原理(从源码上讲)

String 对象是不可改变的,String类中每一个==看起来会修改String对象值得方法,实际上都是创建了一个全新得String对象==,包含了修改后得字符串内容,最初得原始对象没有改变过

String 源码

String 属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

private static final long serialVersionUID = -6849794470754667710L;


private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];

......
}

String 构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
...
*/
public String() {
this.value = "".value;
}
/**
...
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/**
...
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**
...
*/
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
......

理由

从上面得源码可以知道,String得value属性值是一个字符数组,字符串实际由字符串数组组成,并且被final修饰,所以不能被修改

二、 创建字符串的两种方式

通过构造方法创建字符串对象

通过构造函数创建的字符串是在堆内存

1
String str=new String("hello");

++这里创建了多少对象++?
2个。
原因

  • jvm 会在 String pool 中寻找是否存在 “hello” ,如果没有则创建,有则什么都不做
  • 之后遇到了 new ,又在 Java head 中创建了一个对象用了存储 “hello”

所以一共创建了两个

通过直接赋值创建字符串

1
String str="hello";

通过直接赋值创建的对象是在方法区的常量池

两种实例化方法的比较

  • 代码上的比较

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class TestString {
    public static void main(String[] args) {
    String str1 = "Lance";
    String str2 = new String("Lance");
    String str3 = str2; //引用传递,str3直接指向st2的堆内存地址
    String str4 = "Lance";
    /**
    * ==:
    * 基本数据类型:比较的是基本数据类型的值是否相同
    * 引用数据类型:比较的是引用数据类型的地址值是否相同
    * 所以在这里的话:String类对象==比较,比较的是地址,而不是内容
    */
    System.out.println(str1==str2);//false
    System.out.println(str1==str3);//false
    System.out.println(str3==str2);//true
    System.out.println(str1==str4);//true
    }

    }
  • 内存图比较
    如图
    image

    字符串常量池

    在字符串中,如果采用直接赋值的方法进行对象的实例化,则会将匿名对象放入字符串常量池,每当下一次对不同的对象进行直接赋值时,会直接利用池中的原有的匿名对象

总结

  1. 直接赋值:开辟一块堆内存空间,并且自动入池不会产生垃圾
  2. 构造函数:会开辟两块内存堆空间,其中一块堆内存会变成垃圾被系统回收掉,而且不能够自动入池,要手动入池,另一块堆空间存储值。

开发过程中尽量不用采用构成函数来进行字符串实例化

三、 String不可变的性质(从内存上讲)

意思是:String是个常量,从一出生就注定不可变
从第一点就看到,String 是被 final 所修饰的,不可变,以下是经典例子

1
2
3
4
5
6
7
8
9
10
11
public class Apple {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a==b); //true
System.out.println(a.equals(b)); //true
System.out.println(a==c); //false
System.out.println(a.equals(c)); //true
}
}

原因

因为有着字符串常量池的的缘故,每次当直接赋值时,jvm 都会在池寻找是否已经存在该字符串,如果有,则使用同一个字符串,没有,则生成一个,所以,这也就是为什么上面的例子 a==btrue , a==cfalse ,因为变量只是保存地址值 == 比较的是地址,而,equals() 比较的字符串的内容。
以上的这种模式,叫做享元模式

不变的好处

可以实现多个变量引用堆内存中的同一个字符串实例,避免创建的开销。

  我们的程序中大量使用了String字符串,有可能是出于安全性考虑

  大家都知道HashMap中key为String类型,如果可变将变的多么可怕。

  当我们在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。   

重载”+”与StringBuilder 构建字符串

1
2
3
4
5
6
7
public class test{
public static void main(String[] agrs){
String mango="hello";
String s="abc"+mango+"def"+47;
System.out.println(s);
}
}

上面的这段代码,我们会这样想,String可能有个 append() 方法,他会生成一个新的String对象,这个新生成的对象包好了“abc” 与 mango 连接后的字符串,以此类推。

但是这种工作方式,我们知道,他最终生成的String对象中途会产生一堆垃圾回收的中间对象。这样会发现性能相当糟糕

通过反编译字节码我们看到
image

编译器自动使用了 StringBuilder 类来构造最终 String 对象,并且调用StringBuilder 的 append() 方法,总计四次,然后调用toString() 生成结果。

现在可能认为编译器会自动优化,这里在使用两种不同的方式来生成 String 比较,一种使用多个 String 对象,一种使用 StringBuilder,对应着下面的 implicit() 方法和 explicit() 方法
image

反编译之后,首先是 implicit() 方法
image

可以看出,从第8行开始到第35行都在重复的生成 StringBuilder 对象
接下来是 explicit() 方法

image
我们可以看的,代码量进行了缩短,并且只生成了一个 StringBuilder 对象

所以如果是简单字符串,可以交给编译器来合理构造,不然使用 StringBuilder 对象的 append() 方法一个个拼接起来

附加(字符串常量+字符串常量=字符串常量?)

对于以下代码

1
2
3
4
5
6
7
8
9
String test1="helloworld";
String test2="hello"+"world";
String test3="hello";
String test4=test3+"world";
String test5=new String("helloworld");

System.out.println(test1==test2);//true
System.out.println(test1==test4);//false
System.out.println(test1==test5);false

从上面代码例子中看出,为什么 test1==test2true ,而 test1==test4false

  • 对于 test1 为什么会等于 test2 ,因为两个都是字面字符串常量,Java 在编译期间,就会将两边拼接起来,因为编译器认为这个时候的值是不会发生改变,这个运算即使放在运行时不会变化,所以编译期就放到同一个常量池中了。
  • test4有一变量(严格意义上的变量,可以被改变的数据,可大可小可长可短能伸能缩,所有需要一次以上处理才能成为直接字面字符的 值存在时 java都一律将其视为变量),无论这里几个变量,因为 Java 并不知道这里 test3 到底是什么只知道他是一个 String 变量(在编译期)具体值是在运行时而去处理,和多态很像。

从前面的知识点得知,Java 在处理字符串变量 + 时会生成一个 StringBuilder 的 append() 方法,最后 toString() 方法来获得字符串,这是就 new 出来了一个新的堆空间,就不一样了

本文标题:String字符串

文章作者:志者

发布时间:2019年11月18日 - 15:43:00

最后更新:2019年11月18日 - 16:27:50

原始链接:http://witman1999.github.io/String字符串.html

许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢您的阅读-------------
copy