关于String、StringBuffer、StringBuilder

2023-09-21 14:25:25

1. String可以被继承吗?

String类由final修饰,所以不能被继承。

扩展阅读

在Java中,String类被设计为不可变类,主要表现在它保存字符串的成员变量是final的。

Java 9之前字符串采用char[]数组来保存字符,即 private final char[] value;

Java 9做了改进,采用byte[]数组来保存字符,即 private final byte[] value;

之所以要把String类设计为不可变类,主要是出于安全和性能的考虑,可归纳为如下4点。

由于字符串无论在任何 Java 系统中都广泛使用,会用来存储敏感信息,如账号,密码,网络路径,文件处理等场景里,保证字符串 String 类的安全性就尤为重要了,如果字符串是可变的,容易被篡改,那我们就无法保证使用字符串进行操作时,它是安全的,很有可能出现 SQL 注入,访问危险文件等操作。

在多线程中,只有不变的对象和值是线程安全的,可以在多个线程中共享数据。由于 String 天然的不可变,当一个线程”修改“了字符串的值,只会产生一个新的字符串对象,不会对其他线程的访问产生副作用,访问的都是同样的字符串数据,不需要任何同步操作。

字符串作为基础的数据结构,大量地应用在一些集合容器之中,尤其是一些散列集合,在散列集合中,存放元素都要根据对象的 hashCode() 方法来确定元素的位置。由于字符串 hashcode 属性不会变更,保证了唯一性,使得类似 HashMap,HashSet 等容器才能实现相应的缓存功能。由于 String 的不可变,避免重复计算 hashcode,只要使用缓存的 hashcode 即可,这样一来大大提高了在散列集合中使用 String 对象的性能。

当字符串不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池失去意义,基于常量池的 String.intern() 方法也失效,每次创建新的字符串将在堆内开辟出新的空间,占据更多的内存。

因为要保证String类的不可变,那么将这个类定义为final的就很容易理解了。如果没有final修饰,那么就会存在String的子类,这些子类可以重写String类的方法,强行改变字符串的值,这便违背了String类设计的初衷。

字符串是在开发中是使用最多的数据类型,比如存储用户名,密码,性别等字符串的使用是很耗费内存的,内存是软件在运行过程中十分宝贵的资源。为了节省内存,提高系统的性能,Java对字符串占用内存做了很多优化,以节省内存.

2. 关于字符串的优化总结:

  1. 字符串的私有和共享问题:
    • 字符串对象是私有的,要独立分配内存
    • 字面量是共享的,内存可复用
  2. StringBuffer和StringBuilder类的优化:
    • 优化了用 + 拼接字符串的情况
    • Java8 时,其底层使用预估长度的char数组,用 + 连接字符串时就是向数组中追加字符串,追加的字符串超过数组长度时,需要对数组扩容
    • Java9 时,底层使用byte数组,用 + 拼接前,先计算拼接字符串的总长度,根据总长度定义数组大小,避免了数组的扩容。
  3. intern()方法的优化:
    • Java8之前,intern()方法将堆中new出来的字符串对象的值拷贝到字符串常量池中,以备复用。
    • Java8开始:intern() 方法将堆中new出来的字符串对象的引用拷贝到字符串常量池中,以备复用。
  4. 常量池的优化:
    • jdk1.6时,字符串常量池放在方法区中,方法区的内存回收机率低,导致字符串长时间占用内存。
    • JDK1.7时,字符串常量池放在中,堆的内存回收机率高,字符串不会长时间占用内存。

3. String、StringBuffer、StringBuilder 的区别?

区别1:可变性

String 是不可变的(不可变的原因点击这里)。StringBuilder 与 StringBuffer 都继承自AbstractStringBuilder类,在 AbstractStringBuilder 中也是使用字节数组保存字符串,不过没有使用 finalprivate 关键字修饰,最关键的是这个 AbstractStringBuilder 类还提供了很多修改字符串的方法比如 append 方法。

区别2:线程安全性

String 中的对象是不可变的,也就可以理解为常量,所以是线程安全
AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

区别2:性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

4. 说一说String和StringBuffer有什么区别

String类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。

StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。

5. 说一说StringBuffer和StringBuilder有什么区别

