String、StringBuffer、StringBuilder三者的区别与理解:

String: 不可变的字符序列;底层使用char[]存储
StringBuffer: 可变的字符序列;线程安全的,效率低;底层使用char[]存储
StringBuilder: 可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储

在这里插入图片描述

几个问题:

1.理解什么是不可变,什么是可变?

观察String类底层:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

我们发现String类底层是一个char[]数组来存储字符串的,也是被final修饰的;代表这个数组一旦创建就不可更改.我们调用String类中的方法修改了字符串的内容之后,原来的字符串还在方法区中的字符串常量池中没有改变,只是返回了一个新的字符串.这就是不可变的意思.

		String s = "abc";
        String s1 = s.toUpperCase();
        System.out.println(s);//abc 之前的字符串s没有变
        System.out.println(s1);//ABC 返回新的字符串s1

观察StringBuffer底层:

 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
    
 public StringBuffer() {
        super(16);
    }

StringBuffer类继承了AbstractStringBuilder类,底层创建了一个长度是16的数组

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

StringBuffer底层也是一个char[]数组来存储字符串的,但是没有被final修饰,表示这个char[]数组是可变的.我们调用StringBuffer类中的方法修改了字符串的内容之后,原来的字符串还在方法区中的字符串常量池中也改变了,返回新的字符串和原来的是一样的,两个栈空间的对象名s,s1都指向方法区中字符串常量池中同一个地址.这就是可变的意思.

		StringBuffer s = new StringBuffer("abc");
        StringBuffer s1 = s.append("def");
        System.out.println(s);//abcdef 原来的字符串s改变了
        System.out.println(s1);//abcdef 返回的新的字符串s1

2.为什么可变?

StringBuffer继承于抽象类AbstractStringBuilder,观察AbstractStringBuilder类中的append方法:

  public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }


public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
    {
        if (srcBegin < 0)
            throw new StringIndexOutOfBoundsException(srcBegin);
        if ((srcEnd < 0) || (srcEnd > count))
            throw new StringIndexOutOfBoundsException(srcEnd);
        if (srcBegin > srcEnd)
            throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }//调了System类中的arraycopy方法:

System类中的arraycopy方法:

	   @param      src      the source array.
     * @param      srcPos   starting position in the source array.
     * @param      dest     the destination array.
     * @param      destPos  starting position in the destination data.
     * @param      length   the number of array elements to be copied.
     * @exception  IndexOutOfBoundsException  if copying would cause
     *               access of data outside array bounds.
     * @exception  ArrayStoreException  if an element in the <code>src</code>
     *               array could not be stored into the <code>dest</code> array
     *               because of a type mismatch.
     * @exception  NullPointerException if either <code>src</code> or
     *               <code>dest</code> is <code>null</code>.
     */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

观察发现:StringBuffer继承于抽象类AbstractStringBuilder,他的内部实现靠他的父类完成,AbstractStringBuilder内的char数组是变量,可以用append追加,StringBuffer的append方法的底层实现就是创建一个新的目标对象,然后将各个字符引用串接起来,这就是一个新的对象了,本质上就是栈多了一个变量,而堆上面并没有变化(为什么是在栈上呢?因为触发到的arraycopy方法是native的,也就是其生成的变量会放到本地方法栈上面去)

StringBuffer的append方法并不会在堆上创建新的StringBuffer对象且内容是结果字符串,而是在arraycopy方法的帮助下,将各个字符引用连接起来

3.扩容原理

StringBuffer和StringBuilder底层的char[]数组都有扩容机制,在AbstractStringBuilder类中有一个方法

private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

使用append()方法添加数据时,如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组.

默认情况下,扩容为原来容量的2倍 + 2,同时将原数组中的元素复制到新的数组中。

StringBuffer和StringBuilder类似,方法也一样,只是StringBuilder是单线程操作字符串,线程不安全;

而StringBuffer是多线程操作字符串,是线程安全的.在开发中我们根据实际需要选择使用.

4. StringBuffer、StringBuilder中的常用方法

增:append(xxx)
删:delete(int start,int end)
改:setCharAt(int n ,char ch) / replace(int start, int end, String str)
查:charAt(int n )
插:insert(int offset, xxx)
长度:length();
遍历:for() + charAt() / toString()

5.一个面试题:

问: String s = new String(“abc”);方式创建对象,在内存中创建了几个对象?
答: 两个:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:“abc”

