String、StringBuffer与StringBuilder的区别

StringStringBuffer与StringBuilder的区别

在Java 编程中会广泛应用到字符串,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。需要注意的是,String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。为了应对经常性的字符串相关的操作,谷歌引入了两个新的类——StringBuffer 类和StringBuild 类来对此种变化字符串进行处理。

一、String

String 是被 final 修饰的类,不能被继承;String实现了 Serializable 和 Comparable 接口,表示String支持序列化和可以比较大小;String底层是通过char类型的数据实现的,并且被final修饰,所以字符串的值创建之后就不可以被修改,具有不可变性。

String 类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。

例如:

String a = "123";

a = "456";



System.out.println(a) // 打印出来的a为456

咦,这不是明明已经对他进行修改了吗?为什么说他是一个不可变类呢?接下来就看一张上述a对象的内存存储空间图

可以看出,再次给a赋值时,并不是对原来堆中实例对象进行重新赋值,而是生成一个新的实例对象,并且指向“456”这个字符串,a则指向最新生成的实例对象,之前的实例对象仍然存在,如果没有被再次引用,则会被垃圾回收。

String实例化的方式

1.通过字面量方式实例化

String str = "abc";

2.通过new+构造器的方式实例化

String str=new String("abc");

区别:通过字面量方式为字符串赋值时,此时的字符串存储在方法区的字符串常量池中;通过new+构造器方式实例化字符串时,字符串对象存储在堆中,但是字符串的值仍然存储在方法区的常量池中。

String字符串具有不可变性,当字符串重新赋值时,不管是对字符串进行拼接,还是调用String的replace()方法修改指定的字符或字符串,都不会在原来的内存地址进行修改,而是重新分配新的内存地址进行赋值。

二、StringBuffer

StringBuffer对象代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列,但都不会产生新的对象。通过StringBuffer生成的字符串,可以调用toString()方法将其转换为一个String对象。

例如:

StringBuffer b = new StringBuffer("123");

b.append("456");



System.out.println(b); // b打印结果为:123456

看一下b对象的内存空间图:

可以看到它没有重新生成一个对象,而且在原来的对象中可以连接新的字符串。所以说StringBuffer对象是一个字符序列可变的字符串

三、StringBuilder

StringBuilder类也代表可变字符串对象。实际上,StringBuilder和StringBuffer基本相似,他们的原理与操作一样,两个类的构造器和方法也基本相同。不同的是:StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。

四、StringBuffer如何实现线程安全

StringBuffer类中实现的方法:

@Override

public synchronized int length() {

    return count;

}



@Override

public synchronized int capacity() {

    return value.length;

}

StringBuilder类中实现的方法:

@Override

public StringBuilder replace(int start, int end, String str) {

    super.replace(start, end, str);

    return this;

}

可以看到,StringBuffer类中的方法都添加了synchronized关键字,也就是给这个方法添加了一个锁,用来保证线程安全。

五、StringBufferStringBuilder的区别

1.线程安全

StringBuffer 线程安全,StringBuilder 线程不安全。因为 StringBuffer 的所有公开方法都是 synchronized修饰的,而 StringBuilder 并没有synchronized修饰。

2.缓冲区

StringBuffer 每次获取 toString 都会直接使用缓存区的 toStringCache 值来构造一个字符串。StringBuffer 的这个toString 方法仍然是同步的。

而 StringBuilder 则每次都需要复制一次字符数组,再构造一个字符串。

3.性能

StringBuffer 是线程安全的,它的所有公开方法都是同步的,StringBuilder 是没有对方法加锁同步的,所以StringBuilder 的性能要大于 StringBuffer。

StringBuffer 适用于用在多线程操作同一个 StringBuffer 的场景,而 StringBuilder 更适合单线程场合。

六、StringStringBufferStringBuilder的异同

相同点: 都用来代表字符串,底层都是通过char数组实现的。

不同点:

1.String类是不可变类,String对象一旦创建,其值是不能修改的,如果要修改,会重新开辟内存空间来存储修改之后的对象;而StringBuffer和StringBuilder对象的值是可以被修改的;即任何对String的改变会引发新的String对象的生成。

2.StringBuffer和StringBuilder类则是可变类,他俩的原理和操作基本相同,任何对它所指代的字符串的改变都不会产生新的对象。StringBuffer几乎所有的方法都使用synchronized实现了同步,支持并发操作,线程安全,在多线程系统中可以保证数据同步,但是效率比较低;而StringBuilder线程不安全,不支持并发操作,不能同步访问,不适合多线程中使用。但是效率比较高。

3.需要对字符串进行频繁的修改,不要使用String,否则会造成内存空间的浪费。考虑线程安全的场合使用 StringBuffer,如果不需要考虑线程安全,追求效率的场合可以使用 StringBuilder。

七、StringBuffer的扩容机制

StringBuffer和StringBuilder都是继承自AbstractStringBuilder,它们两个的区别在于buffer是线程安全的,builder是线程不安全的,前者安全效率低,后者高效不安全。它们的扩容机制也是这样的区别,所以我们只需要分析一个的扩容就可以了,分析buffer,另一个只用把synchronized关键字去掉就是一样的。

1、初始扩容

既然是容器,那么是一定会有个初始容量的,目的在于避免在内存中过度占用内存。容器的初始容量有默认和使用构造函数申明两种。

