StringBuilder,String与StringBuffer 的异同

引言

根据我在网上查到的资料显示,这三者的区别主要是: String:字符串常量 StringBuffer:字符创变量(多线程) StringBuilder:字符创变量(单线程) 对String的操作表面上看是对同一个变量的操作,但实际上是新建了一个常量,然后修改对象的引用。基于这样的机制,需要不停的GC旧的对象,其效率也很低下。 而StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,当然速度快。

源码分析

上述是一般博客上都能查到的东西,但是我们还是要看一下源码的具体实现,比如,为啥一个是常量一个是变量。

成员变量

StringBuffer与StringBuilder类似,故只介绍string与StringBuilder的对比。

String:

/** 用来存放字符串的内容 */
    private final char value[];
/** 缓存本字符串的hash值 */
    private int hash; // 默认为0
/** 因为实现了Serializable接口,所以需要定义一个序列化ID */
    private static final long serialVersionUID = -6849794470754667710L;

StringBuilder:

/** 同上,序列化ID */
    static final long serialVersionUID = 4383685877147921099L;

因为StringBuilder继承的是AbstractStringBuilder,很多方法都是直接调用父类的方法,变量也是使用父类的变量。 AbstractStringBuilder:

/**用来存放字符串的内容.*/
    char[] value;
/** 这个变量用来统计已经使用的字符数,字符数组不一定所有的空间都被使用.*/
    int count;

对比很明显,String对象的字符数组直接被声明为final,一旦被赋值就不能再修改。所以String被称为常量。

构造函数

String:(过时的不介绍)

 /**
     创建一个空串,但是这个构造函数没有必要,因为数组不可变,一旦被定义,那就是一个空串,但这毫无意义。
     */
    public String() {
        this.value = "".value;
    }

    /**
    以字符串为参数构造,把该字符串的hash值及内容赋值给新的字符串。
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    /**
     把字符数组的内容赋值给新的字符串
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

    /**
     把字符数组的一部分赋给新的字符串
     */
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

    /**
    将存放unicode编码的数组赋值到String中去
     */
    public String(int[] codePoints, int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= codePoints.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > codePoints.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }

        final int end = offset + count;

        // Pass 1: Compute precise size of char[]
        int n = count;
        for (int i = offset; i < end; i++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                continue;
            else if (Character.isValidCodePoint(c))
                n++;
            else throw new IllegalArgumentException(Integer.toString(c));
        }

        // Pass 2: Allocate and fill in char[]
        final char[] v = new char[n];

        for (int i = offset, j = 0; i < end; i++, j++) {
            int c = codePoints[i];
            if (Character.isBmpCodePoint(c))
                v[j] = (char)c;
            else
                Character.toSurrogates(c, v, j++);
        }

        this.value = v;
    }

值得一提的是,几乎所有的构造函数注释都提到一句,使用String的构造函数毫无意义。其实是因为如果使用String s = “ABC”,那么s是存放在字符串常量池中的,如果池中已经有字符串“ABC”,就不会新建一个字符串对象,而是会使用原来的对象。 但是,如果你使用String s1 = new String(s),那么,就会再java堆中重新开辟一篇内存区域用来存放这个s对象。最明显的区别莫过于s1 == s 的结果是false,毕竟起始地址不同,而且后者会浪费资源。

StringBuilder:

/**
     传入一个整数作为容量。
     */
    public StringBuilder(int capacity) {
        super(capacity);
    }

    /**
     传入一个字符串作为参数,把字符串的长度+16作为容量
     */
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }

    /**
     传入字符序列作为参数,把字符序列的长度+16作为容量,然后调用第一个构造函数
     */
    public StringBuilder(CharSequence seq) {
        this(seq.length() + 16);
        append(seq);
    }

以上都是调用父类的构造函数,而父类的构造函数只做了一件事情:

AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

初始化一个长度为capacity的字符数组。

从构造函数来看,二者都是给字符数组赋值,只不过String一般是调用Arraycopy方法一步到位,而String Builder一般是先新建一个一定长度的数组,再调用append方法。

内容的修改

String

最常用的莫过于以下的情况:

