恰好最近项目做到了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同步关键字;