恰好最近项目做到了string类相关的问题,就借着网上的分析,较深入地比较了一下stringBuilder类和String类的源码,本来还应该有一个Stringbuffer类,因为时间原因所以只能下次补上了。
StringBuilder类和string类的区别
相同:底层均采用字符数组value
来保存字符串
区别:String
类的value
数组有final
修饰,指向不可改,同时private
未提供修改value
数组的方法,==因此String类一旦赋值即不可变==。StringBuilder
类的value
数组没有final
修饰,可以改变指向,==且可以扩容,扩容通过新建字符数组完成==。
String类
构造函数
1.默认的无参和含参构造函数
1 | public String() { |
都是直接在现有常量上进行赋值(将常量的指针指向参数)
2.参数是数组的构造函数
1 | public String(char value[]) { |
1 | /** |
调用Arrays的copyOf方法,返回复制完的新数组。
常用函数
length()
返回的是字符数组value的长度
1 | public int length() { |
isEmpty()
判断字符数组长度是否为0
1 | public boolean isEmpty() { |
charAt()
返回value对象的索引
1 | public char charAt(int index) { |
equals()
重写equals()方法,判断为真的情况是:两个对象相同,同时对应的value字符数组内容也相同
其中:可能是同一对象也可能是内容相同的不同String
1 | public boolean equals(Object anObject) { |
compareTo()
返回:如果第一个不相同字符之差,如果一个字符串是另一个的子串(前lim个字符相同),返回两个字符串长度之差
1 | public int compareTo(String anotherString) { |
substring()
1.substring(int)
该函数返回从给定参数位置起到字符串结束的新字符串。如果给定从0开始,则返回原本的String对象,否则返回一个新的String对象。
1 | public String substring(int beginIndex) { |
2.substring(int, int)
如果要获取的子串是从0到最后,则返回原本的String对象,否则返回一个新的String对象。
1 | public String substring(int beginIndex, int endIndex) { |
replace()
返回:如果是相同则返回原来的String对象,不同则先找到第一个不同的字符然后将字符复制到新的字符数组中
1 | public String replace(char oldChar, char newChar) { |
可见,String是依赖字符常量表实现的; 同时所有对String发生修改的方法返回值都是一个新的String对象,没有修改原有对象; 因此是线程安全的
StringBuilder类源码
构造函数
1 .默认构造函数
Stringbuilder
使用了父类AbstractStringBuilder
构造,默认长度16
.注意这里的value没有final修饰,权限为默认,因此内部数值可以改变·
1 | public StringBuilder() { |
1 | /*父类*/ |
2.传入字符串·
会设置初始容量为传入字符串长度加上16
,再通过append
函数将str
写入,append
函数见常用方法
1 | public StringBuilder(String str) { |
常用函数
append()
append函数调用时会首先查看是否超出容量上限
1 |
|
1 | public AbstractStringBuilder append(String str) { |
容量检查函数
参数是当前对象的value
中字符长度与传入字符串长度之和,也即是value
的容量最小值。
如果需要的容量最小值大于目前value
容量,新申请一块内存复制进去
扩容的新容量为当前value
的容量2
倍加2
,如果扩容后的容量还是比需要的最小容量小,则直接扩容为需要的最小容量,再将当前value内容复制给一个新的长度为newCapacity
的字符数组,再将value指向
这个扩容后的新数组。即扩容是通过开辟新数组完成的,返回的也是新创建的新数组。
1 | private void ensureCapacityInternal(int minimumCapacity) { |
执行完ensureCapacityInternal
函数后,this
对象的value
数组已经指向一个扩容后的新数组,并且之前的value
数组里的值也复制到新的value
数组中,接下来执行getChars
函数
复制字符函数
该函数是将调用的string
对象的value
数组从srcBegin
到srcEnd
复制给目标数组dst
,从dst
数组的第dstBegin
位置开始。
append
函数中执行完str.getChars
函数后就将参数str
的内容追加到StringBuilder
对象的value
数组后面,再更新count
值,返回调用对象。
1 | public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { |
删除函数
1 |
|
delete
()函数通过覆盖原理,将删除的元素覆盖掉·
deleteCharAt
()删除索引为index
处的字符。通过调用数组复制函数来完成,将索引后面的内容依次复制到从索引开始的位置上,即通过覆盖的原理完成,更新count。
1 | /*父类调用函数 |
插入函数
1 |
|
首先确保value
数组容量足够,然后通过数组复制,将索引位置开始全部向后移一位,再将索引位置赋值c,更新count
。
1 | /* |
对比
StringBuffer
以后有机会在写吧。。。
差别 | String | StringBuffer | StringBuilder |
---|---|---|---|
常量 / 变量 | 常量 | 变量 | 变量 |
线程是否安全 | 安全 | 安全 | 非安全 |
所在内存区域 | Constant String Pool(常量池) | heap | heap |
是否能被继承 | 否 | 否 | 否 |
代码行数 | 3157 | 718 | 448 |
使用场景 | 在字符串不经常变化的场景 | 在频繁进行字符串运算(如拼接、替换、删除等), 并且运行在多线程环境 | 在频繁进行字符串运算(如拼接、替换、和删除等), 并且运行在单线程的环境 |
场景举例 | 常量的声明、少量的变量运算 | XML 解析、HTTP 参数解析和封装 | SQL 语句的拼装、JSON 封装 |
AbstractStringBuilder
:StringBuffer
类与StringBuilder
类都继承了AbstractStringBuilder
,抽象父类里实现了除toString
以外的所有方法。StringBuilder
:自己重写了方法之后,全都在方法内super.function()
,未做任何扩展。同时从类名语义上来说String
构建者,所以没有subString
方法看来也合情合理;StringBuffer
:在重写方法的同时,几乎所有方法都添加了synchronized
同步关键字;