StringBuffer、StringBuilder都代表可变的字符串对象,它们有共同的父类 AbstractStringBuilder,并且两个类的构造方法和成员方法也基本相同。不同的是,StringBuffer是线程安全的,而StringBuilder是非线程安全的,所以StringBuilder性能略高。一般情况下,要创建一个内容可变的字符串,建议优先考虑StringBuilder类。

7. 使用字符串时,new和""推荐使用哪种方式?

先看看 “hello” 和 new String(“hello”) 的区别:

  • 当Java程序直接使用 "hello" 的字符串直接量时,JVM将会使用常量池来管理这个字符串;

  • 当使用 new String("hello") 时,JVM会先使用常量池来管理 “hello” 直接量,再调用String类的构造器来创建一个的String对象,新创建的String对象被保存在堆内存中

显然,采用new的方式会多创建一个对象出来,会占用更多的内存,所以一般建议使用直接量的方式创建字符串

8. 说一说你对字符串拼接的理解

拼接字符串有很多种方式,其中最常用的有4种,下面列举了这4种方式各自适合的场景。

  1. + 运算符:如果拼接的都是字符串直接量,则适合使用 + 运算符实现拼接;

  2. StringBuilder:如果拼接的字符串中包含变量,并不要求线程安全,则适合使用StringBuilder;

  3. StringBuffer:如果拼接的字符串中包含变量,并且要求线程安全,则适合使用StringBuffer;

  4. String类的concat方法:如果只是对两个字符串进行拼接,并且包含变量,则适合使用concat方法;

扩展阅读

采用 + 运算符拼接字符串时:

  • 如果拼接的都是字符串直接量,则在编译时编译器会将其直接优化为一个完整的字符串,和你直接写一个完整的字符串是一样的,所以效率非常的高。

  • 如果拼接的字符串中包含变量,则在编译时编译器采用StringBuilder对其进行优化,即自动创建StringBuilder实例并调用其append()方法,将这些字符串拼接在一起,效率也很高。但如果这个拼接操作是在循环中进行的,那么每次循环编译器都会创建一个StringBuilder实例,再去拼接字符串,相当于执行了 new StringBuilder().append(str),所以此时效率很低。

采用StringBuilder/StringBuffer拼接字符串时:

  • StringBuilder/StringBuffer都有字符串缓冲区,缓冲区的容量在创建对象时确定,并且默认为16。当拼接的字符串超过缓冲区的容量时,会触发缓冲区的扩容机制,即缓冲区加倍。

  • 缓冲区频繁的扩容会降低拼接的性能,所以如果能提前预估最终字符串的长度,则建议在创建可变字符串对象时,放弃使用默认的容量,可以指定缓冲区的容量为预估的字符串的长度。

采用String类的concat方法拼接字符串时:

  • concat方法的拼接逻辑是,先创建一个足以容纳待拼接的两个字符串的字节数组,然后先后将两个字符串拼到这个数组里,最后将此数组转换为字符串。

  • 在拼接大量字符串的时候,concat方法的效率低于StringBuilder。但是只拼接2个字符串时,concat方法的效率要优于StringBuilder。并且这种拼接方式代码简洁,所以只拼2个字符串时建议优先选择concat方法。

9. 两个字符串相加的底层是如何实现的?

如果拼接的都是字符串直接量,则在编译时编译器会将其直接优化为一个完整的字符串,和你直接写一个完整的字符串是一样的。

如果拼接的字符串中包含变量,则在编译时编译器采用StringBuilder对其进行优化,即自动创建StringBuilder实例并调用其append()方法,将这些字符串拼接在一起。

10. String a = “abc”; ,说一下这个过程会创建什么,放在哪里?

JVM会使用常量池来管理字符串直接量。在执行这句话时,JVM会先检查常量池中是否已经存有"abc",若没有则将"abc"存入常量池,否则就复用常量池中已有的"abc",将其引用赋值给变量a。

11. new String(“abc”) 是去了哪里,仅仅是在堆里面吗?

在执行这句话时,JVM会先使用常量池来管理字符串直接量,即将"abc"存入常量池。然后再创建一个新的String对象,这个对象会被保存在堆内存中。并且,堆中对象的数据会指向常量池中的直接量。

更多推荐

二蛋赠书三期:《C#入门经典(第9版)》

