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
/** 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的异常了。