还搞不清楚String、StringBuilder、StringBuffer?

目录

一、String——引用类型,而不是基本数据类型

二、StringBuffer类

三、StringBuilder类        

四、String、StringBuffer、StringBuilder比较

五、String、StringBuffer、StringBuilder的选择


一、String——引用类型,而不是基本数据类型

1.先来看看String继承了哪些类、实现了哪些接口(我的jdk版本是21)

2.从图可以看出String实现了5个接口,然后我们知道每个类都继承了Object类,所以这个就忽略了,我们就来看一下这几个接口的作用。

接口作用
Serializable表示一个类的对象可以被序列化和反序列化(就是可以将一个对象存到网络或者本地中)
Comparable表示String可以比较大小
CharSequence表示一系列字符的接口。它提供了一些方法来处理和访问字符序列
Constableconstable 类型是指其值是常量的类型,这些常量可以在 Java 类文件的常量池中表示
ConstantDesc用于描述常量池中的常量

3.String的特点:

        3.1   String类不能被继承,因为被final关键字修饰。

        3.2   String内部使用private final byte[] value来存储字符串数据。

        注:jdk<=8的版本使用的是char[] 类型来保存的。为什么呢?主要是因为节省内存空间,官方文档有说明:JEP 254: Compact Strings

         简单解释一下:就是我们大多数情况下使用的字符都是英文字符,然后英文字符需要一个字节,如果使用char的话,就会浪费空间(一个char是2个字节,浪费1个字节的空间)。使用byte就不会浪费空间(一个byte是1个字节)。

        3.3    value被final关键字修饰,就说明value不可修改,这里不可修改指的是value所指的地址不能修改,里面的数据还是可以修改的。看图:

        3.4   修改String类型的变量的值,会重新指向常量池的某个字符串常量,下面会单独讲。 

4.创建String的两种方式:

第一种:String str1 = “hello”;      “hello”就是一个常量

第二种:String str2 =  new String("hello");

        区别:方式一 是先从常量池查看是否有"hello"数据,如果有,则直接指向;如果没有则创建,然后指向。str1最终指向的是常量池的某个常量的内存地址。

                  方式二 是先从堆中创建空间,里面维护了value属性,指向常量池的"hello"内存地址。如果常量池没有"hello",则创建;如果有,直接通过value指向。最终str2指向的是堆中的String对象内存地址。

        内存图:

5.当为String类型的变量重新赋值会发生什么?

String str = "hello";

str = "hello world"; 

        5.1   当执行String str = "hello"会发生什么?它首先会去看运行时常量池是否有"hello",如果常量池没有,则要在常量池中创建一个"hello",然后str指向常量池的"hello";如果常量池有,则str直接指向常量池的"hello"。

        5.2   当执行str = "hello world"会发生什么?首先会去看常量池是否有"hello world",如果常量池没有,则要在常量池中创建一个"hello world",然后str指向常量池的"hello world";如果常量池有,则str直接指向常量池的"hello world"。

        总结:可以看出String效率很低,如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率;如果这样的操作放到循环中,会极大影响程序的性能,所以如果我们需要对String做大量修改,就不要使用String。


二、StringBuffer类

1.先来看看StringBuffer继承了哪些类、实现了哪些接口(我的jdk版本是21)

2.从图可以看出StringBuffer实现了4个接口,继承了AbstractStringBuilder类,我们就来看一下Appendable接口作用吧,其它的String已经讲过了,就不讲了。

接口作用
Appendable定义了可以追加字符序列的类

3.StringBuffer的特点

        3.1   StringBuffer也不能被继承,因为被final关键字修饰。

        3.2   StringBuffer内部是使用父类AbstractStringBuilder的成员变量byte[] value来保存字符串数据的

        注:jdk<=8的版本使用的是char[] 类型来保存的。

        3.3   注意byte[] value没有被final关键字修饰,所以StringBuffer是可变的。

        3.4   byte[] value指向的是堆中byte[]对象,而不是指向常量池的某个字符串常量,下面会单独讲。

        3.5   它是线程安全的,内部的方法都用synchronized来修饰。

4.使用StringBuffer

StringBuffer buffer = new StringBuffer("hello");

        代码解释:在堆中创建StringBuffer对象,然后buffer指向该对象,"hello"存在StringBuffer的成员变量value中,value是指向堆的一个byte[]对象,这个byte[]对象的值为"hello"。

        内存图:

5.当为StringBuffer类型的变量重新赋值会发生什么?

StringBuffer buffer = new StringBuffer("hello");

buffer = new StringBuffer("world");

