java--StringBuilder

举报
brucexiaogui 发表于 2021/12/30 00:17:51 2021/12/30
【摘要】 java--StringBuilder 一、StringBuffer&StringBuilder StringBuffer:  是线程安全的;StringBuilder: 是线程不安全的,性能高点,推荐使StringBuilder;(jdk1.5出现)StringBuffer的字符序列是可变的(通过append等方法...

java--StringBuilder

一、StringBuffer&StringBuilder

  1. StringBuffer:  是线程安全的;
  2. StringBuilder: 是线程不安全的,性能高点,推荐使StringBuilder;(jdk1.5出现)
  3. StringBuffer的字符序列是可变的(通过append等方法操作)
  4. StringBuffer  和  String之间的转换;
  5. String toString() 返回此序列中数据的字符串表示形式。 
  6. StringBuffer(String str):以指定的字符串创建StringBuffer对象。

二、StringBuilder

提到如果字符串修改操作比较频繁,应该采用StringBuilder和StringBuffer类,这两个类的方法基本是完全一样的,它们的实现代码也几乎一样,唯一的不同就在于,StringBuffer是线程安全的,而StringBuilder不是。但是线程安全是有成本的,影响性能,而字符串对象及操作,大部分情况下,没有线程安全的问题,适合使用StringBuilder。所以,本节就只讨论StringBuilder。

基本用法--创建StringBuilder

StringBuilder sb = new StringBuilder();
 

添加字符串,通过append方法


  
  1. sb.append("老马说编程");
  2. sb.append(",探索编程本质");

获取构建后的字符串,通过toString方法

System.out.println(sb.toString());
 

输出为:

老马说编程,探索编程本质
 

StringBuilder是怎么实现的呢?

基本实现原理 
内部组成和构造方法

与String类似,StringBuilder类也封装了一个字符数组,定义如下:

char[] value;
 

与String不同,它不是final的,可以修改。另外,与String不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:

int count;
 

StringBuilder继承自AbstractStringBuilder,它的默认构造方法是:


  
  1. public StringBuilder() {
  2. super(16);
  3. }
调用父类的构造方法,父类对应的构造方法是:

  
  1. AbstractStringBuilder(int capacity) {
  2. value = new char[capacity];
  3. }

append的实现

来看append的代码:


  
  1. public AbstractStringBuilder append(String str) {
  2. if (str == null) str = "null";
  3. int len = str.length();
  4. ensureCapacityInternal(count + len);
  5. str.getChars(0, len, value, count);
  6. count += len;
  7. return this;
  8. }

append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展,实际使用的长度用count体现。具体来说,ensureCapacityInternal(count+len)会确保数组的长度足以容纳新添加的字符,str.getChars会拷贝新添加的字符到字符数组中,count+=len会增加实际使用的长度。

ensureCapacityInternal的代码如下:


  
  1. private void ensureCapacityInternal(int minimumCapacity) {
  2. // overflow-conscious code
  3. if (minimumCapacity - value.length > 0)
  4. expandCapacity(minimumCapacity);
  5. }

如果字符数组的长度小于需要的长度,则调用expandCapacity进行扩展,expandCapacity的代码是:


  
  1. void expandCapacity(int minimumCapacity) {
  2. int newCapacity = value.length * 2 + 2;
  3. if (newCapacity - minimumCapacity < 0)
  4. newCapacity = minimumCapacity;
  5. if (newCapacity < 0) {
  6. if (minimumCapacity < 0) // overflow
  7. throw new OutOfMemoryError();
  8. newCapacity = Integer.MAX_VALUE;
  9. }
  10. value = Arrays.copyOf(value, newCapacity);
  11. }

扩展的逻辑是,分配一个足够长度的新数组,然后将原内容拷贝到这个新数组中,最后让内部的字符数组指向这个新数组,这个逻辑主要靠下面这句代码实现:

value = Arrays.copyOf(value, newCapacity);
 












那如果预先就知道大概需要多长呢?可以调用StringBuilder的另外一个构造方法:

public StringBuilder(int capacity)
 
toString实现

字符串构建完后,我们来看toString代码:


  
  1. public String toString() {
  2. // Create a copy, don't share the array
  3. return new String(value, 0, count);
  4. }


String还有两个构造方法,分别接受String和CharSequence参数,它们的代码分别如下:


  
  1. public StringBuilder(String str) {
  2. super(str.length() + 16);
  3. append(str);
  4. }
  5. public StringBuilder(CharSequence seq) {
  6. this(seq.length() + 16);
  7. append(seq);
  8. }

append有多种重载形式,可以接受各种类型的参数,将它们转换为字符,添加进来,这些重载方法有:


  
  1. public StringBuilder append(boolean b)
  2. public StringBuilder append(char c)
  3. public StringBuilder append(double d)
  4. public StringBuilder append(float f)
  5. public StringBuilder append(int i)
  6. public StringBuilder append(long lng)
  7. public StringBuilder append(char[] str)
  8. public StringBuilder append(char[] str, int offset, int len)
  9. public StringBuilder append(Object obj)
  10. public StringBuilder append(StringBuffer sb)
  11. public StringBuilder append(CharSequence s)
  12. 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()表示在结尾插,比如说:


  
  1. StringBuilder sb = new StringBuilder();
  2. sb.append("老马说编程");
  3. sb.insert(0, "关注");
  4. sb.insert(sb.length(), "老马和你一起探索编程本质");
  5. sb.insert(7, ",");
  6. System.out.println(sb.toString());


来看下insert的实现代码


  
  1. public AbstractStringBuilder insert(int offset, String str) {
  2. if ((offset < 0) || (offset > length()))
  3. throw new StringIndexOutOfBoundsException(offset);
  4. if (str == null)
  5. str = "null";
  6. int len = str.length();
  7. ensureCapacityInternal(count + len);
  8. System.arraycopy(value, offset, value, offset + len, count - offset);
  9. str.getChars(value, offset);
  10. count += len;
  11. return this;
  12. }

这个实现思路是,在确保有足够长度后,首先将原数组中offset开始的内容向后挪动n个位置,n为待插入字符串的长度,然后将待插入字符串拷贝进offset位置。

挪动位置调用了System.arraycopy方法,这是个比较常用的方法,它的声明如下:


  
  1. public static native void arraycopy(Object src, int srcPos,
  2. Object dest, int destPos,
  3. int length);

将数组src中srcPos开始的length个元素拷贝到数组dest中destPos处。这个方法有个优点,即使src和dest是同一个数组,它也可以正确的处理,比如说,看下面代码:


  
  1. int[] arr = new int[]{1,2,3,4};
  2. System.arraycopy(arr, 1, arr, 0, 3);
  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也有很多重载的方法,如下列举一二


  
  1. public StringBuilder insert(int offset, double d)
  2. public StringBuilder insert(int offset, Object obj)

删除

删除指定范围内的字符

public StringBuilder delete(int start, int end) 
 

其实现代码为:


  
  1. public AbstractStringBuilder delete(int start, int end) {
  2. if (start < 0)
  3. throw new StringIndexOutOfBoundsException(start);
  4. if (end > count)
  5. end = count;
  6. if (start > end)
  7. throw new StringIndexOutOfBoundsException();
  8. int len = end - start;
  9. if (len > 0) {
  10. System.arraycopy(value, start+len, value, start, count-end);
  11. count -= len;
  12. }
  13. return this;
  14. }

也是通过System.arraycopy实现的,System.arraycopy被大量应用于StringBuilder的内部实现中,后文就不再赘述了。

删除一个字符

public StringBuilder deleteCharAt(int index)
 

替换

public StringBuilder replace(int start, int end, String str)
 


  
  1. StringBuilder sb = new StringBuilder();
  2. sb.append("老马说编程");
  3. sb.replace(3, 5, "Java");
  4. System.out.println(sb.toString());

程序输出为:

老马说Java
 

替换一个字符

public void setCharAt(int index, char ch)
 

翻转字符串

public StringBuilder reverse()
 

这个方法不只是简单的翻转数组中的char,对于增补字符,简单翻转后字符就无效了,这个方法能保证其字符依然有效,这是通过单独检查增补字符,进行二次翻转实现的。比如说:


  
  1. StringBuilder sb = new StringBuilder();
  2. sb.append("a");
  3. sb.appendCodePoint(0x2F81A);//增补字符:��
  4. sb.append("b");
  5. sb.reverse();
  6. 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)
 