文章目录前言活动规则参与方式本期赠送书籍介绍作者介绍内容简介读者对象获奖名单结语前言大家好!我是二蛋,一个热爱技术、乐于分享的工程师。在过去的几年里,我一直通过各种渠道与大家分享技术知识和经验。我深知,每一位技术人员都对自己的技能提升和职业发展有着热切的期待。因此,我非常感激大家一直以来对我的关注和支持。为了回馈大家的

[DB]数据库--lowdb

[DB]数据库--lowdblowdb基本应用获取数据数据变更写入文件lodash的使用获取数据lodash方法使用数据变更写入文件lowdblowdb,是一个基于文件存储的非关系型数据库基于loadsh的轻量级数据库可用于在json中存储数据,大小一般为0~200M的json文件方便简单的数据存储,快速的实现数据的增

Keepalived 高可用(附带配置实例,联动Nginx和LVS)

Keepalived一、Keepalived相关知识点概述1.1单服务的风险(单点故障问题)1.2一个合格的集群应该具备的特性1.3VRRP虚拟路由冗余协议1.4健康检查1.5”脑裂“现象二、Keepalived2.1Keepalived是什么?2.2Keepalived体系主要模块及其作用2.3Keepalived工

Git --- 基础介绍

Git---基础介绍git是什么git---工作区,暂存区,资源库git---文件状态git---branch和HEADgit---一次正常的git提交流程git是什么Git是一款分布式源代码管理工具(版本控制工具)Git和其他传统版本控制系统比较:传统的版本控制系统(例如SVN)是基于差异的版本控制,它们存储的是一组

HSRP(热备份路由选择协议)的概念,原理与配置实验

作者:Insist--个人主页:insist--个人主页梦想从未散场,传奇永不落幕,持续更新优质网络知识、Python知识、Linux知识以及各种小技巧,愿你我共同在CSDN进步目录一、了解HSRP协议1.什么是HSRP协议2、HSRP协议的作用二、HSRP组成员1.活跃路由器2.备份路由器3.虚拟路由器4.其他三、H

Java——String类

一、String类String是引用类型,在Java中“”引起来的也是String类型对象。//打印"hello"字符串(String对象)的长度System.out.println("hello".length());内部并不存储字符串本身,在String类的实现源码中,String类实例变量如下:publicsta

Linux【一】

目录一、Linux操作系统发展历史UnixMinixLinux二、Linux简介Linux是什么Linux的版本Linux内核版本Linux发行版本Linux应用领域?Linux注意事项三、Linux目录系统目录用户目录文件颜色四、Linux命令行基本操作Linux命令格式:查看帮助文档tab键自动补全命令输入历史命令

Nacos源码启动报错:protoc did not exit cleanly. Review output for more information.

报错解析:这是一个关于Protobuf(ProtocolBuffers)编译器出现问题的错误信息。Protobuf是一种用于结构化数据序列化的工具,该错误提示表明Protobuf编译器在执行过程中出现了问题,并建议检查输出以获取更多信息。快速解决:启动Nacos源码出现这个这报错,到这bean没有被创建,可以使用ide

边界框回归的魔法:揭秘精准高效的MPDIoU损失函数

文章目录摘要1、简介2、相关工作2.1、目标检测和实例分割2.2.场景文本识别2.3、边界框回归的损失函数3、点距最小的并集交点4、实验结果4.1、实验设置4.2、数据集4.3、评估协议4.4、目标检测的实验结果4.5、字符级场景文本识别的实验结果4.6、实例分割的实验结果5、结论摘要https://arxiv.org

分享一下微信拼团活动制作步骤是什么

微信拼团活动是一种非常受欢迎的营销手段,可以帮助商家吸引更多的消费者,提高销售额和品牌知名度。下面我将为大家详细介绍如何制作微信拼团活动。一、了解拼团活动特点和优势拼团活动是一种以社交网络为依托的营销方式,通过将商品以团购的形式推送给消费者,让消费者通过拼团的方式购买商品,享受团购优惠。拼团活动的特点和优势如下:参与门

机器学习实战:Python基于LASSO回归进行正则化(十二)

文章目录1前言1.1LASSO的介绍1.2LASSO的应用2.diabetes数据集实战演示2.1导入函数2.2导入数据2.3拟合模型(AIC/BIC)2.4AIC/BIC可视化2.5拟合交叉验证模型及可视化3.Hitters数据集实战演示3.1导入函数3.2导入数据3.3数据预处理3.4定义变量和缩放数据3.5拟合模

热文推荐