栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java String、StringBuilder、StringBuffer

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java String、StringBuilder、StringBuffer

目录
      • 源码分析
      • 字符串修改的性能问题
      • 字符串、数组的相互转换
      • String、StringBuilder、StringBuffer的比较|区别

 

源码分析

String部分源码

public final class String implements java.io.Serializable, Comparable, CharSequence {

	//存储字符串
    private final char value[];

    
    private int hash;
    
    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);
        //并不是修改内置的char[],而是直接返回一个新的String对象,使用新的char[]
        return new String(buf, true);
    }

}

 

AbstractStringBuilder封装了修改字符串的常用操作,部分源码如下

abstract class AbstractStringBuilder implements Appendable, CharSequence {

    //存储字符串
    char[] value;

    //char[]中存储的字符数量
    int count;
    
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        //先确保数组容量足够。扩容时会复制到新的char[]中,会改变value的指向
        ensureCapacityInternal(count + len);
        //再在内置的char[]上修改内容。容量保证够的,直接在char[]上进行修改,不改变value指向
        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");
        //源数组、目标数组都是value,value的内存地址不变,只是修改其上存储的内容
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }
    
    private void ensureCapacityInternal(int minimumCapacity) {
        //容量不足则自动扩容
        if (minimumCapacity - value.length > 0) {
        	//value指向新的char[]数组
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

    private int newCapacity(int minCapacity) {
        //新容量 = 原容量*2 + 2
        int newCapacity = (value.length << 1) + 2;
        //之所以有这个判断,是因为可能存在这种情况:目前容量较小,将要存储的字符串很多,超过了 原容量*2 + 2,则取实际要存储的字符个数
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }
    
    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

}

 

StringBuffer、StringBuilder未设置char[]的初始容量时,默认取16

public StringBuffer() {
    super(16);
}
public StringBuilder() {
    super(16);
}

 

都是使用char[ ]存储字符串

  • 但String不可变,字符串长度就是char[ ]的长度,所以不需要使用一个成员变量标识字符串长度;
  • StringBuilder、StringBuffer可变,char[ ]只是容器,需要用一个int型的成员变量标识实际存储的字符个数,即字符串长度
     

append()的操作流程

  • 先确保char[ ]容量足够。容量不足时自动扩容,扩容时会复制到新的char[ ]中,然后改变value的指向
  • 再在内置的char[ ]上修改内容。此时容量是保证够的,直接在char[ ]上进行修改内容,不改变value指向
     

内置char[ ]的扩容机制

如果所需容量大于 原容量*2 + 2,则扩容为实际所需容量(将要存储的字符长度),否则扩容为 原容量*2 + 2

 

字符串修改的性能问题
String str = "xxx";
str += "xxx1";
str += "xxx2";

String不可变,每次修改字符串都是创建新串返回,性能是不是比StringBuffer、StringBuilder差得多?

并不是,编译器会自动优化代码,把修改String的操作转换为:创建一个StringBuilder,使用StringBuilder进行字符串的(1个或多个)修改操作,修改完成后将StringBuilder转换为String返回。

 

这样看String的修改性能也不错,但编译器不够智能,优化有限。比如这段代码

String str = "xxx";
for (int i = 0; i < 10; i++) {
    str += "xxx";
}

编译器会优化为:在循环体中,创建一个StringBuilder,使用StringBuilder进行字符串的修改,转为String返回。每次循环都会创建一个新的StringBuilder对象,性能显然不行。
 

不要太依赖编译器的优化,尽量自己优化代码

StringBuilder sb = new StringBuilder("xxx");
for (int i = 0; i < 10; i++) {
    sb.append("xxx");
}

 

字符串、数组的相互转换
//字符串切割为数组,参数指定分割符
String str = "张三&&李四&&王五";
String[] arr = str.split("&&");


//数组连接成字符串,第二个参数指定连接符

//方式一,使用 org.apache.commons.lang.StringUtils
String str1 = StringUtils.join(arr, "&&");

//方式二、使用 org.apache.poi.util.StringUtil
String str2 = StringUtil.join(arr, "&&");

 

String、StringBuilder、StringBuffer的比较|区别

1、整体结构

  • 都实现了Serializable、CharSequence接口
  • String还实现了Comparable接口,可用于比较、排序
  • StringBuilder、StringBuffer还继承了抽象类AbstractStringBuilder

 

2、可变性

  • String不可变,修改字符串是在副本上操作,以新串形式返回,原串不变。
  • StringBuffer、StringBuilder都是可变的,修改字符串是在原串上操作,原串改变。

可变、不可变指的是对象本身,变量本身持有的引用都是可变的。

 

3、线程安全

  • String、StringBuilder没有使用任何保证线程安全的机制,线程不安全
  • StringBuffer:操作字符串的方法都使用synchronized修饰,线程安全,但为保证线程安全,有额外的开销,性能比如String、StringBuilder

 

使用建议

  • 需要考虑线程安全的用StringBuffer
  • 不需要考虑线程安全、需要频繁修改字符串的用StringBuilder
  • 不需要考虑线程安全、也不需要频繁修改字符串的用String

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/352128.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号