Java 的 String、StringBuffer 和 StringBuilder(一文讲透)

news2024/11/27 4:39:21

提到 String、StringBuffer 和 StringBuilder,就不得不谈及它们的历史,在了解它们的历史之后,我们对它们的理解将更上一级台阶!

发展历史

String 与 StringBuffer 的出现

String 和 StringBuffer 在 Java1.0 中就已经有了,目前也一直存在于各个 Java 版本之中,但是 StringBuilder 是在 Java5 中才被引入。我们都知道,String 是 Java 中的字符串类,是不可修改的,在 Java1.0 的时候,若要对字符串进行大量修改,应当使用 StringBuffer,它是可修改的,同时,当时的开发人员考虑到多个线程对一个字符串的修改可能出现线程不安全的问题,于是让 StringBuffer 在拥有可修改字符串的功能的情况下,又给它加上了线程安全的机制。看到这里是不是觉得还挺好,挺正常的?但是要知道一个前提,那就是在 Java5 之前的 Java 在处理字符串的速度上一直被别人诟病,原因出在哪里?原因就在于这个 StringBuffer 上面。

被人诟病的 StringBuffer

StringBuffer 本来是为了实现大量修改字符串的功能而出现的,但却因为 Java 的开发人员给它加了个线程安全的功能,导致它执行效率极大地下降。这个线程安全的功能的实现并不是像我们现在用的方法,当时只是保证没有异常抛出,程序可以正常运行下去而已。在 Java 中,要实现字符串的相加,用加法运算符将两个字符串相加即可。但在这个过程中,Java5 之前是有 String 自动隐含地转换成 StringBuffer,再进行操作这一个步骤的(毕竟 String 类不可直接修改)。只要有这些步骤,就可以实现字符串的修改,但是呢,StringBuffer 有个线程安全的功能,它会在上面提到的步骤中还额外的执行一些功能,以保证线程的安全,而且,这里实现线程安全的方式和我们现在用锁的方式是不一样的!它这里的实现线程安全的方式极为繁琐且复杂,这就大大降低了 StringBuffer 的执行效率,以至于后来被广大程序员诟病。

StringBuilder 的出现

我们仔细地想一下,实际上也并没有多少地方需要在修改字符串的同时保证线程安全,就算有,我们给它加个锁就行。基于这种想法,在 StringBuffer 出现 10 年之后,Java 的开发人员回过头看这个问题,才发现 StringBuffer 的实现是多么的愚蠢,于是后来在 Java5 就有了 StringBuilder。StringBuilder 同样可以快速高效地修改字符串,同时不是线程安全的。虽然它不是线程安全的,但是它的执行效率却比 StringBuffer 要高上了不少。在 Java5 之后的版本中,字符串相加隐含的转化过程中,不再将 String 转化为 StringBuffer,而是转化成 StringBuilder。

历史故事讲完了,下面正式开始讲解它们各自的用法和特性。

String 类

String 类是 Java 用来存储字符串的内置类,在 String 初始化之后,其值就不可改变。

创建字符串

创建字符串的方式有两种,直接创建和使用 new 关键字来创建

直接创建

String str = "Java";

直接创建的 String 类的数据存储在公共的常量池中(Java 的常量优化机制),即直接创建的相同值的不同 String 类的引用相同。

String str1 = "Java";
String str2 = "J" + "a" + "v" + "a";
System.out.println(str1 == str2); // Output: true

但下面这种情况要注意一下,它和上面的不同:

String str1 = "Java";
String str2 = "Java";
String str3 = str2 + "";
System.out.println(str1 == str2); // Output: true
System.out.println(str2 == str3); // Output: false

为什么 str2 与 str3 不相等呢?实际上 str2 + "" 这一过程中,编译器会隐含地将 str2 转换成 StringBuilder(Java5 之前为 StringBuffer),然后再与 "" 相加,此过程会产生一个新的 StringBuilder 类,就是转换而来的那个。这个新产生的 StringBuilder 再转换为 String 类并赋值给变量 str3,因此,str3 的引用位置是 String 对象的堆上(与 new 关键字创建的 String 类相同),故与 str2 不相等。

new 关键字创建

String str = new String("Java");

通过 new 关键字创建的 String 和其他一般的类的创建一样,数据是存储在 String 类的对象的堆上,即通过 new 关键字创建的相同值的不同 String 类的引用不同。

String str1 = new String("Java");
String str2 = new String("Java");
System.out.println(str1 == str2); // Output: false
String 创建方式的区别

 通过 new 关键字创建的 String 类会调用 String 类的构造方法,String 类的构造方法有 11 种,除了通过字符串来创建 String 类之外,我们还可以用字符数组等方式来创建。

