Java 字符串 String、StringBulider和StringBuffer

606 阅读5分钟

一、可变和不可变对象

可变对象:当您拥有对对象实例的引用时,该实例的内容可以更改 不可变对象:当您拥有对对象实例的引用时,该实例的内容无法更改

使用java.lang.String作为不可变类,使用java.awt.Point作为可变类

public class StringTest {
    public static void main(String[] args) {
        Point myPoint = new Point( 0, 0 );
        System.out.println( myPoint );
        myPoint.setLocation( 1.0, 0.0 );
        System.out.println( myPoint );

        String myString = new String( "old String" );
        System.out.println( myString );
        myString.replaceAll( "old", "new" );
        System.out.println( myString );
    }
}

结果输出如下

java.awt.Point[x=0,y=0]
java.awt.Point[x=1,y=0]
old String
old String

我们只查看每个对象的单个实例,但是可以看到myPoint的内容发生了变化,而myString的内容没有发生变化。 某些语言,例如 C++ 和Ruby,通常允许在创建字符串后更改其内容;这些被称为可变字符串。在其他语言中,例如Java和Python,该值是固定的,如果要进行任何更改,则必须创建一个新字符串;这些被称为不可变字符串(其中一些语言还提供了另一种可变类型,例如 Java 和.NET StringBuilder、线程安全的 JavaStringBuffer和Cocoa NSMutableString)。

1、String

在 Java 编程中广泛使用的字符串是一个字符序列。在 Java 编程语言中,字符串是对象。

通过查看String.java的源码可以知道String是被final修饰的,因此它是不能被继承的。

public final class String {
}

下面我们来看一组代码

public class StringTest {
    public static void main(String[] args) {
		String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
    }
}

下面是该代码的内存图 在这里插入图片描述 凡是带有双引号的字符串都会放在字符串常量池中,字符串放在字符串常量池是为了提高效率。如果使用构造函数或方法创建字符串,则这些字符串将存储在堆内存中的SringConstantPool,但是在保存到池中之前,它会调用intern()方法来使用 equals()方法检查池中具有相同内容的对象可用性。如果池中的字符串副本可用,则返回引用。否则,将 String 对象添加到池中并返回引用。

再来看一组代码

public class StringTest {
    public static void main(String[] args) {
		String str3 = new String("abcde");
        String str4 = new String("abcde");
        System.out.println(str3 == str4);
    }
}

此时 str3 和 str4 都在堆中创建了对象,这两个对象时两个不同地址的空间,所以上述代码的结果为false。但是他们所指引的字符串还是在字符串常量区中。 在这里插入图片描述 下面我们再来看一组代码,以下代码会创建十一个对象。假如,我们for循环执行上万条,此时,会造成字符串常量区溢出,所以这个时候就不适合在循环里使用连接符操作字符串。这时就出现了StringBuffStringBuilder

String str = "a";
for (int i = 0; i < 10; i++) {
    str += "b";
}
System.out.println(str);

在这里插入图片描述

2、StringBuffer和StringBuilder

String是一个不可变的类,无法改变。StringBuilder是一个可变类,可以附加到替换或删除的字符,最终转换为String

下面是常用的方法

public class StringTest {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        String str = "java";
        // 将字符串添加"xiao"字符串
        sb.append("xiao");
        // 将sb字符串中第 [0, 2) 位字符替换为str -- javaao
        sb.replace(0, 2, str);
        // 删除第 [3, 5) 为字符                -- javo
        sb.delete(3, 5);
        // 返回字符串的长度                     -- 4
        System.out.println(sb.length());
        // 返回指定下标的值                     -- j
        System.out.println(sb.charAt(0));
        // 返回下标为 [0, 3) 之间的子序列        -- jav
        System.out.println(sb.substring(0, 3));
        System.out.println(sb);
    }
}

StringBuffer 每当我们对使用此类创建的任何 String 进行一些更改时,都不会创建新对象,因为更改是在同一对象中进行的。这个类是线程安全的,即它可以在多线程环境中正常工作。此类的方法是同步的,因此每当多个线程调用对象上的某些方法时,它们就会按照每个单独线程进行的方法调用的顺序执行。 常用方法:

  • append – 在字符串的末尾追加数据。
  • insert – 在任何给定索引处插入数据。

StringBuilder 这是 StringBuffer 类的一个新的但更小的版本,随着 JDK 5 的发布而引入。它与 StringBuffer 类似,但它不为线程安全或同步提供任何保证。 它用于只有一个线程应用程序的地方,因为它在大多数情况下要快得多。其余部分与 StringBuffer 类似,具有相同的方法和声明。我们只需要在初始化期间使用不同的类,其余的代码不需要更改,因为它也有相同的方法。

下面是三者的区别 在这里插入图片描述

二、不可变对象的好处

  • 默认情况下,不可变对象是线程安全的,可以在并发环境中共享而无需同步。
  • 不可变对象简化了开发,因为它更容易在多个线程之间共享,而无需外部同步。 -通过减少代码中的同步,java应用程序的不可变对象提升性能。
  • 不可变对象的另一个重要优点是可重用性,您可以缓存不可变对象并重用它们,就像字符串文本和整数一样。您可以使用静态工厂方法来提供valueOf()之类的方法,它可以从缓存返回现有的不可变对象,而不是创建新对象。

来看下他们三个的使用场景

  1. 如果不是在循环体中进行字符串拼接的话,直接使用 String 的 “+” 就好了。

  2. 单线程循环中操作大量字符串数据 → StringBuilder.append()

  3. 多线程循环中操作大量字符串数据 → StringBuffer.append()