java--StringBuilder
java--StringBuilder
一、StringBuffer&StringBuilder
- StringBuffer: 是线程安全的;
- StringBuilder: 是线程不安全的,性能高点,推荐使StringBuilder;(jdk1.5出现)
- StringBuffer的字符序列是可变的(通过append等方法操作)
- StringBuffer 和 String之间的转换;
- String toString() 返回此序列中数据的字符串表示形式。
- StringBuffer(String str):以指定的字符串创建StringBuffer对象。
二、StringBuilder
提到如果字符串修改操作比较频繁,应该采用StringBuilder和StringBuffer类,这两个类的方法基本是完全一样的,它们的实现代码也几乎一样,唯一的不同就在于,StringBuffer是线程安全的,而StringBuilder不是。但是线程安全是有成本的,影响性能,而字符串对象及操作,大部分情况下,没有线程安全的问题,适合使用StringBuilder。所以,本节就只讨论StringBuilder。
基本用法--创建StringBuilder
StringBuilder sb = new StringBuilder();
添加字符串,通过append方法
-
sb.append("老马说编程");
-
sb.append(",探索编程本质");
获取构建后的字符串,通过toString方法
System.out.println(sb.toString());
输出为:
老马说编程,探索编程本质
StringBuilder是怎么实现的呢?
基本实现原理
内部组成和构造方法
与String类似,StringBuilder类也封装了一个字符数组,定义如下:
char[] value;
与String不同,它不是final的,可以修改。另外,与String不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:
int count;
StringBuilder继承自AbstractStringBuilder,它的默认构造方法是:
-
public StringBuilder() {
-
super(16);
-
}
调用父类的构造方法,父类对应的构造方法是:
-
AbstractStringBuilder(int capacity) {
-
value = new char[capacity];
-
}
append的实现
来看append的代码:
-
public AbstractStringBuilder append(String str) {
-
if (str == null) str = "null";
-
int len = str.length();
-
ensureCapacityInternal(count + len);
-
str.getChars(0, len, value, count);
-
count += len;
-
return this;
-
}
append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展,实际使用的长度用count体现。具体来说,ensureCapacityInternal(count+len)会确保数组的长度足以容纳新添加的字符,str.getChars会拷贝新添加的字符到字符数组中,count+=len会增加实际使用的长度。
ensureCapacityInternal的代码如下:
-
private void ensureCapacityInternal(int minimumCapacity) {
-
// overflow-conscious code
-
if (minimumCapacity - value.length > 0)
-
expandCapacity(minimumCapacity);
-
}
如果字符数组的长度小于需要的长度,则调用expandCapacity进行扩展,expandCapacity的代码是:
-
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);
-
}
扩展的逻辑是,分配一个足够长度的新数组,然后将原内容拷贝到这个新数组中,最后让内部的字符数组指向这个新数组,这个逻辑主要靠下面这句代码实现:
value = Arrays.copyOf(value, newCapacity);
那如果预先就知道大概需要多长呢?可以调用StringBuilder的另外一个构造方法:
public StringBuilder(int capacity)
toString实现
字符串构建完后,我们来看toString代码:
-
public String toString() {
-
// Create a copy, don't share the array
-
return new String(value, 0, count);
-
}
String还有两个构造方法,分别接受String和CharSequence参数,它们的代码分别如下:
-
public StringBuilder(String str) {
-
super(str.length() + 16);
-
append(str);
-
}
-
-
public StringBuilder(CharSequence seq) {
-
this(seq.length() + 16);
-
append(seq);
-
}
append有多种重载形式,可以接受各种类型的参数,将它们转换为字符,添加进来,这些重载方法有:
-
public StringBuilder append(boolean b)
-
public StringBuilder append(char c)
-
public StringBuilder append(double d)
-
public StringBuilder append(float f)
-
public StringBuilder append(int i)
-
public StringBuilder append(long lng)
-
public StringBuilder append(char[] str)
-
public StringBuilder append(char[] str, int offset, int len)
-
public StringBuilder append(Object obj)
-
public StringBuilder append(StringBuffer sb)
-
public StringBuilder append(CharSequence s)
-
public StringBuilder append(CharSequence s, int start, int end)
还有一个append方法,可以添加一个Code Point:
public StringBuilder appendCodePoint(int codePoint)
插入
public StringBuilder insert(int offset, String str)
在指定索引offset处插入字符串str,原来的字符后移,offset为0表示在开头插,为length()表示在结尾插,比如说:
-
StringBuilder sb = new StringBuilder();
-
sb.append("老马说编程");
-
sb.insert(0, "关注");
-
sb.insert(sb.length(), "老马和你一起探索编程本质");
-
sb.insert(7, ",");
-
System.out.println(sb.toString());
来看下insert的实现代码:
-
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;
-
}
这个实现思路是,在确保有足够长度后,首先将原数组中offset开始的内容向后挪动n个位置,n为待插入字符串的长度,然后将待插入字符串拷贝进offset位置。
挪动位置调用了System.arraycopy方法,这是个比较常用的方法,它的声明如下:
-
public static native void arraycopy(Object src, int srcPos,
-
Object dest, int destPos,
-
int length);
将数组src中srcPos开始的length个元素拷贝到数组dest中destPos处。这个方法有个优点,即使src和dest是同一个数组,它也可以正确的处理,比如说,看下面代码:
-
int[] arr = new int[]{1,2,3,4};
-
System.arraycopy(arr, 1, arr, 0, 3);
-
System.out.println(arr[0]+","+arr[1]+","+arr[2]);
这里,src和dest都是arr,srcPos为1,destPos为0,length为3,表示将第二个元素开始的三个元素移到开头,所以输出为:
2,3,4
arraycopy的声明有个修饰符native,表示它的实现是通过Java本地接口实现的,Java本地接口是Java提供的一种技术,用于在Java中调用非Java语言实现的代码,实际上,arraycopy是用C++语言实现的。为什么要用C++语言实现呢?因为这个功能非常常用,而C++的实现效率要远高于Java。
其他插入方法
与append类似,insert也有很多重载的方法,如下列举一二
-
public StringBuilder insert(int offset, double d)
-
public StringBuilder insert(int offset, Object obj)
删除
删除指定范围内的字符
public StringBuilder delete(int start, int end)
其实现代码为:
-
public AbstractStringBuilder delete(int start, int end) {
-
if (start < 0)
-
throw new StringIndexOutOfBoundsException(start);
-
if (end > count)
-
end = count;
-
if (start > end)
-
throw new StringIndexOutOfBoundsException();
-
int len = end - start;
-
if (len > 0) {
-
System.arraycopy(value, start+len, value, start, count-end);
-
count -= len;
-
}
-
return this;
-
}
也是通过System.arraycopy实现的,System.arraycopy被大量应用于StringBuilder的内部实现中,后文就不再赘述了。
删除一个字符
public StringBuilder deleteCharAt(int index)
替换
public StringBuilder replace(int start, int end, String str)
如
-
StringBuilder sb = new StringBuilder();
-
sb.append("老马说编程");
-
sb.replace(3, 5, "Java");
-
System.out.println(sb.toString());
程序输出为:
老马说Java
替换一个字符
public void setCharAt(int index, char ch)
翻转字符串
public StringBuilder reverse()
这个方法不只是简单的翻转数组中的char,对于增补字符,简单翻转后字符就无效了,这个方法能保证其字符依然有效,这是通过单独检查增补字符,进行二次翻转实现的。比如说:
-
StringBuilder sb = new StringBuilder();
-
sb.append("a");
-
sb.appendCodePoint(0x2F81A);//增补字符:��
-
sb.append("b");
-
sb.reverse();
-
System.out.println(sb.toString());
即使内含增补字符”��”,输出也是正确的,为:
b��a
长度方法
StringBuilder中有一些与长度有关的方法
确保字符数组长度不小于给定值
public void ensureCapacity(int minimumCapacity)
返回字符数组的长度
public int capacity()
返回数组实际使用的长度
public int length()
注意capacity()方法与length()方法的的区别,capacity返回的是value数组的长度,length返回的是实际使用的字符个数,是count实例变量的值。
直接修改长度
public void setLength(int newLength)
代码为:
-
public void setLength(int newLength) {
-
if (newLength < 0)
-
throw new StringIndexOutOfBoundsException(newLength);
-
ensureCapacityInternal(newLength);
-
-
if (count < newLength) {
-
for (; count < newLength; count++)
-
value[count] = '\0';
-
} else {
-
count = newLength;
-
}
-
}
count设为newLength,如果原count小于newLength,则多出来的字符设置默认值为’\0’。
缩减使用的空间
public void trimToSize()
代码为:
-
public void trimToSize() {
-
if (count < value.length) {
-
value = Arrays.copyOf(value, count);
-
}
-
}
减少value占用的空间,新建了一个刚好够用的空间。
与String类似的方法
StringBuilder中也有一些与String类似的方法,如:
查找子字符串
-
public int indexOf(String str)
-
public int indexOf(String str, int fromIndex)
-
public int lastIndexOf(String str)
-
public int lastIndexOf(String str, int fromIndex)
取子字符串
-
public String substring(int start)
-
public String substring(int start, int end)
-
public CharSequence subSequence(int start, int end)
获取其中的字符或Code Point
-
public char charAt(int index)
-
public int codePointAt(int index)
-
public int codePointBefore(int index)
-
public int codePointCount(int beginIndex, int endIndex)
-
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
以上这些方法与String中的基本一样,本节就不再赘述了。
String的+和+=运算符
Java中,String可以直接使用+和+=运算符,这是Java编译器提供的支持,背后,Java编译器会生成StringBuilder,+和+=操作会转换为append。比如说,如下代码:
-
String hello = "hello";
-
hello+=",world";
-
System.out.println(hello);
背后,Java编译器会转换为:
-
StringBuilder hello = new StringBuilder("hello");
-
hello.append(",world");
-
System.out.println(hello.toString());
既然直接使用+和+=就相当于使用StringBuilder和append,那还有什么必要直接使用StringBuilder呢?在简单的情况下,确实没必要。不过,在稍微复杂的情况下,Java编译器没有那么智能,它可能会生成很多StringBuilder,尤其是在有循环的情况下,比如说,如下代码:
-
String hello = "hello";
-
for(int i=0;i<3;i++){
-
hello+=",world";
-
}
-
System.out.println(hello);
Java编译器转换后的代码大概如下所示:
-
String hello = "hello";
-
for(int i=0;i<3;i++){
-
StringBuilder sb = new StringBuilder(hello);
-
sb.append(",world");
-
hello = sb.toString();
-
}
-
System.out.println(hello);
在循环内部,每一次+=操作,都会生成一个StringBuilder。
所以,结论是,对于简单的情况,可以直接使用String的+和+=,对于复杂的情况,尤其是有循环的时候,应该直接使用StringBuilder。
小结
本节介绍了StringBuilder,介绍了其用法,实现原理,数组长度扩展策略,以及String的+和+=操作符的实现原理。
字符串操作是计算机程序中最常见的操作,理解了String和StringBuilder的用法及实现原理,我们就对字符串操作建立了一个坚实的基础。
文章来源: brucelong.blog.csdn.net,作者:Bruce小鬼,版权归原作者所有,如需转载,请联系作者。
原文链接:brucelong.blog.csdn.net/article/details/79890883
- 点赞
- 收藏
- 关注作者
评论(0)