代码为:


  
  1. public void setLength(int newLength) {
  2. if (newLength < 0)
  3. throw new StringIndexOutOfBoundsException(newLength);
  4. ensureCapacityInternal(newLength);
  5. if (count < newLength) {
  6. for (; count < newLength; count++)
  7. value[count] = '\0';
  8. } else {
  9. count = newLength;
  10. }
  11. }

count设为newLength,如果原count小于newLength,则多出来的字符设置默认值为’\0’。

缩减使用的空间

public void trimToSize() 
代码为:


  
  1. public void trimToSize() {
  2. if (count < value.length) {
  3. value = Arrays.copyOf(value, count);
  4. }
  5. }

减少value占用的空间,新建了一个刚好够用的空间。

与String类似的方法

StringBuilder中也有一些与String类似的方法,如:

查找子字符串


  
  1. public int indexOf(String str)
  2. public int indexOf(String str, int fromIndex)
  3. public int lastIndexOf(String str)
  4. public int lastIndexOf(String str, int fromIndex)

取子字符串


  
  1. public String substring(int start)
  2. public String substring(int start, int end)
  3. public CharSequence subSequence(int start, int end)

获取其中的字符或Code Point


  
  1. public char charAt(int index)
  2. public int codePointAt(int index)
  3. public int codePointBefore(int index)
  4. public int codePointCount(int beginIndex, int endIndex)
  5. public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

