Java提供了三个类,用于处理字符串,分别是String、StringBuffer和StringBuilder。其中StringBuilder是jdk1.5才引入的。

这三个类有什么区别呢?他们的使用场景分别是什么呢?

本文的代码是在jdk12上运行的,jdk12和jdk5,jdk8有很大的区别,特别是String、StringBuffer和StringBuilder的实现。

jdk5和jdk8中String类的value类型是char[],到了jdk12,value类型变为byte[]。

jdk5、JDK6中的常量池是放在永久代的,永久代和Java堆是两个完全分开的区域。

到了jdk7及以后的版本,

我们先来看看这三个类的源码。

String类部分源码:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc { @Stable
private final byte[] value; public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
public native String intern();

String类由final修饰符修饰,所以String类是不可变的,对象一旦创建,不能改变。

String类中有个value的字节数组成员 变量,这个变量用于存储字符串的内容,也是用final修饰,一旦初始化,不可改变。

java提供了两种主要方式创建字符串:

//方式1
String str = "123";
//方式2
String str = new String("123");

java虚拟机规范中定义字符串都是存储在字符串常量池中,不管是用方式1还是方式2创建字符串,都会从去字符串常量池中查找,如果已经存在,直接返回,否则创建后返回。

java编译器在编译java类时,遇到“abc”,“hello”这样的字符串常量,会将这些常量放入类的常量区,类在加载时,会将字符串常量加入到字符串常量池中。

含有表达式的字符串常量,不会在编译时放入常量区,例如,String str = "abc" + a

常量池的最大作用是共享使用,提高程序执行效率。

看看下面几个案例。

案例1:

1  String str1 = "123";
2 String str2 = "123";
3 System.out.println(str1 == str2);

上面代码运行的结果为true。

运行第1行代码时,现在常量池中创建字符串123对象,然后赋值给str1变量。

运行第2行代码时,发现常量池已经存在123对象,则直接将123对象的地址返回给变量str2。

str1和str2变量指向的地址一样,他们是同一个对象,因此运行的结果为true。

从图中可以看出,str1使用””引号(也是平时所说的字面量)创建字符串,在编译期的时候就对常量池进行判断是否存在该字符串,如果存在则不创建直接返回对象的引用;如果不存在,则先在常量池中创建该字符串实例再返回实例的引用给str1。

案例2:

1  String str1 = new String("123");
2 String str2 = new String("123");
3 String str3 = new String(str2);
4 System.out.println((str1==str2));
5 System.out.println((str1==str3)); 6 System.out.println((str3==str2));

上面代码运行的结果是

false
false
false

从上图可以看出,执行第1行代码时,创建了两个对象,一个存放在字符串常量池中,一个存在与堆中,还有一个对象引用str1存放在栈中。

执行第2行代码时,字符串常量池中已经存在“123”对象,所以只在堆中创建了一个字符串对象,并且这个对象的地址指向常量池中“123”对象的地址,同时在栈中创建一个对象引用str2,引用地址指向堆中创建的对象。

执行第3行代码时,在堆中创建一个字符串对象,这个对象的内存地址指向变量str2所执向的内存地址。

通过new方式创建的字符串对象,都会在堆中开辟一个新内存空间,用于存储常量池中的字符串对象。

对于对象而言,==操作是用于比较两个独享的内存地址是否一致,所以上面的代码执行的结果都是false。

案例3:

//这行代码编译后的效果等同于String str1 = "abcd";
String str1 = "ab" + "cd";
String str2 = "abcd";
System.out.println((str1 == str2));

上面代码执行的结果:true。

使用包含常量的字符串连接创建的也是常量,编译期就能确定了,类加载的时候直接进入字符串常量池,当然同样需要判断字符串常量池中是否已经存在该字符串。

案例4:

String str2 = "ab";  //1个对象
String str3 = "cd"; //1个对象
String str4 = str2 + str3 + “1”;
String str5 = "abcd1";
System.out.println((str4==str5));

上面代码执行的结果:false。

当使用“+”连接字符串中含有变量时,由于变量的值是在运行时才能确定。

如果使用的jdk8以前版本的虚拟机,在拼接字符串时,会在jvm堆中生成StringBuilder对象,调用append方法拼接字符串,最后调用StringBuilder的toString方法在jvm堆中生成最终的字符串对象。

通过查看字节码就可以知道jdk8之前版本的"+"拼接字符串时通过StringBuilder实现的。通过查看字节码就可以知道,如下图所示:

而如果使用的是jdk9以后版本的虚拟机,则是调用虚拟机自带的InvokeDynamic拼接字符串,并且保存在堆中。字节码如下所示:

str4的对象在字符串常量池中,str5的对象在堆中,所以他们的不是同一个对象,所以返回的结果是false。

案例5:

String s5 = new String(“2”) + new String(“3”);

和案例4一样,因为new String("2")创建字符串,也是在运行时才能确定对象内存地址,和案例4一样。

案例6:

final String str1 = "b";
String str2 = "a" + str1;
String str3 = "ab";
System.out.println((str2 == str3));

上面代码执行的结果为true。

str1是常量变量,在编译期就确定,直接放入到字符串常量池中,上面的代码效果等同于:

String str2 = "a" + "b";
String str3 = "ab";
System.out.println((str2 == str3));

调用String类的intern()方法,会将堆中的字符串实例放入到字符串常量池中。

案例7:

String str2 = "ab";
String str3 = "cd";
String str4 = str2 + str3 + "1";
str4.intern();
String str5 = "abcd1";
System.out.println((str4==str5));

上面代码执行的结果:true。调用了str4.intern()方法后,将str4放入到字符串常量池中,和str5是同一个实例。

StringBuffer部分源码:

 public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuffer>, CharSequence
{

StringBuilder部分源码:

public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, Comparable<StringBuilder>, CharSequence
{

可见StringBuffer和StringBuilder都继承了AbstractStringBuilder类。

AbstractStringBuilder类源码:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
byte[] value;

AbstractStringBuilder也有一个字节数组的成员变量value,这个变量用于存储字符串的值,这个变量不是用final修饰,所以是可以改变的,这个是和String的最大区别。

在调用append方法的时候,会动态增加字节数组变量value的大小。

StringBuffer和StringBuilder功能是一样的,都是为了提高java中字符串连接的效率,因为直接使用+进行字符串连接的话,jvm会创建多个String对象,因此造成一定的开销。AbstractStringBuilder中采用一个byte数组来保存需要append的字符串,byte数组有一个初始大小,当append的字符串长度超过当前char数组容量时,则对byte数组进行动态扩展,也即重新申请一段更大的内存空间,然后将当前bute数组拷贝到新的位置,因为重新分配内存并拷贝的开销比较大,所以每次重新申请内存空间都是采用申请大于当前需要的内存空间的方式,这里是2倍。

StringBuffer和StringBuilder最大的区别是StringBuffer是线程安全,而StringBuilder是非线程安全的,从它们两个类的源码就可以知道,StringBuffer类的方法前面都是synchronized修饰符。

String一旦赋值或实例化后就不可更改,如果赋予新值将会重新开辟内存地址进行存储。

而StringBuffer和StringBuilder类使用append和insert等方法改变字符串值时只是在原有对象存储的内存地址上进行连续操作,减少了资源的开销。

总结:

1、频繁使用“+”操作拼接字符时,换成StringBuffer和StringBuilder类的append方法实现。

2、多线程环境下进行大量的拼接字符串操作使用StringBuffer,StringBuffer是线程安全的;

3、单线程环境下进行大量的拼接字符串操作使用StringBuilder,StringBuilder是线程不安全的。

深入了解String,StringBuffer和StringBuilder三个类的异同的更多相关文章

  1. Java String, StringBuffer和StringBuilder实例

    1- 分层继承2- 可变和不可变的概念3- String3.1- 字符串是一个非常特殊的类3.2- String 字面值 vs. String对象3.3- String的方法3.3.1- length ...

  2. String,StringBuffer和StringBuilder源码解析[基于JDK6]

    最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下 ...

  3. String,StringBuffer和StringBuilder的异同

                                                                    String,StringBuffer和StringBuilder的异同 ...

  4. String,StringBuffer与StringBuilder

    1. String,StringBuffer与StringBuilder的区别 String:存储在常量池中:是不可变的字符序列,任何对String值的改变都会引发新的String对象的生成,因此执行 ...

  5. String, StringBuffer and StringBuilder

    一 String 概述: String 被声明为 final,因此它不可被继承. 在 Java 8 中,String 内部使用 char 数组存储数据. public final class Stri ...

  6. String,StringBuffer和StringBuilder

    String,StringBuffer和StringBuilder分别应该在什么情况下使用? String 是Java的字符串类,其实质上也是用Char类型存储的,但是除了hash属性,其他的属性都声 ...

  7. 面试长谈的String,StringBuffer,StringBuilder三兄弟有啥区别

    1.String: /** Strings are constant; their values cannot be changed after they * are created. String ...

  8. String StringBuffer和StringBuilder区别及性能

    结论: (1)如果要操作少量的数据用 String: (2)多线程操作字符串缓冲区下操作大量数据 StringBuffer: (3)单线程操作字符串缓冲区下操作大量数据 StringBuilder(推 ...

  9. String,StringBuffer和StringBuilder的区别

    (1)String类的API概述是这样的:String类代表字符串,Java程序中的所有字符串字面值都作为此类的实例体现.字符串是常量,它们的值在创建之后不能更改.可见,String是对象且为不可变对 ...

随机推荐

  1. Python数据类型详解——列表

    Python数据类型详解--列表 在"Python之基本数据类型概览"一节中,大概介绍了列表的基本用法,本节我们详细学一下列表. 如何定义列表:在[]内以英文里输入法的逗号,,按照 ...

  2. Bluetooth(蓝牙)连接过程分析

    一 基本概念 蓝牙的连接过程是十分重要的,特别是做蓝牙的技术人员来说,这个是十分重要的.理它的流程,是一件必修课.虽然进入蓝牙行业很久了,以前没怎么系统化的做一些事情,趁此机会,就梳理一下这里面的内容 ...

  3. D-Distance_2019牛客暑期多校训练营(第八场)

    题目链接 Distance 题意 1<=nmh,q<=1e5 q个操作 1 x y z往坐标里加入一个点 2 x y z查询距离该点最近的点的距离(曼哈顿距离) 题解 做法一 将要插入的点 ...

  4. 【CF1137C】 Museums Tour 拆点+缩点

    https://codeforc.es/contest/1137/problem/C # 题意 给你n个点,每个点有k天博物馆开放时间的安排表. 有m条单向道路,走过一条边需要一个晚上,经过后就是第二 ...

  5. GNU大型项目构建和覆盖率生成(第一篇)

    目录 0. 序言 1. 项目描述 2. 项目构建 2.1 编译规则 2.2 构建过程 3. 覆盖率分析 0. 序言 在开始正文之前,请允许我先说明一下本文的目的和写作的动机,好让读者不惑. 我们知道, ...

  6. Springboot2.x 自动创建表并且执行初始化数据

    1.使用springboot jdbc初始化数据库 项目结构 schema.sql drop table if exists user; create table user (id bigint(20 ...

  7. Spring MVC 配置类 WebMvcConfigurerAdapter

    WebMvcConfigurerAdapter配置类是spring提供的一种配置方式,采用JavaBean的方式替代传统的基于xml的配置来对spring框架进行自定义的配置.因此,在spring b ...

  8. Myeclipse项目工程目录中各种Libraries的含义

    MyEclipse工程目录下一般会有以下几类Libraries,如图: 各种Libraries的含义如下: JRE System Library:Java SE 的常用库文件集合,构建任何Java项目 ...

  9. 理解Yarn的执行流程和组件作用

    Yarn引入案例 1.学生找院长报到,院长给学生一个学号 2.院长比较忙,继续找主任处理学生事务 3.系主任找院办给学生分配资源(书本) 4.主任找张老师教授java 5.张老师给学生安排座位 6.学 ...

  10. java8新特性使用

    一.接口的默认方法(允许接口有非抽象方法)Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: 代码如下: interface ...