Java--String、StringBuilder及StringBuffer区别及性能对比_java string 和 stringbuilder的性能对比

学习路线:

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
在这里插入图片描述

需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

    System.out.println("Start...");
}

public static void main(String[] args) throws RunnerException {
    //1、启动基准测试:输出普通文件

// Options opt = new OptionsBuilder()
// .include(ArrayAndLinkedJmhTest.class.getSimpleName()) //要导入的测试类
// .output(“C:\Users\Administrator\Desktop\StringAppendJmhTest.log”) //输出测试结果的普通txt文件
// .build();

    //1、启动基准测试:输出json结果文件(用于查看可视化图)
    Options opt = new OptionsBuilder()
            .include(StringAppendJmhTest.class.getSimpleName()) //要导入的测试类
            .result("C:\\Users\\Administrator\\Desktop\\StringAppendJmhTest.json") //输出测试结果的json文件
            .resultFormat(ResultFormatType.JSON)//格式化json文件
            .build();

    //2、执行测试
    new Runner(opt).run();
}

@Benchmark
public void stringAppendTest(Blackhole blackhole) {
    String str = new String();
    for (int i = 0; i < count; i++) {
        str = str + "Justin";
    }
    blackhole.consume(str);
}

@Benchmark
public void stringBufferAppendTest(Blackhole blackhole) {
    StringBuffer strBuffer = new StringBuffer();
    for (int i = 0; i < count; i++) {
        strBuffer.append("Justin");
    }
    blackhole.consume(strBuffer);
}

@Benchmark
public void stringBuilderAppendTest(Blackhole blackhole) {
    StringBuilder strBuilder = new StringBuilder();
    for (int i = 0; i < count; i++) {
        strBuilder.append("Justin");
    }
    blackhole.consume(strBuilder);
}

@TearDown(Level.Trial) // 结束方法,在全部Benchmark运行之后进行
public void clear() {
    System.out.println("End...");
}

}


**运行`main`方法进行测试~**


## 1.2 测试结果


### 1.2.1 普通展示


**查看控制台输出的结果信息,拉到最后查看最后几行的`Score`指标如下:**



Benchmark (count) Mode Cnt Score Error Units
StringAppendJmhTest.stringAppendTest 2 avgt 15 43.029 ± 4.440 ns/op
StringAppendJmhTest.stringAppendTest 10 avgt 15 212.911 ± 22.882 ns/op
StringAppendJmhTest.stringAppendTest 100 avgt 15 9262.168 ± 431.742 ns/op
StringAppendJmhTest.stringAppendTest 1000 avgt 15 830811.924 ± 38227.519 ns/op
StringAppendJmhTest.stringBufferAppendTest 2 avgt 15 35.546 ± 1.159 ns/op
StringAppendJmhTest.stringBufferAppendTest 10 avgt 15 167.670 ± 4.900 ns/op
StringAppendJmhTest.stringBufferAppendTest 100 avgt 15 1698.781 ± 80.934 ns/op
StringAppendJmhTest.stringBufferAppendTest 1000 avgt 15 14059.694 ± 820.273 ns/op
StringAppendJmhTest.stringBuilderAppendTest 2 avgt 15 27.621 ± 1.745 ns/op
StringAppendJmhTest.stringBuilderAppendTest 10 avgt 15 154.621 ± 3.360 ns/op
StringAppendJmhTest.stringBuilderAppendTest 100 avgt 15 1488.514 ± 31.618 ns/op
StringAppendJmhTest.stringBuilderAppendTest 1000 avgt 15 12032.867 ± 69.878 ns/op


**示例测试结果中的`Score`指标,表示`ns/op`即平均每次调用需要多少微秒,时间越低说明效率越高~**


### 1.2.2 图形展示


**程序运行完成后,会在控制台输出结果信息,还会将结果信息格式化成`json`格式保存到了桌面的`StringAppendJmhTest.json`文件中,将`json`文件通过如下可视化工具生成图形:**


* `JMH Visualizer`:<https://jmh.morethan.io/>
* `JMH Visual Chart`:<http://deepoove.com/jmh-visual-chart/>