以上这些方法与String中的基本一样,本节就不再赘述了。

String的+和+=运算符

Java中,String可以直接使用+和+=运算符,这是Java编译器提供的支持,背后,Java编译器会生成StringBuilder,+和+=操作会转换为append。比如说,如下代码:


  
  1. String hello = "hello";
  2. hello+=",world";
  3. System.out.println(hello);

背后,Java编译器会转换为:


  
  1. StringBuilder hello = new StringBuilder("hello");
  2. hello.append(",world");
  3. System.out.println(hello.toString());

既然直接使用+和+=就相当于使用StringBuilder和append,那还有什么必要直接使用StringBuilder呢?在简单的情况下,确实没必要。不过,在稍微复杂的情况下,Java编译器没有那么智能,它可能会生成很多StringBuilder,尤其是在有循环的情况下,比如说,如下代码:


  
  1. String hello = "hello";
  2. for(int i=0;i<3;i++){
  3. hello+=",world";
  4. }
  5. System.out.println(hello);

Java编译器转换后的代码大概如下所示:


  
  1. String hello = "hello";
  2. for(int i=0;i<3;i++){
  3. StringBuilder sb = new StringBuilder(hello);
  4. sb.append(",world");
  5. hello = sb.toString();
  6. }
  7. System.out.println(hello);

在循环内部,每一次+=操作,都会生成一个StringBuilder。

所以,结论是,对于简单的情况,可以直接使用String的+和+=,对于复杂的情况,尤其是有循环的时候,应该直接使用StringBuilder。

小结

本节介绍了StringBuilder,介绍了其用法,实现原理,数组长度扩展策略,以及String的+和+=操作符的实现原理。

字符串操作是计算机程序中最常见的操作,理解了String和StringBuilder的用法及实现原理,我们就对字符串操作建立了一个坚实的基础。


文章来源: brucelong.blog.csdn.net,作者:Bruce小鬼,版权归原作者所有,如需转载,请联系作者。

原文链接:brucelong.blog.csdn.net/article/details/79890883

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。