【java基础】String、StringBuffer和StringBuild 那些事

发布时间:2024年01月18日

String

基本特性

  • String是一个final类,代表不可变的字符序列。
  • 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
  • String对象的字符内容是存储在一个字符数组value[]中的。

String的继承图

Serializable

在 Java 中,Serializable 是一个标记接口,没有任何方法需要实现。它的主要用途是用于序列化对象,使得对象可以被转换为字节流,从而可以轻松地保存到文件或通过网络发送到另一个进程或机器。

序列化是将对象的状态信息转换为可以存储或传输的形式的过程。通过实现 Serializable 接口,一个类的对象可以被序列化,这意味着它们可以被转换为字节流。然后,这些字节流可以存储到文件、数据库或通过网络发送到任何地方。之后,这些字节流可以被反序列化回其原始对象状态。

以下是一些使用 Serializable 接口的常见场景:

  1. 持久化存储:对象可以被序列化并保存到文件中,以便在程序重新启动或需要时恢复它们的状态。
  2. 网络传输:对象可以被序列化并通过网络发送到另一个进程或机器。例如,通过网络传输对象状态信息到客户端。
  3. 单元测试:在单元测试中,对象的状态信息经常需要被捕获和比较。通过序列化对象,可以将它们的状态信息保存到文件或数据库中,以便稍后进行比较。
  4. 分布式系统:在分布式系统中,对象的状态信息经常需要在不同的节点之间共享。通过序列化对象,可以将它们的状态信息发送到其他节点。

要使一个类可序列化,只需实现 Serializable 接口即可。但是,如果一个类中有不可序列化的字段(例如,字段的类型不是可序列化的),则需要在该字段上使用 transient 关键字进行标记。这样,该字段的值将不会被序列化,而是会使用默认值进行反序列化。

Comparable

在Java中,Comparable接口是一个泛型接口,用于定义对象之间的自然顺序。通过实现Comparable接口,一个类可以指定其元素的排序方式。

当一个类实现了Comparable接口,就意味着它支持某种形式的比较语义,使得可以对类的实例进行排序和比较。要实现Comparable接口,类需要实现compareTo方法,该方法用于比较当前对象与另一个对象的大小关系。

实现Comparable接口的好处是:

  1. 类型安全:使用泛型,可以在编译时捕获类型错误。
  2. 灵活性:可以在运行时改变对象的排序规则。
  3. 自动排序:可以使用Java集合框架中的排序算法(如Collections.sort())对实现了Comparable接口的对象进行排序。

下面是一个简单的示例,演示如何使用Comparable接口对一个简单的Person类进行排序:


import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
  
public class Person implements Comparable<Person> {  
    private String name;  
    private int age;  
  
    public Person(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
  
    @Override  
    public int compareTo(Person other) {  
        return this.age - other.age; // 按年龄升序排序  
    }  
  
    public static void main(String[] args) {  
        List<Person> people = new ArrayList<>();  
        people.add(new Person("Alice", 25));  
        people.add(new Person("Bob", 30));  
        people.add(new Person("Charlie", 20));  
  
        Collections.sort(people); // 对people列表进行排序  
  
        for (Person person : people) {  
            System.out.println(person.getName() + " " + person.getAge());  
        }  
    }  
}

在上述示例中,Person类实现了Comparable<Person>接口,并重写了compareTo方法来指定按照年龄升序排序。然后,可以使用Collections.sort()方法对Person对象列表进行排序。

CharSequence

在Java中,CharSequence 是一个标记接口,主要用于表示任何可以产生字符序列的对象,如字符串(String)、缓冲区(StringBuilder 或 StringBuffer)等。

CharSequence 接口定义了一些用于操作字符序列的方法,例如 length(), charAt(int index), subSequence(int start, int end) 等。这些方法允许你获取字符序列的长度、访问特定位置的字符以及获取子序列。

虽然 CharSequence 是一个标记接口,但它并没有强制实现类必须实现特定的方法。它的主要作用是作为一个通用的字符序列接口,以便在不同的字符序列类型之间进行转换或操作。

例如,当你需要将一个字符串转换为缓冲区时,可以使用 StringBuffer 类的构造方法,该构造方法接受一个 CharSequence 参数。同样地,当需要将缓冲区转换为字符串时,可以使用 String 类的构造方法,该构造方法接受一个 CharSequence 参数。

以下是一个简单的示例,演示了如何使用 CharSequence 接口:


String str = "Hello, World!";  
CharSequence cs = str;  
  
// 使用CharSequence接口的方法  
int length = cs.length();  
char ch = cs.charAt(0); // 'H'  
CharSequence subSeq = cs.subSequence(7, 12); // "World"

通过实现 CharSequence 接口,不同的字符序列类型可以更加灵活地进行操作和转换。

创建String 对象的两种方式