String s1 = "PangYuQing";
String s2 = "Pang";
String s3 = "YuQing";
String s4 = "Pang" + "YuQing";
String s5 = s2 + s3;
String s5s = s5.intern();
String s6 = "Pang" + s3;
String s6s = s6.intern();
String s7 = new String("PangYuQing");
String s7s = s7.intern();

其实以上就两种情况,s4是一种,s5,s6是一种。s4经过jvm的优化,其实质就是String s4 = “PangYuQing”。后者,便转化为new StringBuilder.append(s2).append(s3)。然后再调用toString方法。因为需要额外生成一个String对象,速度自然变慢。 而s5s,s6s,s7s,官方API的解释是“当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串 (用equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。”

StringBuilder

一般使用append方法,以下选几个常用的介绍:

@Override
    public StringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
@Override
    public StringBuilder append(char[] str) {
        super.append(str);
        return this;
    }

可以看到,其都是调用父类的append方法。 而父类的方法是:

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        //把str的内容拷贝到value里去
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

其中

    /**
     如果需要的容量大于当前数组的长度,则进行扩容
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

    /**
     扩容,如果栈溢出则把容积置为Integer.MAX_VALUE
     */
    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }

值得注意的一点是,char[] value是父类私有的成员,所以StringBuilder中所有对内容的修改都是调用父类的方法类完成。

可以看到,在修改内容方面,String新建一个String对象或者往字符串常量池中添加常量,而StringBuiler是直接对原有的字符串数组扩容后添加字符。

小结

各位还是用StringBuilder吧。。。。

tips

发现一个有趣的事情

private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

原来这个方法是真往字符串后面加个null啊。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开发之途

Java集合框架源码解析之LinkedList

1083
来自专栏宋凯伦的技术小栈

【工作中学习2】Map的使用及排序(第三个参数)

  项目进行中,使用到Map(std::map),Map要点整理如下:   1. Map,也叫关联数组,提供key/value(键/值对),key用来索引,va...

17510
来自专栏xingoo, 一个梦想做发明家的程序员

Java程序员的日常—— Arrays工具类的使用

这个类在日常的开发中,还是非常常用的。今天就总结一下Arrays工具类的常用方法。最常用的就是asList,sort,toStream,equals,copy...

1777
来自专栏编程

详解栈及其实现

转自:melonstreet http://www.cnblogs.com/QG-whz/p/5170418.html 栈的特点 栈(Stack)是一种线性存储...

1896
来自专栏Java Web

数据结构与算法(2)——栈和队列栈队列LeetCode 相关题目整理其他题目整理

栈是一种用于存储数据的简单数据结构(与链表类似)。数据入栈的次序是栈的关键。可以把一桶桶装的薯片看作是一个栈的例子,当薯片做好之后,它们会依次被添加到桶里,每一...

983
来自专栏Java3y

Java实现单向链表

一、前言 最近在回顾数据结构与算法,有部分的算法题用到了栈的思想,说起栈又不得不说链表了。数组和链表都是线性存储结构的基础,栈和队列都是线性存储结构的应用~ 本...

3218
来自专栏小樱的经验随笔

C/C++中对链表操作的理解&&实例分析

链表概述    链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以head表示,它存放...

3104
来自专栏GreenLeaves

JavaScript之面向对象学习六原型模式创建对象的问题,组合使用构造函数模式和原型模式创建对象

一、仔细分析前面的原型模式创建对象的方法,发现原型模式创建对象,也存在一些问题,如下: 1、它省略了为构造函数传递初始化参数这个环节,结果所有实例在默认的情况下...

1986
来自专栏前端儿

ES6笔记(6)-- Set、Map结构和Iterator迭代器

JS中的iterator也有类似的功能,JS内部为一些数据结构实现了iterator迭代器的接口,让我们可以方便的使用

931
来自专栏海纳周报

Java的字符串常量相关的一个问题

大家过年好!春节假期休了一个长假,今天刚回来。在知乎上遇到了一个很好的问题,忍不住回答了一下。原文转载过来了。 以下代码的运行结果,如何解释? String h...

2948

扫码关注云+社区