问题
- 说明String和StringBulider的区别?
- 说明StringBulider和StringBuffer的区别
解析
1. 首先String和StringBuilder的区别
在java中提供了三个类String,StringBuffer,StringBuilder来表示和操作字符串,字符串就是多少个字符的组合,这三个类都是使用final修饰的类,因此创建对象是在方法区中进行,存储时间比较长,耗费小部分内存,提高效率。
类关系图如下
String的内容是不可变的,String的底层是使用了final修饰的字符数组,首先我们来看一下String拼接方法concat();
//String的成员变量使用了final修饰
private final char value[];
//String进行拼接的方法
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);
return new String(buf, true);
}
- 首先定义一个final修饰的字符数组,是一个常量,当我们使用字符串拼接的时候,实际对新增的str进行复制,重新创建静态字符数组,增加char buf[]数组长度,重新创建String的对象。所以字符串内容不可变,在进行拼接的时候,实际上是对象的重新创建String对象。
——————————————————————————————————————————
- 我们在来看StringBuilder的字符串拼接的方法append();
首先StringBuilder继承 AbstractStringBuilder类,成员变量定义的是没有final修饰的字符串数组
PS:关键字transient解释
StringBuilder的append();方法
//这里是append方法
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;
}
/**
* This method has the same contract as ensureCapacity, but is
* never synchronized.
* append方法的调用,英文的意思是,这个方法结构和ensureCapacity相同
* 父类的ensureCapacity方法,实际是线程不安全的
*
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0)
//如果输入的数据长度大于定义的数组长度
expandCapacity(minimumCapacity);
}
//再次调用方法,实现对字符数组扩容,扩容大小为输入字符串长度*2+2
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
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);
}
//最终实现 ,创建新的str,调用的是本地方法,无法看到源码
str.getChars(0, len, value, count);
因此,我们可以的出结论,在进行字符串拼接的时候:
- String类中成员变量是final修饰的字符数组,创建后长度确定,拼接的时候,需要重新创建新的对象的方法,效率低。
- StringBuilder类中成员变量是没有修饰的字符数组,当我们调用append();方法进行拼接的时候,调用父类的方法对数组扩容,本地方法实现字符串的拼接。
StringBuffer和StringBuilder的区别
StringBuilder
- 上面我们已经看到,在对StringBuilder进行扩容的时候,调用AbstractStringBuilder父类的方法,通过上面的扩容源码可以指代是线程不安全的,因此可以实现快速的添加,但是当我们使用多线程进行数据添加的时候,会出现无序的问题。
——————————————————————————————————————————
StringBuffer
- 首先StringBuffer的创建对象的时候,字符数组长度不确定,这一点与StringBuilder相同,如下:
private transient char[] toStringCache;
PS:这里我们对transient关键字简单的说明
-
StringBuffler类是实现了序列化接口的Seriailzable关键字,在进行网络传输和持久化的时候,保证对象的有序。可以进行有效读取。
-
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
-
java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
-
我们再来看StringBuffer中的方法
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
toStringCache = null;
value[index] = ch;
}
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
基本所有的操作方法都使用关键字synchronized修饰,保证线程的安全。
最后,得出的结论是:
从执行效率来讲:
String<StringBuffer<StringBuilder
从线程安全来讲
StringBuffer线程安全
StringBuilder线程不安全