  1. 直接赋值:String s = "涛涛之海";

这种方式它首先会先从常量池查看是否有"涛涛之海" 这个数据空间,如果有就直接指向,如果没有就创建一个”涛涛之海“这个数据空间然后指向它。注意s最终指向的是常量池的空间地址。

  1. 调用构造器 String s1= new String("涛涛之海");

这种方式则是先在堆中创建空间,里面维护了value属性,指向常量池的"涛涛之海"空间。如果常量池中没有''涛涛之海'',则重新创建,如果有就直接通过value指向。注意这里最终指向的是堆中的空间地址

StringBuffer

基本特性

  1. 线程安全,可变的字符序列。 字符串缓冲区就像一个String ,但可以修改。
  2. 字符缓冲可以安全的被多个线程使用。前提是这些方法必须进行同步
  3. 每个字符串缓冲区都有一个容量。 只要字符串缓冲区中包含的字符序列的长度不超过容量,就不必分配新的内部缓冲区数组。 如果内部缓冲区溢出,则会自动变大

StringBuffer的继承图

Appendable

在Java中,Appendable 接口是一个标记接口,用于表示一个对象可以被追加内容。它没有定义任何方法,只是作为一个类型标记来指示一个对象支持追加操作。

Appendable 接口通常与 String 构造器一起使用,以便将可变的内容追加到字符串中。通过实现 Appendable 接口,对象可以表明它支持追加操作,并提供一个用于追加内容的 append 方法。

以下是一个使用 Appendable 接口的示例:


import java.io.Appendable;  
import java.io.IOException;  
  
public class Example implements Appendable {  
    private StringBuilder sb;  
  
    public Example() {  
        sb = new StringBuilder();  
    }  
  
    @Override  
    public Appendable append(CharSequence csq) throws IOException {  
        sb.append(csq);  
        return this;  
    }  
  
    @Override  
    public Appendable append(CharSequence csq, int start, int end) throws IOException {  
        sb.append(csq, start, end);  
        return this;  
    }  
  
    @Override  
    public Appendable append(char c) throws IOException {  
        sb.append(c);  
        return this;  
    }  
  
    @Override  
    public String toString() {  
        return sb.toString();  
    }  
}

在上面的示例中,我们创建了一个自定义的 Example 类,该类实现了 Appendable 接口。然后,我们重写了 append 方法来将内容追加到 StringBuilder 中。最后,我们提供了 toString 方法来返回追加的内容。

通过实现 Appendable 接口,我们可以使用 append 方法将内容追加到对象中,并最终将其转换为字符串。这种方式比直接使用字符串连接操作更加灵活和高效。

StringBuilder

基本特性

1)一个可变的的字符序列。提供了和SteingBuffer兼容的API。

2)StringBuilder是线程不安全的,此类设计用作简易替换为StringBuffer在正在使用由单个线程字符串缓冲区的地方。

3)StringBuilder的主要StringBuilder是append和insert方法,它们是重载的,以便接受任何类型的数据。 每个都有效地将给定的数据转换为字符串,然后将该字符串的字符附加或插入字符串构建器。

