String、StringBuffer和StringBuild 那些事
String
基本特性
- String是一个final类,代表不可变的字符序列。
- 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
- String对象的字符内容是存储在一个字符数组value[]中的。
String的继承图
Serializable
在 Java 中,Serializable 是一个标记接口,没有任何方法需要实现。它的主要用途是用于序列化对象,使得对象可以被转换为字节流,从而可以轻松地保存到文件或通过网络发送到另一个进程或机器。
序列化是将对象的状态信息转换为可以存储或传输的形式的过程。通过实现 Serializable 接口,一个类的对象可以被序列化,这意味着它们可以被转换为字节流。然后,这些字节流可以存储到文件、数据库或通过网络发送到任何地方。之后,这些字节流可以被反序列化回其原始对象状态。
以下是一些使用 Serializable 接口的常见场景:
- 持久化存储:对象可以被序列化并保存到文件中,以便在程序重新启动或需要时恢复它们的状态。
- 网络传输:对象可以被序列化并通过网络发送到另一个进程或机器。例如,通过网络传输对象状态信息到客户端。
- 单元测试:在单元测试中,对象的状态信息经常需要被捕获和比较。通过序列化对象,可以将它们的状态信息保存到文件或数据库中,以便稍后进行比较。
- 分布式系统:在分布式系统中,对象的状态信息经常需要在不同的节点之间共享。通过序列化对象,可以将它们的状态信息发送到其他节点。
要使一个类可序列化,只需实现 Serializable 接口即可。但是,如果一个类中有不可序列化的字段(例如,字段的类型不是可序列化的),则需要在该字段上使用 transient 关键字进行标记。这样,该字段的值将不会被序列化,而是会使用默认值进行反序列化。
Comparable
在Java中,Comparable接口是一个泛型接口,用于定义对象之间的自然顺序。通过实现Comparable接口,一个类可以指定其元素的排序方式。
当一个类实现了Comparable接口,就意味着它支持某种形式的比较语义,使得可以对类的实例进行排序和比较。要实现Comparable接口,类需要实现compareTo方法,该方法用于比较当前对象与另一个对象的大小关系。
实现Comparable接口的好处是:
- 类型安全:使用泛型,可以在编译时捕获类型错误。
- 灵活性:可以在运行时改变对象的排序规则。
- 自动排序:可以使用Java集合框架中的排序算法(如Collections.sort())对实现了Comparable接口的对象进行排序。
下面是一个简单的示例,演示如何使用Comparable接口对一个简单的Person类进行排序:
java
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接口,并重写了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 接口:
ini
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 对象的两种方式
- 直接赋值:String s = "涛涛之海";
这种方式它首先会先从常量池查看是否有"涛涛之海" 这个数据空间,如果有就直接指向,如果没有就创建一个"涛涛之海"这个数据空间然后指向它。注意s最终指向的是常量池的空间地址。
- 调用构造器 String s1= new String("涛涛之海");
这种方式则是先在堆中创建空间,里面维护了value属性,指向常量池的"涛涛之海"空间。如果常量池中没有''涛涛之海'',则重新创建,如果有就直接通过value指向。注意这里最终指向的是堆中的空间地址
StringBuffer
基本特性
- 线程安全,可变的字符序列。 字符串缓冲区就像一个String ,但可以修改。
- 字符缓冲可以安全的被多个线程使用。前提是这些方法必须进行同步
- 每个字符串缓冲区都有一个容量。 只要字符串缓冲区中包含的字符序列的长度不超过容量,就不必分配新的内部缓冲区数组。 如果内部缓冲区溢出,则会自动变大
StringBuffer的继承图
Appendable
在Java中,Appendable 接口是一个标记接口,用于表示一个对象可以被追加内容。它没有定义任何方法,只是作为一个类型标记来指示一个对象支持追加操作。
Appendable 接口通常与 String 构造器一起使用,以便将可变的内容追加到字符串中。通过实现 Appendable 接口,对象可以表明它支持追加操作,并提供一个用于追加内容的 append 方法。
以下是一个使用 Appendable 接口的示例:
java
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的继承图
实战
ini
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都是用于处理字符串的类,但它们在性能和功能上有所不同。以下是它们之间的主要区别:
- 不可变性:
-
- String:字符串是不可变的,这意味着一旦创建了一个字符串,就不能更改它。如果你需要修改字符串,实际上是创建了一个新的字符串。
- StringBuffer和StringBuilder:这两个类允许你修改字符串。
- 线程安全:
-
- String:字符串是不可变的,所以它是线程安全的。
- StringBuffer:此类的所有公共方法都是同步的,这意味着它们是线程安全的。但是,每次调用方法都需要进行同步,这可能会降低性能。
- StringBuilder:此类的所有公共方法都是非同步的,这意味着它是非线程安全的。如果你在多线程环境中工作,可能需要额外的同步机制。
- 性能:
-
- 对于大量字符串的修改操作(如拼接),StringBuffer和StringBuilder通常比String更高效,因为它们避免了创建大量新的字符串对象。
- API:
-
- String:提供了很多用于操作字符串的静态方法,如toUpperCase()、toLowerCase()等。
- StringBuffer和StringBuilder:提供了更多的方法来修改字符串,如append()、insert()、delete()等。
- 使用场景:
-
- 当你需要一个不可变的字符串时,可以使用String。
- 当你需要一个线程安全的可变字符串时,字符串存在大量的修改操作时,可以使用StringBuffer。
- 当你需要一个非线程安全的可变字符串时,字符串存在大量的修改操作时,可以使用StringBuilder。
总之,选择使用哪个类取决于你的具体需求,例如是否需要修改字符串、是否需要考虑线程安全以及性能要求等。