**测试结果可视化如下:**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/fa99b05f229e4957b0456afd732170ff.png)


## 1.3 结果分析


**字符串拼接性能:`StringBuilder` > `StringBuffer` > `String`**



> 
> **通过`JMH`的测试结果,可以发现在少量拼接字符串`10`个左右,效率区别不大,但是当字符串拼接的数据量比较大时,`100`左右,`String`比另外两者效率开始相差好几倍,当达到`1000`时,此时`String`的字符串拼接效率真的非常差非常差了,比另外两者效率低了即几十上百倍,这种情况应当避免使用`String`来拼接字符串~**
> 
> 
> 


## 二、区别说明


### 2.1 String


#### 2.1.1 String特性



> 
> * ⭐ **实现了序列化`Serializable`,`Comparable`以及`CharSequence`字符序列接口**
> * ⭐ **`String`是`Java`字符串对象,底层是基于`char`字符数组,使用了`final`修饰类,表示最终类,不能被继承和修改,线程安全~**
> * ⭐ **每一次对`String`声明的对象的内容进行修改,得到的都是另外一个新的`字符串常量`对象,如果`字符串常量池`中已经存在该`字符串常量`对象,则不会再创建~**
> * ⭐ **`字符串常量`在`JDK1.7`之前,存在于方法区`运行时常量池`中的`字符串常量池`,`JDK1.7`时,`字符串常量池`被移到堆区中,`运行时常量池`还保留在方法区中**
> * ⭐ **JDK1.8时,取消了方法区(永久代),方法区被元空间替代,`字符串常量`拼接还被自动优化成了`StringBuiler`,例如:**  
>  String s1 = “Justin”;  
>  String s2 = “Jack”;  
>  String s3 = s1 + s2;  
>  //**先`javac`编译java源文件得到`Class`,再经过`javap -c ClassName`反编译查看汇编指令发现,发现`s1+s2`等价于**  
>  String s4 = new StringBuffer().append(s1).append(s2).toString();
> * ⭐ **`String`重写了`Object`类中的`equals`、`hashCode`方法,重写后`equals`方法比较了字符串的每一个字符,而重写`hashCode`方法则是由字符串的每一个字符计算出字符串的`hashCode`值~**
> 
> 
> 


#### 2.1.2 String常用API




| 常用方法 | 方法说明 |
| --- | --- |
| `int length()` | 求字符串长度 |
| `boolean isEmpty()` | 判断字符串是否为空字符串,注意`str.isEmpty()`调用时,要避免`str`为`null` |
| `String valueOf(Object obj)` | 转换`Object`类型为字符串类型 |
| `String trim()` | 去除字符串两端的空白 |
| `int indexOf(int ch)` | 返回指定字符在字符串中第一次出现的索引,这里的`ch`指的是`char`字符对应的`ASCII`码值 |
| `String replace(char oldChar, char newChar)` | 替换字符串中的字符`oldChar`为`newChar` |
| `String[] split(String regex)` | 根据`regex`分割字符串,返回一个分割后的字符串数组 |
| `byte[] getBytes()` | 获取字符串的 `byte`类型数组 |
| `char charAt(int index)` | 获取指定索引处的字符 |
| `String toLowerCase()` | 将字符串中的所有大写字母转成小写字母后返回新的字符串,注意原来的字符串没变 |
| `String toUpperCase()` | 将字符串中的所有小写字母转成大写字母后返回新的字符串,注意原来的字符串没变 |
| `String substring(int beginIndex, int endIndex)` | 截取字符串,第一位从`0`开始,包含左边`beginIndex`,不包含右边`endIndex` |
| `boolean equals(Object anObject)` | 比较字符串内容是否相等 |


**以上是比较常用的方法,更多可以查看`java.lang.String`的源码~**


#### 2.1.3 String常见面试题(附参考答案)