4)它的速度比StringBuffer快毕竟线程不安全换来的。

StringBuilder的继承图

实战


public class Test {
        public static void main(String[] args) {

            long startTime = 0L;
            long endTime = 0L;
            StringBuffer buffer = new StringBuffer("");

            startTime = System.currentTimeMillis();
            for (int i = 0; i < 80000; i++) {
                buffer.append(String.valueOf(i));
            }
            endTime = System.currentTimeMillis();
            System.out.println("StringBuffer的执行时间:" + (endTime - startTime));





            StringBuilder builder = new StringBuilder("");
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 80000; i++) {
                builder.append(String.valueOf(i));
            }
            endTime = System.currentTimeMillis();
            System.out.println("StringBuilder的执行时间:" + (endTime - startTime));


            String text = "";
            startTime = System.currentTimeMillis();
            for (int i = 0; i < 80000; i++) {
                text = text + i;
            }
            endTime = System.currentTimeMillis();
            System.out.println("String的执行时间:" + (endTime - startTime));

          
        long startTime1 = System.nanoTime();  
        for (int i = 0; i < 1000000; i++) {  
            str1 = "123" + "456" + "789";  
        }  
        long endTime1 = System.nanoTime();  
        System.out.println("String Concatenation Time: " + (endTime1 - startTime1) + " nanoseconds");  
          
        long startTime2 = System.nanoTime();  
        for (int i = 0; i < 1000000; i++) {  
            str2 = new StringBuilder("123").append("456").append("789").toString();  
        }  
        long endTime2 = System.nanoTime();  
        System.out.println("StringBuilder Concatenation Time: " + (endTime2 - startTime2) + " nanoseconds");
           
        }
    }

结论

在循环中,每执行一次 “+”,都会创建一个 String 对象,因此会有大量对象创建和回收的消耗。

简单来说,在循环中对同一个字符串对象做字符串拼接,优先选择 StringBuilder。

在单次拼接中,String 更快。

我们都知道 String str1 = "123" + "456" + "789"; 其实是等同于 String str1 = "123456789";的,而 StringBuilder 反而需要多次调用 append 方法。

总结

在Java中,String、StringBuffer和StringBuilder都是用于处理字符串的类,但它们在性能和功能上有所不同。以下是它们之间的主要区别:

  1. 不可变性
    • String:字符串是不可变的,这意味着一旦创建了一个字符串,就不能更改它。如果你需要修改字符串,实际上是创建了一个新的字符串。
    • StringBuffer和StringBuilder:这两个类允许你修改字符串。
  1. 线程安全
    • String:字符串是不可变的,所以它是线程安全的。
    • StringBuffer:此类的所有公共方法都是同步的,这意味着它们是线程安全的。但是,每次调用方法都需要进行同步,这可能会降低性能。
    • StringBuilder:此类的所有公共方法都是非同步的,这意味着它是非线程安全的。如果你在多线程环境中工作,可能需要额外的同步机制。
  1. 性能
    • 对于大量字符串的修改操作(如拼接),StringBuffer和StringBuilder通常比String更高效,因为它们避免了创建大量新的字符串对象。
  1. API
    • String:提供了很多用于操作字符串的静态方法,如toUpperCase()、toLowerCase()等。
    • StringBuffer和StringBuilder:提供了更多的方法来修改字符串,如append()、insert()、delete()等。
  1. 使用场景
    • 当你需要一个不可变的字符串时,可以使用String。
    • 当你需要一个线程安全的可变字符串时,字符串存在大量的修改操作时,可以使用StringBuffer。
    • 当你需要一个非线程安全的可变字符串时,字符串存在大量的修改操作时,可以使用StringBuilder。

总之,选择使用哪个类取决于你的具体需求,例如是否需要修改字符串、是否需要考虑线程安全以及性能要求等。

文章来源:https://blog.csdn.net/QQ736238785/article/details/135670340
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。