对比
引言
在JAVA中字符串的表示有三种方式 String StringBuilder StringBuffer .
关于String 需要注意两点:
1.String是不可变的字符串,它的底层是一个用final修饰的字符数组
2.String 对象赋值之后就会在字符串常量池中缓存,如果下次创建会判定常量池是否已经有缓存对象,如果有的话直接返回该引用给创建者。
什么是字符串常量池?
Java中的字符串常量池(String Pool)是Java堆内存中的一片内存空间。
我们知道String是java中比较特殊的类,我们可以使用new运算符创建String对象,也可以用双引号(”“)创建字串对象,看下图:
String s1 = “Cat”当我们用这种方式创建字符串对象的时候,首先会去字符串常量池中查找看有没有“Cat”字符串,如果有则返回它的地址给s1,如果没有则在常量池中创建“Cat”字符串,并将地址返回给s1.
String s3 = new String(“Cat”)当我们用这种方式创建字符串对象的时候,首先会去字符串常量池中查找看有没有“Cat”字符串,如果没有则在常量池中创建“Cat”字符串,然后在堆内存中创建“Cat”字符串,并将堆内存中的地址返回给s3.
public class HelloWorld {public static void main(String []args) {String s1 = "cat";String s2 = "cat";String s3 = new String("cat");String s4 = new String("cat");System.out.println(s1==s2);System.out.println(s1==s3);System.out.println(s3==s4);}}>True>False>False
上面的操作就很令人费解 其中使用引号创建对象的时候,会直接在常量池中创建 使用关键字创建的时候会在常量池和堆上都创建
所以结果 s1 == s2 为true s1==s3为false,s1和s2都指向了常量池中的“Cat”而s3指向了堆内存中的“Cat”
大家想想 如果有这么一行代码 String str = new String(“hello”)
在内存中会创建几个字符串对象?
答案是一个或两个
如果常量池中已经存在“hello”,则会在堆内存中创建一个“hello”对象,如果常量池中不存在则在常量池中创建一个,在堆内存中创建一个。
通过引入字符串常量池的概念,让字符串处理的效率得到了提高,这是jvm对字符串的一种优化手段。
当我们做拼接字符串操作的时候:
String str = “you”;
Str = str+”win”;
底层是这样的:
Str刚开始指向常量池中的“you”,拼接字符串“win”的时候又开辟了两块块内存空间一块保存“win”,一块保存拼接以后生成的字符串“ you win”并且str指向拼接以后的字符串,在这个过程中一共占用了三块内存空间,所以效率是非常低下的。(原因就是string原代码的中char[]是final修饰的)
StringBuilder 和 StringBuffer都继承于:AbstractStringBuilder他们的底层使用的是没有用final修饰的字符数组:char[]
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
所以在做字符串拼接的时候就在原来的内存上进行拼接,不会浪费内存空间。
StringBuilder和StringBuffer的区别是
StringBuilder是线程不安全的,它的执行效率比StriingBuffer要高
StringBuffer是线程安全的,它的执行效率比StringBuilder要低
总结
1.String字符串是不可变的。
2.在修改字符串操作比较多的时候用StringBuilder或StringBuffer.
在要求线程安全的情况下用StringBuffer
在不要求线程安全的情况下用StringBuilder
StringBuffer源码解析
类的定义
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence
从StringBuffer定义中可以看到
- StringBuffer是final修饰的类,不可继承
- 继承了AbstractStringBuilder类
- 实现了java.io.Serializable接口,可以序列化
实现了CharSequence接口,表示StringBuffer一个可读的char序列
字段属性
//从父类AbstractStringBuilder继承char[] value;//从父类继承int count;//缓存,toString的时候才缓存,修改就置为nullprivate transient char[] toStringCache;//序列化版本号static final long serialVersionUID = 4383685877147921099L;
从上面可以看出
StringBuffer 本质是一个char数组
- 保存了char数组中可以用字符的长度,注意:count不是char数组的长度
- toStringCache缓存了char数组可用字符的值,transient表示不可序列化
-
构造方法
//空构造函数,默认指定char的长度为16public StringBuffer() {super(16);}//传入char数组的初始化长度public StringBuffer(int capacity) {super(capacity);}//传入一个字符串,默认char数组长度为字符串长度加16//将str保存到char数组中去public StringBuffer(String str) {super(str.length() + 16);append(str);}//传入一个CharSequence对象,默认char数组长度为CharSequence对象长度加16//将CharSequence对象保存到char数组中去public StringBuffer(CharSequence seq) {this(seq.length() + 16);append(seq);}
从构造方法中可以看出
StringBuffer中的char数组默认长度是16
- 可以自己指定默认长度
构造方法可以传入String对象和CharSequence对象,预留16个char的长度
方法
StringBuffer中所有的方法都是使用synchronized修饰的,因此StringBuffer是线程安全的
length 方法
@Overridepublic synchronized int length() {//返回count表示可用字符的数量,跟char数组长度不一定相等return count;}
capacity 方法
@Overridepublic synchronized int capacity() {//value的长度表示容量return value.length;}
ensureCapacity 方法
@Overridepublic synchronized void ensureCapacity(int minimumCapacity) {//调用父类方法super.ensureCapacity(minimumCapacity);}public void ensureCapacity(int minimumCapacity) {//参数验证if (minimumCapacity > 0)ensureCapacityInternal(minimumCapacity);}private void ensureCapacityInternal(int minimumCapacity) {// overflow-conscious code//确保容量,容量不足则扩容到minimumCapacityif (minimumCapacity - value.length > 0) {value = Arrays.copyOf(value,newCapacity(minimumCapacity));}}
charAt 方法
@Overridepublic synchronized char charAt(int index) {//参数检查if ((index < 0) || (index >= count))throw new StringIndexOutOfBoundsException(index);//直接返回return value[index];}
append 方法
@Overridepublic synchronized StringBuffer append(String str) {//将缓存置为nulltoStringCache = null;//调用父类的方法super.append(str);return this;}public AbstractStringBuilder append(String str) {//参数检查if (str == null)return appendNull();//获取参数的长度int len = str.length();//char数组长度检查,如果长度不够会进行扩容,扩容的大小为count+lenensureCapacityInternal(count + len);//将传入的str添加到char数组后面str.getChars(0, len, value, count);//重新设置当前可用字符的数量count += len;return this;}
delete 方法
@Overridepublic synchronized StringBuffer delete(int start, int end) {//将缓存置为nulltoStringCache = null;//调用父类方法super.delete(start, end);return this;}public AbstractStringBuilder delete(int start, int end) {//参数检查if (start < 0)throw new StringIndexOutOfBoundsException(start);//如果结尾大于可用字符的数量把end的值设置为countif (end > count)end = count;//参数检查if (start > end)throw new StringIndexOutOfBoundsException();//计算需要删除的数量int len = end - start;if (len > 0) {//把start+len作为起始位置,count作为结束位置去覆盖start作为起始位置,覆盖数量为count-end//类似于后面的前移,覆盖中间删除部分System.arraycopy(value, start + len, value, start, count - end);//重新计算可用字符长度,这里删除的话应该减小count -= len;}return this;}
replace 方法
@Overridepublic synchronized StringBuffer replace(int start, int end, String str) {//将缓存置为nulltoStringCache = null;//调用父类方法super.replace(start, end, str);return this;}public AbstractStringBuilder delete(int start, int end) {//参数检查if (start < 0)throw new StringIndexOutOfBoundsException(start);//如果结尾大于可用字符的数量把end的值设置为countif (end > count)end = count;//参数检查if (start > end)throw new StringIndexOutOfBoundsException();//计算需要删除的数量int len = end - start;if (len > 0) {//把start+len作为起始位置,count作为结束位置去覆盖start作为起始位置,覆盖数量为count-end//类似于后面的前移,覆盖中间删除部分System.arraycopy(value, start + len, value, start, count - end);//重新计算可用字符长度,这里删除的话应该减小count -= len;}return this;}
substring 方法
@Overridepublic synchronized String substring(int start, int end) {//调用父类方法return super.substring(start, end);}public String substring(int start, int end) {//参数验证if (start < 0)throw new StringIndexOutOfBoundsException(start);if (end > count)throw new StringIndexOutOfBoundsException(end);if (start > end)throw new StringIndexOutOfBoundsException(end - start);//从valule中返回start到end的字符串return new String(value, start, end - start);}
substring(int start)的实质是sbustring(start, count)
insert 方法
@Overridepublic synchronized StringBuffer insert(int offset, String str) {//缓存设置为nulltoStringCache = null;//调用父类方法super.insert(offset, str);return this;}public AbstractStringBuilder insert(int offset, String str) {//验证参数合法性if ((offset < 0) || (offset > length()))throw new StringIndexOutOfBoundsException(offset);//如果传入的String为空,替换为”null“字符串if (str == null)str = "null";//获取插入字符串的长度int len = str.length();//检查char数组容量,容量不够则扩容ensureCapacityInternal(count + len);//把offset作为起始位置,count作为结束位置去覆盖offset + len作为起始位置,覆盖数量为count - offset//类似于offset后面的后移,空出插入部分System.arraycopy(value, offset, value, offset + len, count - offset);//把str的内容填充进去str.getChars(value, offset);//重新计算count的值count += len;return this;}
indexOf 方法
@Overridepublic int indexOf(String str) {//注意,这个方法没有用synchronized修饰//调用父类方法return super.indexOf(str);}@Overridepublic synchronized int indexOf(String str, int fromIndex) {//调用父类方法return super.indexOf(str, fromIndex);}public int indexOf(String str, int fromIndex) {//最后调用String的indexOfreturn String.indexOf(value, 0, count, str, fromIndex);}
lastIndexOf 方法
@Overridepublic int lastIndexOf(String str) {//注意,这个方法没有用synchronized修饰//调用父类方法return lastIndexOf(str, count);}@Overridepublic synchronized int lastIndexOf(String str, int fromIndex) {//调用父类方法return super.lastIndexOf(str, fromIndex);}public int lastIndexOf(String str, int fromIndex) {//最后调用的是String的lastIndexOf方法return String.lastIndexOf(value, 0, count, str, fromIndex);}
reverse 方法
@Overridepublic synchronized StringBuffer reverse() {//将缓存置为nulltoStringCache = null;//调用父类方法super.reverse();return this;}public AbstractStringBuilder reverse() {//是否含有unicode编码,包含Unicode编码的话,替换后高低位会改变位置boolean hasSurrogates = false;int n = count - 1;//采取二分法 n-1>>1 等同于 (n-1)/2for (int j = (n - 1) >> 1; j >= 0; j--) {//根据对称性左右替换int k = n - j;char cj = value[j];char ck = value[k];value[j] = ck;value[k] = cj;//判断是否含有unicode编码if (Character.isSurrogate(cj) ||Character.isSurrogate(ck)) {hasSurrogates = true;}}//包含unicode编码if (hasSurrogates) {reverseAllValidSurrogatePairs();}return this;}/*** Outlined helper method for reverse()*/private void reverseAllValidSurrogatePairs() {for (int i = 0; i < count - 1; i++) {char c2 = value[i];//如果包含Unicode编码,将高低位位置改变回来if (Character.isLowSurrogate(c2)) {char c1 = value[i + 1];if (Character.isHighSurrogate(c1)) {value[i++] = c1;value[i] = c2;}}}}
toString 方法
@Overridepublic synchronized String toString() {//如果缓存为空,重新设置缓存if (toStringCache == null) {toStringCache = Arrays.copyOfRange(value, 0, count);}//直接使用缓存的数组return new String(toStringCache, true);}