> 
> **(1)String重写equals、hashCode方法有什么用??**
> 
> 
> * **不重写默认是`Object`中的两个方法,`equals`默认进行双等号判断,比较的是两个对象的堆区内存地址是否相等,而`hashCode`则是一个`native`本地方法,内部会自行计算出一个唯一随机整数值返回**
> * **`String`都重写了`equals`、`hashCode`方法,`equals`重写后比较的是字符串中的每一个字符,`hashCode`重写后则是通过数字`31`与字符串中的每一个字符的`ASCII`码值计算得到`hashCode`值**
> * **简单的说`String`重写`equals`、`hashCode`方法的主要目的是为了比较两个对象的内容是否相同,而不是比较对象的内存地址,因为两个内容一样的字符串,可能内存地址是不相同的,不是我们想要的结果。**
> 
> 
> 



> 
> **(2)重写String中的`hashCode`方法时,为什么要用`31`这个数字与字符串中的每一个字符的ASCII码值进行计算?**
> 
> 
> * **因为`31`是数学家们计算得到的一个优选质数(如果一个数只能够被1和本身整除,不能够被其他数字整除,这个数就是质数,最小质数是2,其他3,5,7,13,17…31…37…)  
>  这个优选质数能够降低哈希算法的冲突率,而且`31`能够被`JVM`优化为1右移5位后再减去1即 `31 * i = (i << 5) - i`**
> 
> 
> 



> 
> **(2)new String(“Justin”)创建了几个对象?**
> 
> 
> * **一个或者两个,使用`new`实例化,首先肯定会在堆区创建一个新对象,至于`new String`中指定的字符串常量,如果该字符串常量在`字符串常量池`中不存在,则会再次创建字符串常量池中的对象,一共两个对象~**
> * **需要注意的是`字符串常量池`是从JDK1.7开始,就从`JVM`的方法区迁移到了堆区中了,不是JDK1.8才迁移,JDK1.8是永久代被取消,同时由元空间取代了方法区~**
> 
> 
> 



> 
> **(3)定义String s1=null,String s2="",String s3 = new String(),String s4=new String("")有什么区别?**
> 
> 
> * **主要区别在于null没有分配内存,其他三种都分配了内存空间**
> * **`空字符串`也属于字符串常量,定义的引用会直接指向`字符串常量池`中的字符串,如果`字符串常量池`不存在`空字符串`,则该过程会在`字符串常量池`中创建`空字符串`的对象。**
> * **`new String()` 由于使用了`new`实例化,必然会在堆区创建一个新对象,而`new String()`底层默认将`空字符串`作为字符串对象的值,因此该过程可能创建了`1`个对象或`2`个对象**
> * **同样`new String("")` 跟`new String()`一样也是可能创建了`1`个对象或`2`个对象~**
> 
> 
> 



> 
> **(3)String、StringBuilder及StringBuffer最大的区别是什么?**
> 
> 
> * **最大的区别在于String使用`final`修饰,表示最终类,不可继承和修改,线程安全**
> * **而StringBuilder和StringBuffer都是可修改对象,StringBuffer使用`synchronized`同步修饰方法,线程安全,StringBuilder非线程安全~**
> * **`String`在JDK1.8时`字符串常量`拼接被自动优化成了`StringBuiler`**
> * **关于字符串拼接效率,我个人通过Open JDK基准性能测试工具`JMH`对三者的new实例化对象,进行字符串的拼接测试,发现效率始终是:  
>  StringBuilder > StringBuffer > String  
>  而且在少量拼接字符串`10`个左右时,三者的拼接效率区别并不大,但是当字符串拼接的数据量比较大时,`100`左右,`String`比另外两者效率开始相差好几倍,当达到`1000`时,此时`String`的字符串拼接效率真的非常差非常差了,比另外两者效率低了即几十上百倍,这种情况应当避免使用`String`来拼接字符串~**
> 
> 
> 


### 2.2 StringBuilder


#### 2.2.1 StringBuilder特性



> 
> * ⭐ **底层继承了`AbstractStringBuilder`,实现了`Serializable`、`CharSequence`接口**
> * ⭐ **底层基于char字符数组,可以修改操作对象,非线程安全**
> * ⭐ **实例化`new StringBuffer()`时默认字节数组初始化容量大小为`16`,当容量大于当前字节数组容量时会自动进行1倍扩容再加2,每次扩容都会开辟新空间,并且进行新老字符数组的复制**
> * ⭐ **源码底层通过调用`System`的一个`native`本地方法`arraycopy`实现新老字符数组的复制,该native方法底层会直接操作内存,比一般的`for`循环遍历复制数组的效率要快很多~**
> * ⭐ **如果要操作拼接字符串,并且拼接的字符串很长,又没有给`StringBuilder`指定合适的初始化容量大小,可能会导致底层的字符数组进行多次扩容,多次申请内存空间来完成新老字符数组的复制,性能开销比较大~**
> 
> 
> 