buffer.append(" java");

        当为buffer重新赋值时,就是在堆中重新创建一个StringBuffer对象,然后buffer指向该对象,new StringBuffer("hello");这个对象没有被引用,所以就会被垃圾回收器回收。

        当执行buffer.append(" java")时,它会直接将" java"添加到world后面,所以buffer的内容就变成了"world java"。

6.value的大小为多少呢?

        当调用的是public StringBuffer()时,会初始化大小为16。

        当调用public StringBuffer(int capacity)时,大小为外部设置的值。

         当调用public StringBuffer(String str)时,它先获取字符串的长度,然后进行判断,如果长度小于Integer.MAX_VALUE-16的话,容量就设为length+16;如果长度大于Integer.MAX_VALUE-16的话,容量就设置为Integer.MAX_VALUE(int MAX_VALUE = 0x7fffffff = 2的31次方-1)。

        当调用AbstractStringBuilder(CharSequence seq)时,流程和public StringBuffer(String str)一样。

 7.详细讲解append方法

        首先判断传进来的str是否为null,如果为null,则为当前StringBuffer的内容追加null。但是如果当前容量不够追加null,则会执行方法ensureCapacityInternal(count + 4);进行扩容(它怎么扩容的一会我们在讲),它扩容四个字符,存放null4个字符,例如:

        如果str不为null,则先获取str的长度,然后进行判断是否需要扩容(扩容一会再说),如果需要则ensureCapacityInternal(count + len);扩容已用字符个数加str长度。然后进行复制的方式,进行修改内容,就是先将value的内容复制到一个新的byte[]中,然后将追加的字符添加到byte[]中,最后将新byte[]赋值给value。

        讲讲扩容:将传进来的值和旧容器大小进行比较,如果小于0,则不需要扩容,还能再用;如果大于0,则需要扩容,然后通过复制达到扩容。

 8.StringBuffer是线程安全的!

        它的全部方法都用了synchronized关键字修饰。

9.String VS StringBuffer

  •  String保存的是字符串常量,里面的值不能更改,每次String类的修改,实际上就是修改指向的地址,效率低。
  • StringBuffer保存的是字符串变量,里面的值是可以更改的,每次StringBuffer的更新实际上是直接更新内容(使用append),不用每次更新地址,效率极高
  • String和StringBuffer都是线程安全的,因为String是不可变的,StringBuffer的方法都使用了synchronized关键字修饰。

三、StringBuilder类        

1.先来看看StringBuilder继承了哪些类、实现了哪些接口(我的jdk版本是21)

2.对应接口的作用可以看上面,这里就不一一介绍了。

3.StringBuilder的特点

        3.1   StringBuilder也不能被继承,因为被final关键字修饰。

        3.2   StringBuilder内部是使用父类AbstractStringBuilder的成员变量byte[] value来保存字符串数据的

        注:jdk<=8的版本使用的是char[] 类型来保存的。

        3.3   注意byte[] value没有被final关键字修饰,所以StringBuilder是可变的。

        3.4   byte[] value指向的是堆中byte[]对象,而不是指向常量池的某个字符串常量,下面会单独讲。

        3.5   它是线程不安全的。

4.使用StringBuilder

StringBuilder builder= new StringBuilder("hello");

        代码解释:在堆中创建StringBuilder对象,然后builder指向该对象,"hello"存在StringBuilder 的成员变量value中,value是指向堆的一个byte[]对象,这个byte[]对象的值为"hello"。

        内存图就不画了,和buffer是一样的,大家可以试着去画一下。


四、String、StringBuffer、StringBuilder比较

  • StrignBuffer和StringBuilder非常相似,均代表可变的字符序列,而且方法也一样。
  • String是不可变字符序列,效率低,但复用率高(多个String指向同一个字符串数据)。
  • StringBuffer是可变字符序列,效率较高、线程安全。
  • StringBuilder是可变字符序列,效率最高、线程不安全。

        怎么证明呢?我们来使用代码证明一下:

        解释代码:用循环修改String、StringBuffer、StringBuilder里面的内容,然后看看他们各自耗时多久。运行结果:

        可以看到StringBuilder耗时最短,String耗时最长并且是StringBuffer和StringBuilder的百倍。可以看到在需要更改字符串内容时,String效率很低,会影响程序的性能。StringBuffer之所以会比StringBuilder耗时久一点,是因为它一旦访问append方法,就会先去拿锁,然后进行相应的操作。StringBuilder一旦执行append,就直接进行相应的操作。


五、String、StringBuffer、StringBuilder的选择

  • 如果字符存在大量的修改操作,一般使用StringBuffer 或 StringBuilder
  • 如果字符串存在大量的修改操作,并在单线程的情况,使用StrignBuilder
  • 如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer
  • 如果我们字符串很少修改,被多个对象引用,使用String

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

b顶峰相见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值