<think>嗯,用户之前问过关于String能否被继承的问题,现在又接着问StringStringBufferStringBuilder区别。看来他们可能是在学习Java字符串处理相关的知识,或者是在准备面试题。我需要详细分析这三个区别,确保回答全面且易懂。 首先,要明确这三个的用途和设计目的。String是不可变的,而StringBufferStringBuilder是可变的。但用户可能需要知道为什么会有这样的设计,以及在实际应用中如何选择。 然后,线程安全是一个重点。StringBuffer是线程安全的,因为它的方法都是同步的,而StringBuilder不是。需要解释清楚线程安全的概念,以及在不同场景下的影响。 性能比较也很重要。通常StringBuilder在单线程下更快,但为什么?需要提到同步带来的开销,以及字符串操作的频繁程度如何影响性能。 接下来,可能需要举例说明它们的用法差异。比如字符串拼接时,使用String会生成多个对象,而使用StringBuilderStringBuffer可以避免这种情况,提高效率。 用户可能还会关心什么时候应该用哪个。比如在单线程环境下推荐使用StringBuilder,多线程则用StringBuffer,而String适用于不需要修改字符串内容的情况。 此外,需要提到JVM对String的优化,比如字符串常量池,以及如何通过final关键字保证不可变性。还有,StringBufferStringBuilder的API几乎相同,但同步处理不同。 要避免过于技术化的术语,用简单易懂的语言解释。可能还要对比它们的继承结构,比如都继承自AbstractStringBuilder,但String是独立的。 最后,总结三者区别,强调线程安全、可变性和性能,并给出使用建议。这样用户能有一个清晰的比较,方便记忆和应用。</think>--- ### 🚩 核心区别总结 | 特性 | `String` | `StringBuffer` | `StringBuilder` | |--------------------|---------------------------|---------------------------|---------------------------| | **可变性** | 不可变 | 可变 | 可变 | | **线程安全** | 天然安全(不可变) | 安全(同步方法) | 不安全(无同步) | | **性能** | 高频修改性能差 | 线程安全但较慢 | 单线程下最快 | | **内存开销** | 易产生大量中间对象 | 对象复用 | 对象复用 | | **适用场景** | 常量字符串/少量操作 | 多线程字符串操作 | 单线程字符串操作 | --- ### 1️⃣ **不可变的 `String`** ```java String str = "Hello"; str += " World"; // 实际生成新对象:效率低下 ``` #### 核心特点: - **不可变性**:每次操作都生成新对象(适合缓存、哈希键等场景) - **字符串常量池优化**:相同字面量复用对象(`"a" + "b"` 编译期优化为 `"ab"`) - **线程安全**:无需额外同步(天然线程安全) #### 典型使用场景: - 配置文件读取 - HTTP 请求参数 - 数据库连接字符串等**不需要修改**的场景 --- ### 2️⃣ **线程安全的 `StringBuffer`** ```java StringBuffer sb = new StringBuffer(); sb.append("Hello").append(" World"); // 直接修改原对象 ``` #### 核心特点: - **同步方法**:所有关键方法用 `synchronized` 修饰(线程安全但性能较低) - **初始容量 16**:自动扩容(每次扩容 `旧容量*2 + 2`) - **方法链调用**:支持链式写法(`append().insert().delete()`) #### 典型使用场景: - 多线程环境下的日志拼接 - 并发服务器中的请求处理 - **JDK 1.0** 开始存在(早期方案) --- ### 3️⃣ **高性能的 `StringBuilder`** ```java StringBuilder sb = new StringBuilder(); sb.append("Java").reverse(); // 直接修改原对象 ``` #### 核心特点: - **无同步开销**:方法未加锁(单线程性能比 `StringBuffer` 高约 **10-15%**) - **初始容量 16**:扩容逻辑 `StringBuffer` 相同 - **JDK 1.5+** 引入:为单线程场景优化 #### 典型使用场景: - 单线程下的 JSON/XML 构建 - 循环体内的字符串处理 - 高频字符串操作(如大数据处理) --- ### ⚡️ 性能对比测试 ```java // 测试代码片段 public void testPerformance() { int N = 100000; // String 测试 long start = System.currentTimeMillis(); String s = ""; for (int i = 0; i < N; i++) { s += i; // 每次循环创建新对象 } System.out.println("String: " + (System.currentTimeMillis() - start) + "ms"); // StringBuffer 测试 start = System.currentTimeMillis(); StringBuffer sb1 = new StringBuffer(); for (int i = 0; i < N; i++) { sb1.append(i); } System.out.println("StringBuffer: " + (System.currentTimeMillis() - start) + "ms"); // StringBuilder 测试 start = System.currentTimeMillis(); StringBuilder sb2 = new StringBuilder(); for (int i = 0; i < N; i++) { sb2.append(i); } System.out.println("StringBuilder: " + (System.currentTimeMillis() - start) + "ms"); } ``` #### 典型结果(10万次循环): - `String`: 约 **5000ms**(产生大量垃圾对象) - `StringBuffer`: 约 **10ms** - `StringBuilder`: 约 **5ms** --- ### 🛠️ 选型建议 1. **优先用 `String`** - 数据不经常变化 - 需要作为常量使用(如 `Map` 的键) 2. **多线程操作 → `StringBuffer`** - 多线程日志处理 - 并发环境下的字符串组装 3. **单线程操作 → `StringBuilder`** - 循环体内字符串操作 - SQL 语句拼接 - 高频字符串处理 --- ### 📌 底层实现揭秘 #### 共有特性(`StringBuffer`/`StringBuilder`): ```java // AbstractStringBuilder 中的核心存储 char[] value; // 动态数组存储字符 int count; // 实际字符数量 ``` #### 扩容机制: 1. 计算新容量 = `原容量*2 + 2` 2. 如果仍不足,直接扩容到**目标长度** 3. 调用 `Arrays.copyOf()` 复制数据 --- ### ❌ 常见误区 1. **错误认知**:`StringBuilder` 在所有场景都比 `StringBuffer` 快 **真相**:在单线程下更快,但在多线程中频繁同步时可能更慢 2. **错误写法**:在循环中用 `+` 拼接字符串 **修正**:应使用 `StringBuilder`(编译器不会优化循环内的 `+` 操作) 3. **过度优化**:预设容量(仅在超大数据处理时有意义) ```java // 合理预设容量(减少扩容次数) StringBuilder sb = new StringBuilder(1024); ``` --- **总结**:理解三者的差异是写出高效 Java 代码的基础,根据线程安全和性能需求做出精准选择。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值