char[] s = {'J', 'a', 'v', 'a'};
String str = new String(s);

格式化字符串

在 Java 中,格式化字符串的方法有很多,比如有 C 语言风格的 printf,也有 Java 风格的 String.format。

System.out.printf("%f, %.2f, %s", Math.E, Math.PI, "Java");

虽然说是 C 语言风格,但还是略微有一点点不同,比如说,用 %d 来输出浮点数在 C 语言里面编译是不会报错的,尽管输出的结果不对,但是这在 Java 里面是会报错的。

用 String 类的 format 方法也能格式化字符串,它可以看作是强化了的格式化字符串工具。

它既可以像 printf 那样格式化字符串: 

System.out.println(String.format("%f, %.2f, %s", Math.E, Math.PI, "Java"));

也有它自己独特的方式:%[index]$[flag][type]

[index] 表示参数的索引,从 1 开始,因为 0 是格式化字符串;

[flag] 表示格式化的标识,有以下几种:

  • - :左对齐(默认是右对齐的),和长度限定一起使用;
  • + :正数前加正号;
  • 0 :不够长度用 0 来补齐;
  • # :对非十进制的数前加上标识;
  • , :对于十进制整数每隔三位加一个逗号分隔符;
  • ( :若为负数,则用括号将其括起来;
  • (空格字符):正数前空一格,负数没有变化;
  • m.nf :对于浮点数,输出长度为 m 位,保留小数点后 n 位;
  • e/E :以科学计数法表示浮点数;
  • g/G :根据情况智能选择以普通形式或者科学计数法输出浮点数;
  • a/A :输出带有效位数和指数的十六进制浮点数;
int i = 1234567890;
System.out.println(String.format("%,d", i));    // Output: 1,234,567,890
System.out.println(String.format("%16d", i));   // Output:       1234567890
System.out.println(String.format("%016d", i));  // Output: 0000001234567890
System.out.println(String.format("%-16d", i));  // Output: 1234567890
System.out.println(String.format("% d", i));    // Output:  1234567890
System.out.println(String.format("% d", -i));   // Output: -1234567890
System.out.println(String.format("%+d", i));    // Output: +1234567890
System.out.println(String.format("%(d", -i));   // Output: (1234567890)
System.out.println(String.format("%#x", i));    // Output: 0x499602d2
System.out.println(String.format("%x", i));     // Output: 499602d2
System.out.println(String.format("%o", i));     // Output: 11145401322
System.out.println(String.format("%b", i));     // Output: true

double d = 3.1415926;
System.out.println(String.format("%12.5f", d)); // Output:      3.14159
System.out.println(String.format("%g", d));     // Output: 3.14159
System.out.println(String.format("%e", d));     // Output: 3.141593e+00
System.out.println(String.format("%a", d));     // Output: 0x1.921fb4d12d84ap1

[type] 标识格式化的类型,有 d(整数)、f(浮点数)、s(字符串)、c(字符)、b(布尔值) 等。注意,这里的整数包括 int 类型和 long 类型,浮点数包括 float 类型和 double 类型。除了上面的基本类型外,还有几种特殊的:x(十六进制)、o(八进制)。

int i = 1;
long l = 1l;
float f = 1.f;
double d = 1.;
char c = 'a';
String str = "Java";
String format = String.format("%6$s %5$c %4$f %3$f %2$d %1$d", i, l, f, d, c, str);
System.out.println(format); // Output: Java a 1.000000 1.000000 1 1

关于 Java 中输出整数的二进制的方法:

System.out.println(Integer.toBinaryString(123)); // Output: 1111011

输出二进制不能用格式化输出了,但可以用 Integer 类的 toBinaryString 方法将其转化为二进制形式的字符串,然后再输出即可。

这里再补充一些 Java 和其他语言不同的地方(方便 C/C++ 和 Python 的人熟悉 Java):

  • C/C++ 的整数可以用单引号进行分隔,但是 Java 和 Python 不可以;
  • Java 可以通过逗号格式字符串来达到整数每隔三位分隔的效果,C/C++ 和 Python 不可以;
  • C/C++ 和 Python 可以将参数作为精度值并进行输出,而 Java 不可以;
  • C/C++、Java 和 Python 在十六进制、十进制、二进制上表示方法一样,但在八进制上 Python 与 C/C++ 和 Java 表示方法不同;

C/C++: 

int num = 1'234'567'890;

int num = 0xabc;    // 十六进制
int num = 123;      // 十进制
int num = 0123;     // 八进制
int num = 0b101;    // 二进制

double d = 3.14;
long long l = 112358;
printf("%lf, %lld", d, f);  // Output: 3.14, 112358

printf("%*.*f", 6, 3, 3.1415926);  // Output:  3.142

Java:

int num1 = 0xabc;   // 十六进制
int num2 = 123;     // 十进制
int num3 = 0123;    // 八进制
int num4 = 0b101;   // 二进制

int num = 1234567890;
System.out.println(String.format("%,d", num)); // Output: 1,234,567,890

Python:(下面的类型提示语法是为了让读者更容易理解)

num: int = 0xabc  # 十六进制
num: int = 123    # 十进制
num: int = 0o123  # 八进制
num: int = 0b101  # 二进制

print('%*.*f' % (6, 3, 3.1415926))  # Output:  3.142

String 类的各种方法

常用方法

方法名方法描述
int length()返回字符串的长度
char charAt(int index)返回字符串中索引为 index 处的字符
int indexOf(char c)返回第一个字符 c 的索引,若没有则返回 -1
String concat(String str)返回原字符串和 str 连接后的新字符串
String[] split(char c)返回以字符 c 进行分割得到的字符串数组
boolean equals(Object anObject)将字符串与 anObject 进行比较,并返回比较结果
boolean startsWith(String str)测试字符串是否以 str 开头,并返回测试结果
boolean endsWith(String str)测试字符串是否以 str 结尾,并返回测试结果
boolean contains(String str)判断字符串中是否包含 str,并返回判断结果
String substring(int start, int end)截取字符串,返回索引为 start 和索引为 end(不含)之间的字符串

String format(String fmt, Object... args)

返回格式化后的字符串

String 与 char

众所周知,String 的值是不可改变的,但是下面的代码又是为什么呢?

String str = "Java";
System.out.println(str); // Output: Java
str = "java";
System.out.println(str); // Output: java

上面的代码表面看上去 String 类型的变量 str 的值被改变,其实没有,因为 Java 中的变量都只是对象的引用。原来值为 "Java" 的 String 类型对象实际上还存在于内存中,str = "java"; 只不过是将变量 str 的引用改到了常量 "java" 上面去了。

String 类型不可变

实际上,String 就是一个 char 类型的数组,且 String 封装的这个 char 数组是用 final 关键字修饰的,String 本身也被 final 修饰,因此无法被改变。所以 String 类型的方法只能返回一个修改原 String 的、新的 String 类型的变量,而无法对原值进行修改。要产生一个新的 String 必然要开辟新的内存,这将花费不少的时间,因此只有在对 String 有少量修改的需求情况下,才使用 String 类,若要大量修改,那么还是需要 StringBuffer 类和 StringBuilder 类。

StringBuffer 与 StringBuilder 

StringBuffer 和 StringBuilder 与 String 最大的不同之处在于,它们可以大量且频繁地修改字符串的值而不产生新的字符串。StringBuffer 和 StringBuilder 最大的不同之处在于,StringBuffer 是线程安全的,而 StringBuilder 不是线程安全的,但 StringBuilder 相较于 StringBuffer 有速度优势,绝大多数情况下,推荐使用 StringBuilder。

继承结构

String 是直接继承自 CharSequence,而 StringBuffer 和 StringBuilder 继承自 AbstractStringBuilder,AbstractStringBuilder 又同时继承自 CharSequence 和 Appendable。

继承结构

基本用法

这里以 StringBuilder 为例,介绍它的基本用法,StringBuffer 的用法与之类似,不同的地方会指出来的。

下面是 StringBuilder 的常用方法:

方法名方法描述
append(String str)在字符串的后面添加字符串 str
insert(int start, String str)在索引为 start 的位置插入字符串 str
delete(int start, int end)将索引为 start 和索引为 end 之间的字符串删除
replace(int start, int end, String str)将索引为 start 和索引为 end 之间的字符串替换为字符串 str
reverse()反转字符串本身
StringBuilder sb = new StringBuilder(10);
sb.append("Java");        // sb: Java
sb.insert(1, "java");     // sb: Jjavaava
sb.delete(2, 3);          // sb: Jjvaava
sb.replace(4, 5, "JAVA"); // sb: JjvaJAVAva
sb.reverse();             // sb: avAVAJavjJ
StringBuilder 方法示意图

与 String 的区别

在调用 String 类的 concat 方法(字符串相加)时,实际上是将两个字符串相加并得到一个新的 String 类并返回,而并非对原 String 进行修改,而 StringBuffer 和 StringBuilder 是对自身的值直接进行修改的,速度和内存的消耗谁更多显而易见。

concat 方法与加法运算符

String.concat 方法和使用加号来连接字符串的结果都是得到连接后的字符串,但是这两者有什么区别吗?

String str1 = "Ja" + "va";
String str2 = "Ja".concat("va");
System.out.println(str1 == str2); // Output: false

无疑,str1 和 str2 的值是相等,但是两者的引用地址不同,从前面的知识我们可以知道,str1 的内存地址在公共的常量池中,上面的代码就说明 str2 的内存地址在 String 类的堆上。concat 方法实际上先复制了原来的字符串后再与新的字符串拼接,然后返回了一个新的字符串对象,所以地址不在公共常量池中,与 new 创建的字符串类似。

除 concat 和加法运算符之外,还有一种方式可以实现字符串的拼接,那就是 StringBuffer 类或 StringBuilder 类的 append 方法,实际上,加法运算符拼接字符串,转换成 StringBuffer 或 StringBuilder 后再进行字符串拼接的操作就是使用 append 方法进行拼接。也就是说,加法拼接字符串的底层实际是调用 append 方法。

适用场景

String、StringBuffer 和 StringBuilder 用处不同,各有各的适用场景。总结如下:

类名称是否可修改线程是否安全相对速度比较
String\缓慢
StringBuffer一般
StringBuilder快速

综上所述,当我们不需要对字符串做太多修改的时候,我们就选择 String 类,当我们需要对字符串进行大量且频繁的修改时,我们就选择 StringBuilder 类,除非遇到了需要线程安全的情况。不过,就算遇到了需要线程安全的情况,仍然推荐使用 StringBuilder,因为 StringBuffer 的线程安全,仅仅是保证 Jvm 不抛出异常,顺利地往下执行而已,它并不能保证逻辑正确和调用顺序正确。在大多数时候,我们需要的是锁。但也可以偷懒使用 StringBuffer。

从 Java5 之后,用加号来连接字符串的时候,都会隐含地调用 StringBuilder 类,因此,大部分情况下用加号连接字符串的操作已经没有太多的性能损失,但并非绝对的。如果在有循环的情况下,编译器可能无法完全做到智能地替换,这个时候我们还是自己手动使用 StringBuilder 类比较好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/577830.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python自动操作软件:提高工作效率和节省时间的利器

Python自动操作软件:提高工作效率和节省时间的利器 Python是一种高级编程语言,具有易读易用、快速开发、可移植性好、跨平台兼容等优点。它在自动化操作方面具有很大的优势,可以帮助用户实现各种自动化操作,从而为我们的工作提供…

有哪些信息安全/网络安全/渗透测试/众测/CTF/红蓝攻防/漏洞测试等前沿技术/研究/技巧获取渠道?

​前言 护网的定义是以国家组织组织事业单位、国企单位、名企单位等开展攻防两方的网络安全演习。进攻方一个月内采取不限方式对防守方展开进攻,不管任何手段只要攻破防守方的网络并且留下标记即成功,直接冲到防守方的办公大楼,然后物理攻破…

第2章 Class

Point结构体 //C语言写法 typedef struct point{float x;float y; }Point;Point a; a.x 1; a.y 2; //const表示p指向的对象里的值不能由p指针修改 void print(const Point* p){printf("%d %d\n", p -> x, p -> y); } print(&a);//想实现点的移动&#x…

深入解析OSI七层协议:实现网络通信的基石

目录 引言:详细介绍1. 物理层(Physical Layer)2. 数据链路层(Data Link Layer)3. 网络层(Network Layer)4. 传输层(Transport Layer)5. 会话层(Session Layer…

【章节1】git commit规范 + husky + lint-staged实现commit的时候格式化代码

创建项目我们不多说,可以选择默认的,也可以用你们现有的项目。 前言: git commit 的时候总有人填写一堆花里胡哨乱写的内容,甚至看了commit 的描述都不知道他这次提交到底做了个啥,那我们有没有办法规范大家的commit提…

chatgpt赋能python:Python中的绝对值函数:abs()

Python中的绝对值函数:abs() 在Python中,绝对值函数可以用来计算一个数的绝对值。这个函数名为abs(),它的语法为: abs(x)其中x为需要计算绝对值的数字。 abs()的用法 abs()函数可以计算传入参数的绝对值,并返回一个…

JavaScript实现使用js外链的方式输出一个5行6列的长方形的代码

以下为实现使用js外链的方式输出一个5行6列的长方形的程序代码和运行截图 目录 前言 一、使用js外链的方式输出一个5行6列的长方形(HTML部分) 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 二、使用js外链的方式输出一个5行6列的长方形&…

Solidity基础七

无论风暴将我带到什么岸边,我都将以主人的身份上岸 目录 一、Solidity的单位 1. 货币Ether 2. 时间单位Time 二、地址的形成 三、以太坊的账户 1.内部账户(简称CA) 2.外部账户(简称EOA) 3.内部账户和外部账户…

dom中的事件处理

事件参考 | MDN (mozilla.org) 什么是事件 事件监听方式 直接在html中编写JavaScript代码(了解) <button οnclick"console.log(按钮1发生了点击~);">按钮1</button> DOM属性&#xff0c;通过元素的on.....来监听事件 // 2.onclick属性// function h…

如何在华为OD机试中获得满分?Java实现【任务总执行时长】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

Visual Studio2022编译器实用调试技巧

目录 1.什么是bug 2.调试是什么&#xff1f; 3.debug和release的介绍 4.windows环境调试介绍 4.1 调试环境的准备 4.2 学会快捷键 4.3 调试的时候查看程序当前信息 4.4 查看内存信息 5.如果写出好&#xff08;易于调试&#xff09;的代码 7.编程常见的错误 1.什么是b…

android MutableLiveData与AndroidViewModel避坑小提示,Java

android MutableLiveData与AndroidViewModel避坑小提示&#xff0c;Java import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LifecycleOwner; import androidx.l…

Tomcat部署项目后,验证码不显示问题

在使用Tomcat服务器部署项目后&#xff0c;发现验证码不显示&#xff0c;在浏览器按f12查询后出现以下页面 查看源码发现一切正常 查阅相关资料后&#xff0c;得到以下方法&#xff1a; 1.在tomcat配置文件catalina.sh文件中找到-Djava.io.tmpdir"$CATALINA_TMPDIR" …

day38|动态规划-爬楼梯问题

DP问题类型&#xff1a; 动态规划比较重要的是找到前后两个状态之间的联系&#xff0c;在向后遍历的过程中注意遍历的顺序和初始化操作。 动归基础类问题 背包问题 打家劫舍 股票问题 子序列问题 DP问题的一些注意事项&#xff1a; 动态规划类的问题代码都是比较简洁的&…

数据结构之排序专题 —— 快速排序原理以及改进方法(添加随机,三路快排)

内容概述 尽管此类博客已经非常非常多&#xff0c;而且也有很多写得很好&#xff0c;但还是想记录一下&#xff0c;用最容易理解的方式&#xff0c;并且多补充了一些例子。 整理云盘的时候发现大一时候的笔记&#xff0c;用的是 txt 文本文件记录的&#xff0c;格式之丑陋可想…

SAP-MM-采购申请审批那些事!

1、ME55不能审批删除行项目的PR 采购申请审批可以设置行项目审批或抬头审批。如果设置为抬头审批时&#xff0c;ME55集中审批时&#xff0c;就会发现有些采购申请时不能审批的&#xff0c; 那么这些采购申请时真的不需要审批么&#xff1f;不是的&#xff0c;经过核对这些采购申…

solr快速上手:managed-schema标签详解(三)

0. 引言 core核心是solr中的重中之重&#xff0c;类似数据库中的表&#xff0c;在搜索引擎中也叫做索引&#xff0c;在solr中索引的建立&#xff0c;要先创建基础的数据结构&#xff0c;即schema的相关配置&#xff0c;今天继续来学习solr的核心知识&#xff1a; solr快速上手…

chatgpt赋能python:Python绑定CPU:提高性能的利器

Python 绑定 CPU&#xff1a;提高性能的利器 介绍 Python 作为一门通用编程语言&#xff0c;具有易学易用、开发效率高等优点&#xff0c;但由于其解释型的特性&#xff0c;执行效率相对较低&#xff0c;尤其是在处理大量计算时&#xff0c;性能瓶颈更为明显。在这种情况下&a…

chatgpt赋能python:用Python发送短信的简单方法

用Python发送短信的简单方法 在今天的数字时代&#xff0c;没有任何事情比即时通讯更方便。然而&#xff0c;短信仍然是一种极为有用的通信方式。 实际上&#xff0c;正如您所看到的&#xff0c;本文将告诉您如何使用Python在几步内轻松地发送短信。 发送短信的三种方法 要发…

Unity之TileMap

1、创建瓦片资源 教程中老师在Asset---Create---Tile创建&#xff0c;但是新版本Unity不能这样创建 新版本是在Asset---Create---2D--Tile里面选择&#xff0c;跟老师的不太一样&#xff0c;暂时也不懂怎么解决 所以我们可以用方法二创建&#xff1a; 在Window---2D---Tile…