一.简介
1.线程安全与非线程安全
2.StringBuffer和StringBuilder的联系与区别
3.源代码阅读理解
4.优化
二.线程安全与非线程安全
线程安全指在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
如果说一个类是线程安全的,简单的理解就是,在多线程的情况下,能够确保它的数据的一致性。就比如说ab两个线程,和线程安全的c类,c类里面有个变量d,那么ab线程一次只能有一个线程可以修改d变量。而非线程安全,就是在多线程的情况下不能提供这种保障,适用于单线程执行的任务。因为非线程安全的比线程安全的效率高,所以在能够确定单线程的任务中,使用非线程安全是比较推荐的。
三.StringBuffer和StringBuilder的联系与区别
1.都是final修饰的v类,不能被继承,同时继承的类和实现的接口相同,
两者在字符串的处理中的方法几乎是相同的。
2.底层都是用字符数组实现,字符串都是可变的;
3.StringBuilder是非线程安全的,StringBuffer是线程安全的,所以StringBuilder运行速度较快,从jdk官方来说,StringBuilder在jdk1.5版本中出现的,StringBuffer是jdk1.0中就存在的,StringBuilder就是在线程安全的情况下取代StringBuffer,较为推荐的一种使用方式。
4.StringBuffer相对StringBuilder的线程安全实现通常是在相同功能的方法上加上synchronized关键字。
当然它们之间的相同点和不同点还是很多的,接下来从源代码的方面进行分析;
四.源代码阅读理解
1.类的声明
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{//都是final修饰的v类,不能被继承,同时继承的类和实现的接口相同
2.成员变量
StringBuilder成员变量都在父类AbstractStringBuilder当中
char[] value; char类型数组,每次实例化时都会给这个数组一个容量,容量最小默认16位
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//最大允许容量
int count;//真实的字符串数组长度;
2.StringBuffer比StringBuilder多了个缓存char类型数组成员变量,它的作用是,在多次调用tostring方法时,可以输出缓存的数据值,因此每次涉及到StringBuffer中字符串变化时都需要清除这个字段的数据,典型的线程安全,牺牲了时间和空间保证了数据安全;
private transient char[] toStringCache;
StringBuffer的tostring 方法,使用synchronized关键字,保证线程安全,同时需要判断toStringCache字段,性能方面低于StringBuilder;
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
StringBuilder的tostring方法,明显性能优于StringBuffer
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
3.构造方法
在构造方法上,两者是完全相同的,当不传入字符串是,初始化16位大小的char类型数组
public StringBuffer() {
super(16);
}
super(16)如下
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
他们还有其它的构造方法:
public StringBuffer(int capacity) {
super(capacity);//指定数组长度
}
public StringBuffer(String str) {
super(str.length() + 16);//指定字符串,长度为字符串长度+16
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);//同上
append(seq);
}
需要注意的是往构造方法里面传null会导致空指针异常
4.StringBuffer的append方法,作用是在字符串后面拼接字符串
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;//把缓存设置为null,StringBuilder没有这个步骤
super.append(String.valueOf(obj));
return this;
}
super.append(String.valueOf(obj));方法如下
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();//如果append的是个空值,在后面追加null字符串
int len = str.length();
ensureCapacityInternal(count + len);//如果添加的值比原本的字符串容器大,
//则容器大小乘以2+2,容器的最大限制是Integer.MAX_VALUE - 8
//就是 0x7fffffff-8 ,2147483647-8=2147483639
str.getChars(0, len, value, count);
count += len;//StringBuffer字符串的长度
return this;
}
StringBuilder是相同的,只是少了synchronized 关键字修饰,以下不再重复这句话,涉及到的修改字符串内容源代码,都是这个区别
5.StringBuffer的insert方法,实在当前字符串的指定位置添加字符串
public synchronized StringBuffer insert(int offset, String str) {
toStringCache = null;
super.insert(offset, str);
return this;
}
super.insert(offset, str);如下,跟append方法大同小异,就不在多介绍
public AbstractStringBuilder insert(int offset, String str) {
if ((offset < 0) || (offset > length()))
throw new StringIndexOutOfBoundsException(offset);
if (str == null)
str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
System.arraycopy(value, offset, value, offset + len, count - offset);
str.getChars(value, offset);
count += len;
return this;
}
6.其它方法
//反转字符串
public synchronized StringBuffer reverse() {
toStringCache = null;
super.reverse();
return this;
}
//截取字符串
public synchronized String substring(int start, int end) {
return super.substring(start, end);
}
//得到字符串某个下标的值
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
//删除某个下标的值
public synchronized StringBuffer deleteCharAt(int index) {
toStringCache = null;
super.deleteCharAt(index);
return this;
}
......还有很多这里就不贴出来了,跟string很多方法还是相通的
五.优化
这里需要指出的一点是,优化是在性能达到瓶颈时必须处理才进行的,因为过多的优化在不必要的时候反而会增加代码的冗余,让代码难以理解和维护。
1.实例化时指定容器大小,避免不必要的空间浪费
public StringBuffer(int capacity) {
super(capacity);//指定数组长度
}
这哥是当你知道字符串大概范围的时候可以操作的优化
2.trimToSize()方法
public synchronized void trimToSize() {
super.trimToSize();
}
super.trimToSize();如下:
public void trimToSize() {
if (count < value.length) {//判断实际字符串长度,讲char 数组容量转换成实际大小,
//减少容量浪费
value = Arrays.copyOf(value, count);
}
}
这个方法在不需要再次更改字符串内容时使用。
写在最后,我在学习的过程中,喜欢结合自己的理解记录在我的有道云笔记中,在这里我重新整理了关于StringBuffer和StringBuilder的学习理解,写篇博客,如果有一些地方存在歧义,希望能够有人指出,并在后期改正,同时希望对Java学习者有所帮助。