您现在的位置是:首页 > 正文

详解String,StringBuffer,StringBuilder

2024-02-01 02:27:32阅读 4

一、通过代码测试三者进行字符串拼接的性能

S t r i n g , S t r i n g B u f f e r , S t r i n g B u i l d e r String,StringBuffer,StringBuilder String,StringBuffer,StringBuilder三者各做6000次字符串拼接,观察其分别使用的内存和时间。

/**
 * 对String,StringBuilder,StringBuffer进行字符串拼接性能比较
 */
public class String_StringBuilder_StringBuffer {
    public static void main(String[] args) {
        int nums = 6000;//进行字符串拼接的次数
        //使用String进行字符串的拼接
        String str = "";
        long memoryStart1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
        long timeStart1 = System.currentTimeMillis();//获取系统的当前时间
        for (int i = 0; i < nums; i++) {
            str = str + i;
        }
        long memoryEnd1 = Runtime.getRuntime().freeMemory();
        long timeEnd1 = System.currentTimeMillis();
        System.out.println("String占用内存:" + (memoryEnd1 - memoryStart1));
        System.out.println("String占用时间:" + (timeEnd1 - timeStart1));

        //使用StringBuilder进行字符串的拼接
        StringBuilder builder = new StringBuilder("");
        long memoryStart2 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
        long timeStart2 = System.currentTimeMillis();//获取系统的当前时间
        for (int i = 0; i < nums; i++) {
            builder = builder.append(i);
        }
        long memoryEnd2 = Runtime.getRuntime().freeMemory();
        long timeEnd2 = System.currentTimeMillis();
        System.out.println("StringBuilder占用内存:" + (memoryEnd2 - memoryStart2));
        System.out.println("StringBuilder占用时间:" + (timeEnd2 - timeStart2));

        //使用StringBuffer进行字符串的拼接
        StringBuffer buffer = new StringBuffer("");
        long memoryStart3 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
        long timeStart3 = System.currentTimeMillis();//获取系统的当前时间
        for (int i = 0; i < nums; i++) {
            buffer = buffer.append(i);
        }
        long memoryEnd3 = Runtime.getRuntime().freeMemory();
        long timeEnd3 = System.currentTimeMillis();
        System.out.println("StringBuffer占用内存:" + (memoryEnd3 - memoryStart3));
        System.out.println("StringBuffer占用时间:" + (timeEnd3 - timeStart3));
    }
}

输出结果:

String占用内存:188816272
String占用时间:108
StringBuilder占用内存:0
StringBuilder占用时间:0
StringBuffer占用内存:0
StringBuffer占用时间:1

从结果来看, S t r i n g String String所用的时间和占用的内存远远高于 S t r i n g B u i l d e r StringBuilder StringBuilder S t r i n g B u f f e r StringBuffer StringBuffer,下面就来分析一下原因。

二、String

为什么效率低?

在Java中,字符串属于对象,String类的字符串对象初始化后是不能够改变其中的字符的,因此每次对String类的字符串的每一次改动,都会生成新的String对象(开辟新的堆内存),所以导致效率低下,且浪费内存空间。
String字符串对象进行拼接操作的内存变化如下:
在这里插入图片描述

为什么String字符串是不可变的呢?

打开String类的源码看看

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

String 类内部使用char数据来保存字符串的value,并且使用关键字final修饰。
而final实例字段必须在构造对象时初始化,并且之后不能再修改这个字段,所以String类的字符串对象是不可变的。

String的优点

每次修改字符串都需要重新创建一个新的字符串对象,那一定会降低运行效率吗?也不尽然。不可变的String类虽然不像StringBuilder和StringBuffer那样,可以直接修改代码单元,而不用创建新的对象,但String是不可变的字符串,同样有着一个很大的优点:编译器可以让字符串共享。
字符串对象在JVM中可能有两个存放的位置:字符串常量池或堆内存。

  • 使用常量字符串初始化的字符串对象,它的值存放在字符串常量池中;
  • 使用字符串构造方法创建的字符串对象,它的值存放在堆内存中。

对于存储在公共的字符串常量池中的字符串,字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串和复制的字符串共享相同的字符。Java的设计者认为共享带来的高效率远远超过拼接字符串所带来的低效率,同时大多数情况下,我们只需要对字符串进行比较等操作,并不需要改变字符串。因此,String算是有利有弊吧。

三、StringBuilder和StringBuffer

和 String 类不同的,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不会产生新的对象。我们先看StringBuilder的源码来分析

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

从StringBuilder的append()方法看不出来什么,注意到StringBuilder是继承了AbstractStringBuilder

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

那么继续查看AbstractStringBuilder的源码

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

    /**
     * The count is the number of characters used.
     */
    int count;

AbstractStringBuilder同样采用char数组来保存值,但是并没有用final修饰,说明AbstractStringBuilder对象是可变的。
再看append()方法

