一篇不一样的StringBuffer和StringBuilder

StringBuffer和StringBuilder相信大家都不陌生,我与它们之间也有过一段不愉快的经历。在一次面试过程中,面试官先是问了我的兴趣爱好,虽然我平时喜欢看书,音乐,踢球,跑步等等。但是那段时间为了面试没日没夜的看书,刷资料。当他问到我兴趣爱好时,脑子一片空白、答曰:“看书”。然后迎来的却是面试官的一阵嘲笑和质疑,我当时就已经很不舒服了,喜欢看书很奇怪吗?我本来就是一个转行生,不看书不学习,我拿什么支撑这一份工作?更何况我有过一次考研失败的经历,结果虽然不好,但是过程却培养了我能够静坐下来看一整天书的耐心。

开始技术问题面试后,我更是一脸懵逼,在我明明正确回答了StringBuffer和StringBuilder的区别、HashMap和HashTable的区别几个问题后,面试官再次表示质疑,他认为我说反了。这让我脑子一片混乱,临走时面试官还让我自己回去查。我还用回家查吗?我出了公司门就掏出手机,打开印象笔记,确认了的确是面试官自己弄混淆了。所以,我当时就萌生了要写一篇关于这两个问题的博客的想法。查阅资料,跑Demo,从源码角度去分析,真正的掌握它们。当下一次面试再遇到诸如此类问题时,不再仅仅从表面去回答,不再背出面经中的答案,而是自信得讲出自己的理解。

下面我将从:浅析区别,性能、部分源码解读,历史原因及选用。三个方面试图完整的解读StringBuffer和StringBuilder。希望读者看后既能游刃有余于面试题中,又能对日常开发提供一些支撑。

1.浅析区别

在进入本文主题之前,先提一下String。因为很多面试官会连带一起问,而涉及该问的String部分又比较简单,便一并讲述清楚,也有助于整体理解字符串储存这部分知识。

  • String

String可以用来保存字符串,一旦生成一个String对象之后,对其的任何改变操作,都会造成新的字符串生成。因此,当频繁操作(改变)String对象的时候,就会产生大量的垃圾对象。不仅导致不必要的内存消耗,当达到一定内存占用之后,就会引起JVM的GC操作,GC属于耗时操作,必然带来系统性能上的急剧下滑。

  • StringBuffer

StringBuffer也可以用来保存字符串,并且它的目的就是为了解决String类对象保存字符串时的尴尬处境。当我们对StringBuffer对象操作并改变时,它不会像String那样产生新的对象,而是对StringBuffer对象本身进行操作甚至改变。
StringBuffer上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而insert方法则在指定的点添加字符。
除此之外,由于StringBuffer类中又大量的synchronize修饰,它是一个线程安全的类。(牵涉到更多线程安全的问题将在第三部分详细讲述)

  • StringBuilder

在jdk1.5之后,Java又新增了一个StringBuilder类,它是作为StringBuffer的建议替换出现的,它们兼容同一套API。但是StringBuilder不再有大量的synchronize修饰,因此它是非线程安全的。亦因此在实际运行中,不再存在由同步引起的额外开销,从而带来性能上的又一些提升。

2.性能、源码剖析

先简单看下为什么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修饰的类,它是不能被继承的。虽然被final修饰的类,其中的属性和方法可以选择性的由final修饰与否。而String中字符串的保存是由字符数组来完成的,这个字符数组value恰好也由final修饰。我们知道,数组本身是属于引用变量类型,当final修饰引用类型变量后,它的指针是不可以更改的。所以String对象一旦生成并且有了指针指向,那么它是不可更改的。
有下列部分源码可知,当我们对String对象进行操作,试图更改它,无论是sub、concat还是replace都不是在原有的字符串上进行的,而是重新生成一个新的字符串对象。也就是说,对String对象的任何改变都不影响到原对象。

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
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);
    }
 public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

可以看到,这三个方法的返回值都是new了一个String对象。当然,他们调用的String构造方法各不相同。
例如,concat方法就是在新声明的字符数组NC拷贝了原String对象内的字符数组之后,再将(增添)目标String对象中的字符数组OC拼接在NC之后,最后调用如下构造方法,并传入NC。

String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }

关于这部分,如果想全面了解请自行阅读源码。

…由于在写博客的时候又冒出几个问题,我都还没弄懂。稍后接着写。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值