关于StringBuider的使用


String与StringBuilder的区别

1.String

String类位于Java的lang包下 无需导包即可调用

String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间

每创建一个String对象就要在栈中 开辟空间 当修改String的值的时候 会在堆中新开辟一块空间 造成空间的浪费

用String类创建对象通常有两种形式:

1、String str = “abc”

2、String str1 = new String(“abc”);

方法1:通过字面量直接给字符串进行赋值,在栈中创建字符串str时,会先在字符串常量池中检查是否已有该字符串,如果没有则创建并存入字符串常量池中,如果已存在则直接从常量池中获取。

方法2:创建的字符串,不管内存有没有为“abc”字符串分配内存,都会在内存中重新分配一个内存区域存放“abc”,然后用str1指向它,相当于内存中有两个“abc”,只是存放的地址不一样。

构造方法

1、public String();

String类的无参构造方法,用来创建空字符串的String类;

案例:String str = new String();

2、public String(String value);

用已知的字符串value值来创建一个字符串对象;

案例:String str1 = new String(“aaa”);

3、public String(char[] value)

用字符数组value创建一个String对象。

案例:char[] value = {“ab”,”cd”,”efg”};

2. StringBuffer

StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量

我们调用equals方法 显而易见结果是false

一些常用的方法 看参数 你应该就知道如何使用 无非就是增删改查 类型转换 多练即可掌握

(1) String中的是常量(final)数组,只能被赋值一次。
比如:new String(“abc”)使得value[]={‘a’,’b’,’c’}(查看jdk String 就是这么实现的),之后这个String对象中的value[]再也不能改变了。这也正是大家常说的,String是不可变的原因 。
注意:这个对初学者来说有个误区,有人说String str1=new String(“abc”); str1=new String(“cba”);不是改变了字符串str1吗?那么你有必要先搞懂对象引用和对象本身的区别。这里我简单的说明一下,对象本身指的是存放在堆空间中的该对象的实例数据(非静态非常量字段)。而对象引用指的是堆中对象本身所存放的地址,一般方法区和Java栈中存储的都是对象引用,而非对象本身的数据。

(2) StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。

比如:new StringBuffer(“abc”)使得value[]={‘a’,’b’,’c’,’’,’’…}(注意构造的长度是str.length()+16)。如果再将这个对象append(“abc”),那么这个对象中的value[]={‘a’,’b’,’c’,’a’,’b’,’c’,’’….}。这也就是为什么大家说 StringBuffer是可变字符串 的涵义了。从这一点也可以看出,StringBuffer中的value[]完全可以作为字符串的缓冲区功能。其累加性能是很不错的

总结,讨论String和StringBuffer可不可变。本质上是指对象中的value[]字符数组可不可变,而不是对象引用可不可变。

3.StringBuilder

程序开发过程中,我们常常碰到字符串连接的情况,方便和直接的方式是通过”+”符号来实现,但是这种方式达到目的的效率比较低,且每执行一次都会创建一个String对象,即耗时,又浪费空间。使用StringBuilder类就可以避免这种问题的发生,下面就Stringbuilder的使用做个简要的总结:

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

String和StringBuilder的区别:

●String:内容不可变

●StringBuilder:内容是可变的

好处:效率高 但线程不安全

使用

1.无参数 stringBuilder s=new stringBuilder();

实例化时会有一个默认的16字符的空间,当追加字符超过16个字符时,会新开一个翻倍的空间,而原油空间会浪费掉

2.有参数:stringBulider s=new stringBuilder(20);

可以在实例化的时候创建一个固定大小的空间,避免资源的浪费。

常见的方法:

StringBuilder的常见用法。

1、初始化一个StringBuilder。

StringBuilder sb = new StringBuilder();

2、填充:使用append() 来追加字符串填充到sb中:

  sb.append("xxxx").append(" xxx")....可继续延伸

3、删除sb里面的已有的内容:

  sb.delete(0,sb.length());将删除sb里面从0开始到结尾的所有内容,也可以自己定义区间

4、sb里面的内容要显示时需要转换成String类型

  System.out.println(sb.toString());r reverse() 返回相反的字符序列

5.reverse 可以反转