/*
* @param   str   a string.
     * @return  a reference to this object.
     */
    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()方法里调用了getChars()方法,继续查看getChars()方法
getChars()方法是String类中的方法

/*
 * @param      srcBegin   index of the first character in the string to copy.
 * @param      srcEnd     index after the last character in the string to copy.
 * @param      dst        the destination array.
 * @param      dstBegin   the start offset in the destination array.
*/
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

getChars()方法继续调用System提供了一个静态方法arraycopy(),将追加append的值复制到原字符串(char数组)末尾。所以StringBuilder就是通过这样对数组的扩容、复制操作完成字符串拼接(append)。
附系统方法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. 
*/
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

讲完了StringBuilder,我们再来看StringBuffer
StringBuffer和StringBuilder类功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的
先上源码:

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

可见 StringBuffer和StringBuilder一样,同样继承自AbstractStringBuilder。
再来看append()方法

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

和StringBuilder一样,都是调用父类AbstractStringBuilder的方法来实现,但不同的是,StringBuffer通过Synchronized给方法加了锁,是线程安全的。

四、使用总结

  • StringBuilder线程不安全,效率高(一般使用它)
  • StringBuffer线程安全,效率低,需要考虑多线程并发的时候使用
  • String类的字符串初始化后是不能够改变其中的字符的,所以使用StringBuilder和StringBuffer。当要多次往string数组后加内容时,避免使用String类 。操作少量数据或者很少改变字符串的值时,使用String。

网站文章

  • Collections.unmodifiableMap 用法解析

    Collections.unmodifiableMap 用法解析

    返回指定映射的不可修改视图。 通俗的讲就是:产生一个只读的Map,当你调用此map的put方法时会抛错。 public static Map unmodifiableMap(Map m) 此方法允许模块为用户提供对内部映射的“只读”访问。 在返回的映射上执行的查询操作将“读完”指定的映射。 试图修改返回的映射(不管是直接修改还是通过其 collection 视图进行修改)将

    2024-02-01 02:27:24
  • 短暂的“歧途”——写在短暂的第二份工作结束之时

    距离上一份工作结束总结才过去3个月,现在又要写第二份工作总结了。不用说,这份工作绝对是不太如意的。其中有不少需要总结的地方,希望读者也能从中吸取经验和教训。记事去年9月,还在第一份工作期间,跳槽的心就...

    2024-02-01 02:27:17
  • Jenkins部署新项目

    Jenkins部署新项目

    1、安装 Jenkins 下载jenkins-1.656.zip文件,进行安装 2、部署一个项目 2.1登录Jenkins http://127.0.0.1:xxxx/ 2.2新建 2.2.1构建一个maven项目 2.2.2输入SVN地址 2.2.4构建触发器 构建方式

    2024-02-01 02:27:10
  • Java读取输入流和文件内容——BufferedReader

    BufferedReader读取控制台输入从控制台读取数据readline() 方法关于 `try-catch` 语句的拓展 Java不像C那样直接用 scanf 即可从控制台读入数据,读取数据很麻烦...

    2024-02-01 02:26:41
  • Python使用定时任务

    Python使用定时任务

    【代码】Python使用定时任务。

    2024-02-01 02:26:33
  • 计算机卸载目录不让它显示,电脑删除文件时提示“无法删除文件夹 目录不是空的”怎么办?...

    计算机卸载目录不让它显示,电脑删除文件时提示“无法删除文件夹 目录不是空的”怎么办?...

    电脑使用久了之后会发现系统中存在很多没用的文件夹,此时最简单的方法就是直接删除,但是有时会在删除文件夹的时候出现“无法删除文件夹 目录不是空的”的提示,这是什么问题呢?现在本文就给大家分析该问题的原因...

    2024-02-01 02:26:27
  • 正向代理与反向代理通俗理解

    关于正向代理以及反向代理的理解。特别通俗,一看就懂。

    2024-02-01 02:26:00
  • MySQL if else的格式_mysql if--else

    MySQL if else的格式_mysql if--else

    case具有两种格式。简单case函数和case搜索函数。--简单case函数case sexwhen &#39;1&#39; then &#39;男&#39;when &#39;2&#39; the...

    2024-02-01 02:25:55
  • Js - 模板字面量

    背景 参考资料 JavaScript高级程序设计(第4版) ES6指北【7】——从回调地狱到Promise和async/await JS异步编程之Promise详解和使用总结

    2024-02-01 02:25:51
  • Git和Bitbucket入门之代码上传

    作为一名代码渣,虽然代码写得很烂,但多多少少也写了些了。听说大牛们都在用Bitbucket,瞻仰代码时却连git clone都不会也有点说不过去了,因此,我要入门!参考:http://blog.jobbole.com/53573/ (中文) https://confluence.atlassian.com/bitbucket/bitbucket-cloud-documen

    2024-02-01 02:25:21