**StringBuilder扩容机制的关键源码:**



//扩容条件:当容量大于当前字节数组容量时
if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity);

//扩容多少:会自动进行1倍扩容再加2
int newCapacity = value.length * 2 + 2;

//新老字符数组的复制
value = Arrays.copyOf(value, newCapacity);

public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
//底层操作内存进行复制原字符数组的元素到新字节数组
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}

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


#### 2.2.2 StringBuilder常用API




| 常用方法 | 说明 |
| --- | --- |
| `StringBuilder append(String str)` | 拼接字符串 |
| `String toString()` | `返回字符串内容` |
| `char charAt(int index)` | 获取指定索引的字符 |
| `StringBuilder insert(int offset, String str)` | 在指定位置`offset`之前插入字符串 |
| `void setCharAt(int index, char ch)` | 将指定位置`index`的字符替换为`ch` |
| `StringBuilder insert(int offset, String str)` | 在指定位置`offset`之前插入字符串 |
| `StringBuilder delete(int start, int end` | 删除起始位置`start`(含)到结尾位置`end`(不含)之间的字符串 |


**其他方法请查看`java.lang.StringBuilder`源码详情~**


#### 2.2.3 StringBuilder常见面试题(附参考答案)



> 
> **(1)讲一下StringBuilder的扩容机制?**
> 
> 
> * **主要结合StringBuffer特性来回答即可~**
> * ⭐ **实例化`new StringBuffer()`时默认字节数组初始化容量大小为`16`,当容量大于当前字节数组容量时会自动进行1倍扩容再加2,每次扩容都会开辟新空间,并且进行新老字符数组的复制**
> * ⭐ **源码底层通过调用`System`的一个`native`本地方法`arraycopy`实现新老字符数组的复制,该native方法底层会直接操作内存,比一般的`for`循环遍历复制数组的效率要快很多~**
> * ⭐ **如果操作的字符串很长,又没有给`StringBuilder`指定合适的初始化容量大小,可能会导致底层的字符数组进行多次扩容,多次申请内存空间来完成新老字符数组的复制,性能开销比较大~**
> 
> 
> 



> 
> **(2)String、StringBuilder及StringBuffer最大的区别是什么?**
> 
> 
> * **同String常见面试问题解答即可~**
> 
> 
> 


### 2.3 StringBuffer


#### 2.3.1 StringBuffer特性



> 
> * ⭐ **StringBuffer底层实现与StringBuffer最大的区别在于方法使用了`synchronized`(自创谐音:星可nice的,哈哈哈)同步修饰,因此是线程安全的,StringBuilder非线程安全~**
> 
> 
> 


#### 2.3.2 StringBuffer常用API


**跟StringBuilder常用API一样,只不过加了`synchronized`修饰,线程安全~**


#### 2.3.3 StringBuffer常见面试题(附参考答案)



> 
> **(1)StringBuffer为什么是线程安全的?**
> 


### 一、网安学习成长路线图


网安所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/aa7be04dc8684d7ea43acc0151aebbf1.png)


### 二、网安视频合集


观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f0aeee2eec7a48f4ad7d083932cb095d.png)


### 三、精品网安学习书籍

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/078ea1d4cda342f496f9276a4cda5fcf.png)


### 四、网络安全源码合集+工具包

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。  
 **需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)**

![在这里插入图片描述](https://img-blog.csdnimg.cn/e54c0bac8f3049928b488dc1e5080fc5.png)


### 五、网络安全面试题


最后就是大家最关心的网络安全面试题板块  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/15c1192cad414044b4dd41f3df44433d.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/b07abbfab1fd4edc800d7db3eabb956e.png)  



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值