4.从源码出发(进阶)吊打面试官

stringbuffer和stringbuild区别
1、StringBuffer 与 StringBuilder 是长度可变得。
2、只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
3、在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全。而StringBuffer则每次都需要判断锁,效率相对更低。

public final class String implements java.io.Serializable, Comparable, 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

/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;

 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);
}

一个被final关键字修饰的char[]数组,所以实现细节上也是不允许改变,这就是String类的Immutable(不可变)属性(你知道String类为什么不能被继承吗?),导致每次对String的操作都会生成新的String对象导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。
2、StringBuffer和StringBuild

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

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



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

//final修饰的char数组
private final char value[];
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    //stringbuilder和stringbuffer都继承了AbstractStringBuilder 但AbstractStringBuilder 中的     
//char数组没有使用final修饰,这就是为什么string是不可变,但stringbuffer和stringbuilder是可变的
    char[] value;
 
    /**
     * The count is the number of characters used.
     */
    int count;
 
    /**
     * This no-arg constructor is necessary for serialization of subclasses.
     */

通过Stringbuffer和Stringbuilder的源码和类结构图,可以发现Stringbuilder和Stringbuffer都是继承了abstractStringbuilder这个抽象类,然后实现了Serializable, CharSequence接口。其次Stringbuilder和Stringbuffer的内部实现其实跟String是一样的,都是通过一个char类型的数组进行存储字符串的,但是是String类中的char数组是final修饰的,是不可变的,而StringBuilder和StringBuffer中的char数组没有被final修饰,是可变的。这就是StringBuilder和StringBuffer和String的区别。

在多线程编程中有个概念叫原子操作,原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有切换到任何的一个其他的线程)。上述代码中的count+=len就不是一个原子操作,它等同于count=count+len,比如在上诉代码中,执行到count的值为99998的时候,新建一个len长度为1,但是当有两个线程同时执行到了count+=len的时候,他们的count的值都是99998,然后分别各自都执行了count+=len,则执行完之后的值都是99999,然后将值赋给count,则count最后的结果是99999,不是正确的100000,所以在多线程中执行stringbuilder的值始终会小于正确的结果。但是StringBuilder和stringbuffer都是继承了abstractstringbuilder为什么结果不一样呢。既然abstractstringbuilder中的append方法肯定都是一样的,再来看看stringbuffer中的

@Override
    //append操作被synchronized 关键字修饰了
    public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }
    @Override
   //append操作被synchronized 关键字修饰了
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;

可以发现stringbuffer中的append操作被synchronized关键字修饰了。这个关键字肯定不会陌生,主要用来保证多线程中的线程同步和保证数据的准确性。所以再多线程中使用stringbuffer是线程安全的。在AbstractStringBuilder的append方法中有这样的两个个操作:

ensureCapacityInternal(count + len);   //1
str.getChars(0, len, value, count);    //2

转到第一个操作方法的源码,可以发现这是一个是检查StringBuilder对象的原数组的大小是否能装下新的字符串的方法,如果装不下了就new一个新的数组,新的数组的容量是原来char数组的两倍,再通过CopyOf()方法将原数组的内容复制到新数组。

/**
     * For positive values of {@code minimumCapacity}, this method
     * behaves like {@code ensureCapacity}, however it is never
     * synchronized.
     * If {@code minimumCapacity} is non positive due to numeric
     * overflow, this method throws {@code OutOfMemoryError}.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

然后第二步操作是将String对象里面char数组里面的内容拷贝到StringBuilder对象的char数组里面。getchars源码如下:

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
//1       
 if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
//2   
     if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
//3   
     if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

可以看到原来在这里会抛出StringIndexOutOfBoundsException的异常。假设前面的代码中有两个线程a和线程b同时执行了append方法,并且都执行完了ensureCapacityInternal()方法,这个时候count的值为99997,如果当线程a执行完了,则轮到线程2继续执行,线程b执行完了append方法之后,count变成了99998,这个时候如果线程a执行到了上面的getchars方法的时候线程a得到的count的值就是99998了,而它本来的值应该是99997,所以在这个时候就会抛出ArrayIndexOutOfBoundsException的异常了。


文章作者: liming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 liming !
评论
  目录