String、StringBuilder和StringBuffer的区别?面试回答

面试回答

String 是不可变的,StringBuilder 和 StringBuffer 是可变的。而 StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的。

知识扩展

String 的不可变性

String 在 Java 中特别常用,相信很多人都过他的源码,在 JDK 中,关于 String 的类声明是这样的:

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

    /** Cache the hash code for the string */
    private int hash; // Default to 0
} 

可以看到,String 类是 final 类型的,表示这个类不可以被继承。

其次,String 中存储的 char[] 也是被 final 修饰的,表示他也是不能修改的。

所以,String 是一个不可变对象。

不可变对象是在完全创建后其内部状态保持不变的对象。这意味着,一旦对象被赋值给变量,我们既不能更新引用,也不能通过任何方式改变内部状态。

可是有人会有疑惑,String 为什么不可变,我的代码中经常改变 String 的值啊,如下:

        String str="abcd";
        str=str.concat("ef");

这样操作,不就将原本的 “abcd”的字符串改变成“abcdef”了么?

我们看一下 concat 的源码:

    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

所以,虽然字符串内容看上去从“abcd”变成了“abcdef”,但是实际上,我们得到的已经是一个新的字符串了。

 

如上图,在堆中重新创建了一个“abcdef”字符串,和“abcd”并不是同一个对象。

所以,一旦一个 string 对象在内存(堆)中创建出来,他就无法被修改。而且,String 类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。

如果我们想要一个可修改的字符串,可以选择 StringBuffer 或者 StringBuilder 这两个代替 String

String 的“+”是如何实现的

使用+拼接字符串,其实只是 Java 提供的一个语法糖,那么,我们来解一解这个语法糖,看看他的内部原理到底是如何实现的。

还是这样一段代码。我们把他生成的字符码进行反编译,看看结果。

        String str1="hello";
        String str2="world";
        String text=str1+str2;

反编译后的内容如下,反编译工具为 jad。

        String str1="hello";
        String str2="world";
        String text=(new StringBuilder()).append(str1).append(str2).toString();

通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将 String 转成了 StringBuilder 后,使用其 append 方法进行处理的。

那么也就是说,Java 中的堆+对字符串的拼接,其实现原理是使用 StringBuilder.append。

StringBuffer 和 StringBuilder

接下来我们看看 StringBufferStringBuilder 的实现原理

String 类类似,StringBuilder 类也封装了一个字符数组,定义如下:

char[] value;

其 append 源码如下:

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

该类继承了 AbstractStringBuilde 类,看下其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;
    }

append 会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。

StringBufferStringBuilder类似,最大的区别就是 StringBuffer是线程安全的,看一下 StringBufferappend方法。

    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

该方法使用 synchronized进行说明,说明是一个线程安全的方法。而 StringBuilder则不是线程安全的。

不要在 for 循环中使用 + 拼接字符串

前面我们分析过,其实使用 +拼接字符串的实现原理也是使用的 StringBuilder,那为什么不建议大家在 for 循环中使用呢?

我们把以下代码反编译下:

        long t1=System.currentTimeMillis();
        String str="tango";
        for (int i = 0; i <50000 ; i++) {
            String s=String.valueOf(i);
            str+=s;
        }
        long t2=System.currentTimeMillis();
        System.out.println("+ cost:"+(t2-t1));

反编译后代码如下:

        long t1 = System.currentTimeMillis();
        String str = "tango";
        for(int i = 0; i < 50000; i++)
        {
            String s = String.valueOf(i);
            str = (new StringBuilder()).append(str).append(s).toString();
        }

        long t2 = System.currentTimeMillis();
        System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());

我们可以看到,反编译后的代码,在 for 循环中,每次都是 new 了一个 StringBuilder,然后把 String 转成 StringBuilder ,再进行 append

而频繁的新建对象当然要耗费很多时间了,不仅仅会耗费时间,频繁的创建对象,还会造成内存资源的浪费。

所以,阿里巴巴 Java 开发手册建议:循环体内,字符串的连接方式,使用 StringBuilderappend 方法进行扩展。而不是使用 +

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

协享科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值