作为作为一个已经入了门的java程序猿,肯定对Java中的String、StringBuffer和StringBuilder都略有耳闻了,尤其是String 肯定是经常用的。但肯定你有一点很好奇,为什么java中有三个关于字符串的类?一个不够吗!先回答这个问题,黑格尔曾经说过——存在必合理,单纯一个String确实是不够的,所以要引入StringBuffer。再后来引入StringBuilder是另一个故事了,后面会详细讲到。

  要了解为什么,我们就得先来看下这三者各自都有什么样的特点,有什么样的异同,对其知根知底之后,一切谜团都会被解开。

String

  点开String的源码,可以发现String被定义为final类型,意味着它不能被继承,再仔细看其提供的方法,没有一个能对原始字符串做任何操作的,有几个开启了貌似是操作原字符串的,比如replaceFirst replaceAll,点进去一看,其实是重新生成了一个新的字符串,对原始内容没有做任何修改。

  是的,从实现的角度来看,它是不可变的,所有String的变更其实都会生成一个新的字符串,比String str = "abcdefghijklmnopqrstuvwxy"; str = str + "z"; 之后新生成的a-z并不包含原来的a-y,原来的a-y已经变成垃圾了。简单概括,只要是两个不同的字符,肯定都是两个完全不同不相关的对象,即便其中一个是从另一个subString出来的,两个也没有任何关系。 如果是两个相同的字符串,情况比较复杂,可能是同一份也可能不是。如果在JVM中使用G1gc,而且开启-XX:+UseStringDeduplication ,JVM会对字符串的存储做优化,所以如果你的服务中有大量相同字符串,建议开启这个参数。

  Java作为一个非纯面向对象的语言,除了提供分装对象外,也提供了一些原始类型(比如:int long double char),String的使用居然可以像用原始类型一样不需要new,直接String str = "a"这样声明,我觉得String更像是面向对象和非面向对象结合的一个产物。

  String最大的特点就是 __ 不可变__,这是它的优点,因为不可变意味着使用简单,没有线程安全的问题。 但这也是它的缺点,因为每次变更都会生成一个新的字符串,明显太浪费空间了。

StringBuffer

  我觉得StringBuffer是完全因为String的缺点而生的。我们日常使用String的过程中,肯定经常会用到字符串追加的情况,按String的实现,没次追加即便只是一个字符,都是生成一个完全不同的对象,如果这次操作很频繁很多的话会大幅提高内存的消耗,并且增加gc的压力。对于这种问题,StringBuffer是如何解决的呢?我们直接从源码上来看。



  但看StringBuffer里,几乎所有的方法都会调super父类,其实它所有的实现都是在AbstractStringBuilder里的。鉴于我们对其最长用的方法是append,所以我们就从append入手,其实append也是StringBuffer比较核心的功能。

    /**
* The value is used for character storage.
*/
char[] value; AbstractStringBuilder(int capacity) {
value = new char[capacity];
} public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}

  原来是StringBuffer父类AbstractStringBuilder有个char数组value,用来存放字符串所有的字符,StringBuffer默认初始大小是16。StringBuffer在每次append的时候,如果value的容量不够,就会申请一个容量比当前所需大一倍的字符数组,然后把旧的数据拷贝进去。这种一次性扩容一倍的方式,在我们之前HashMap源码浅析中已经看到过了。一次性多申请内存,虽然看起来会有大段的内存空闲,但其实可以减少String append时频繁创建新字符串的问题。

  所以记住,如果你代码中对String频繁操作,千万不用用String而是选择用StringBuffer或者我们下面要讲的StringBuilder。还有一个优化点,如果你能提前知道你字符串最大的长度,建议你在创建StringBuffer时指定其capacity,避免在append时执行ensureCapacityInternal,从而提升性能。

  对于StringBuffer还有一个点没提到,注意看它源码的所有方法,除构造函数外,所有的方法都被__synchronized__修饰,意味着它是有个线程安全的类,所有操作查询方法都会被加同步,但是如果我们只是单线程呢,想用StringBuffer的优势,但又觉得加同步太多余,太影响性能。这个时候就轮到StringBuilder上场了。

StringBuilder



  StringBuilder从类图上看和StringBuffer完全没有任何区别,再打开它的源码,和StringBuffer一样几乎啥逻辑都没有,全是调调super父类AbstractStringBuilder,它和StringBuffer最大的区别就是所有方法没有用synchronized修复,它不是一个线程安全的类,但也意味着它没有同步,在单线程情况下性能会优于StringBuffer。

总结

  看完上面内容,我觉得你应该知道上面时候用String、什么时候用StringBuffer、什么时候用StringBuilder了。

  1. 如果是常量字符串,用String。
  2. 多线程环境下经常变动的字符串用StringBuffer。
  3. 单线程经常变动的字符串用StringBuilder。

彩蛋

  我们来看个比较底层的东西,是关于jvm对String优化的,现在有如下代码。