StringBuffer类可以创建可修改的字符串序列,该类有以下三个改造方法。

1.StringBuffer()的初始容量可以容纳16个字符,当该对象的实体存放的字符的长度大于16时,实体容量就自动增加。StringBuffer对象可以通过length()方法获取实体中存放的字符序列长度,通过 capacity()方法来获取当前实体的实际容量。

2.StringBuffer(int size)可以指定分配给该对象的实体的初始容量参数为参数size指定的字符个数。当该对象的实体存放的字符序列的长度大于size个字符时,实体的容量就自动的增加。以便存放所增加的字符。

3.StringBuffer(String s)可以指定给对象的实体的初始容量为参数字符串s的长度额外再加16个字符。当该对象的实体存放的字符序列长度大于size个字符时,实体的容量自动的增加,以便存放所增加的字符。

2、如何扩容

首先我们需要知道StringBuffer和StringBuilder类都继承了抽象类 AbstractStringBuilder类。

查看父类 AbstractStringBuilder的构造函数,发现底层是一个字符数组来保存字符串的。

AbstractStringBuilder(int capacity) {

        value = new char[capacity];

    }

再来看看StringBuffer的构造函数

无参构造函数,默认容量为16

public StringBuffer() {

        super(16);

    }

带参(参数为字符串)构造函数,默认容量为参数字符串长度加上16

public StringBuffer(String str) {

        super(str.length() + 16);

        append(str);

    }

带参(参数为int容量)构造函数,容量大小为指定的大小

public StringBuffer(int capacity) {

        super(capacity);

    }

源码都调用父类来进行初始化。

3、扩容原理

算法原理:

使用append()方法在字符串后面追加值的时候,如果长度超过了该字符串存储空间大小了就就会先进性扩容。构建新的并且存储空间更大的字符串,将旧的复制过去。

在进行字符串append添加的时候,会先计算添加后字符串大小,传入一个方法:ensureCapacityInternal 这个方法进行是否扩容的判断,需要扩容就调用expandCapacity方法进行扩容。

扩容规则:

先 原始容量 * 2 + 2(加2是因为拼接字符串通常末尾都会有个多余的字符)

如果扩容了之后,容量够用,新的容量就为扩容之后的容量。

如果扩容了之后,容量不够用,新的容量就是所需要的容量,即原始字符串长度加上新添加的字符串长度。

扩容完成之后,将原始数组复制到新的容量中,然后将新的字符串添加进去

Java 中,`String`, `StringBuilder` `StringBuffer` 是处理字符串的三个常用类。它们各有特点,适用于不同的应用场景。下面是关于这三个类的主要区别的详细介绍: ### 1. **不可变性 vs 可变性** - **String** - 特点:`String` 类的对象是**不可变**的(immutable),即每次对 `String` 对象的操作都会创建新的对象。 - 示例: ```java String str = "Hello"; str += " World"; // 实际上生成了一个新对象:"Hello World" ``` - **StringBuilder & StringBuffer** - 特点:这两个类都是**可变**的(mutable),可以在原对象基础上直接修改内容而不必每次都新建对象。 - 这使得频繁操作字符串时效率更高。 ### 2. **线程安全** - **String** - 线程安全否并不适用,因为它本身就是不可变的,所以不用担心多线程环境下的同步问题。 - **StringBuffer** - 提供了内置的线程安全保障机制,所有的方法都被 synchronized 关键字修饰,因此它是线程安全的。 - 因此,在多线程环境中使用更为合适,但由于锁机制的存在,性能相对较低。 - **StringBuilder** - 没有提供任何形式的线程安全性措施,所以在单线程环境下运行速度更快。 - 单线程或不需要考虑并发的情况下推荐优先选择 `StringBuilder`。 ### 3. **性能对比** 由于 `StringBuffer` 的所有方法都带有同步控制,所以在高频率更新字符串的情景下,它的开销比 `StringBuilder` 要大得多。相比之下,`StringBuilder` 更加高效,但在需要保证线程安全的地方就不如前者。 #### 性能测试示例: ```java public class PerformanceTest { public static void main(String[] args) throws InterruptedException { long start; StringBuilder sb = new StringBuilder(); start = System.currentTimeMillis(); for (int i = 0; i < 50000; ++i) sb.append("a"); System.out.println("Time taken by StringBuilder: " + (System.currentTimeMillis() - start)); StringBuffer sf = new StringBuffer(); start = System.currentTimeMillis(); for (int i = 0; i < 50000; ++i) sf.append("a"); System.out.println("Time taken by StringBuffer: " + (System.currentTimeMillis() - start)); // 测试 String (这里仅做展示目的) String s = ""; start = System.currentTimeMillis(); for (int i = 0; i < 50000; ++i) s += "a"; System.out.println("Time taken by String concatenation: " + (System.currentTimeMillis() - start)); } } ``` 从上述代码可以看出,在循环中进行大量拼接操作时,`StringBuilder` 明显快于 `StringBuffer`,而 `String` 的表现最差。 综上所述, - 如果你需要一个简单的字符串并且不打算更改它,那就用 `String`. - 若是在单一任务内构建大量的动态文本,请选用 `StringBuilder`. - 当涉及到多线程并行访问同一个缓冲区的情况,则应采用 `StringBuffer`.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值