public class StringTest {
public static void main(String[] args) {
String str = "abc";
str = str + "d";
str = str + "e";
}
}

  我们用javac StringTest.java编译成class文件,然后用javap -c StringTest 生成字节码,内容如下

public class StringTest {
public StringTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #2 // String abc
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String d
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: return
}
➜ java git:(master) ✗ javap -c StringTest
Compiled from "StringTest.java"
public class StringTest {
public StringTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #2 // String abc
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String d
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: return
}
➜ java git:(master) ✗ javac StringTest.java
➜ java git:(master) ✗ javap -c StringTest
Compiled from "StringTest.java"
public class StringTest {
public StringTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #2 // String abc
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String d
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: new #3 // class java/lang/StringBuilder
26: dup
27: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
30: aload_1
31: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: ldc #8 // String e
36: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
42: astore_1
43: return
}

  其实可以看出,java底层实现字符串+的时候其实是用StringBuilder的append()来实现的,如果有字符串的连续+,jvm用StringBuilder append也可以实现优化。

备注:源码来自JDK11

本文由博客一文多发平台 OpenWrite 发布!

Java中的String、StringBuffer和StringBuilder的更多相关文章

  1. Android/Java 中的 String, StringBuffer, StringBuilder的区别和使用

    Android 中的 String, StringBuffer 和 StringBuilder 是移动手机开发中经常使用到的字符串类.做为基础知识是必须要理解的,这里做一些总结. A.区别 可以从以下 ...

  2. 重温java中的String,StringBuffer,StringBuilder类

    不论什么一个系统在开发的过程中, 相信都不会缺少对字符串的处理. 在 java 语言中, 用来处理字符串的的类经常使用的有 3 个: String.StringBuffer.StringBuilder ...

  3. 在JAVA中,String,Stringbuffer,StringBuilder 的区别

    首先是,String,StringBuffer的区别 两者的主要却别有两方面,第一是线程安全方面,第二是效率方面 线程安全方面: String  不是线程安全的,这意味着在不同线程共享一个String ...

  4. Java中的String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  5. Java中的String,StringBuilder,StringBuffer三者的区别(转载)

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  6. 转:Java中的String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  7. [转载]Java中的String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  8. Java中的String,StringBuilder,StringBuffer的区别

    这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > ...

  9. 【转】Java中的String,StringBuilder,StringBuffer三者的区别

    https://www.cnblogs.com/su-feng/p/6659064.html 最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及String ...

  10. Java中的String,StringBuilder,StringBuffer三者的区别(转发:https://www.cnblogs.com/su-feng/p/6659064.html)

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

随机推荐

  1. 在Maven项目中添加代码目录下的配置文件

    问题 Maven 是约定大于配置的一种工具, 通常约定在 src/main/resources 目录下放配置文件, 当我们想要在 src/main/java 代码目录下放置配置文件用来测试, Mave ...

  2. 【Scala】什么是隐式转换?它又能用来干嘛?该怎么用

    文章目录 定义 隐式参数 隐式转换 隐式值:给方法提供参数 隐式视图 将Int和Double类型转换为String 狗狗学技能(使用别的类中的方法) 使用规则 定义 隐式参数 隐式参数指在函数或者方法 ...

  3. 【MySQL基础总结】常用函数库

    常用函数库 数学函数 分类及含义 示例 字符串函数 分类及含义 示例 日期时间函数 分类及含义 示例 条件判断函数 分类及含义 示例 系统函数 分类及含义 加密函数 分类及定义 其他常用函数 分类及含 ...

  4. 初级PLC

    SMB2接收到一个数据即产生一次中断,必须在中断处理程序中将数据从SMB2中读出,依次填表.这是一种效率极低的通讯处理方法,通讯字节多了会影响其它程序的运行. M 是位地址.比如M0.0,M0.1等. ...

  5. 正则表达式在java中的用法

    /** * 测试正则表达式的基本用法 Pattern 和 Matcher * @author 小帆敲代码 * */public class Demo01 {  public static void m ...

  6. 11JAVA基础-集合

    一.集合 ` 二.Collection类 Collection 是单列的顶层类. Collection是接口. 创建对象需要借助多态. //e为集合中数据类型 //ArrayList是List的实现类 ...

  7. 2020网鼎杯 白虎组reverse:hero

    主函数,当bossexist的值不为0时,while循环dround()函数,循环结束输出flag outflag()函数的flag值由6段数据拼凑而成 while循环的dround()函数有三个选择 ...

  8. python第二课list基本命令

    列表: stus= 'ada,dsfas.,saf,sdfas,saf'  #字符串new_stus = ['段','加','linux','123','数组']   #列表 取值方便#列表,数组,l ...

  9. ORACLE 统计文件后缀SQL

    ORACLE 统计文件后缀SQL查询语句 select lower(substr(file_name,instr(file_name, '.', -1),length(file_name))),cou ...

  10. @vue/cli 4.0.5 学习记录

    1. Vue CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue 命令.Vue CLI 插件的名字以 @vue/cli-plugin- (内建插件) 或 vue-cli ...