Java中的String、StringBuffer和StringBuilder
作为作为一个已经入了门的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了。
- 如果是常量字符串,用String。
- 多线程环境下经常变动的字符串用StringBuffer。
- 单线程经常变动的字符串用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的更多相关文章
- Android/Java 中的 String, StringBuffer, StringBuilder的区别和使用
Android 中的 String, StringBuffer 和 StringBuilder 是移动手机开发中经常使用到的字符串类.做为基础知识是必须要理解的,这里做一些总结. A.区别 可以从以下 ...
- 重温java中的String,StringBuffer,StringBuilder类
不论什么一个系统在开发的过程中, 相信都不会缺少对字符串的处理. 在 java 语言中, 用来处理字符串的的类经常使用的有 3 个: String.StringBuffer.StringBuilder ...
- 在JAVA中,String,Stringbuffer,StringBuilder 的区别
首先是,String,StringBuffer的区别 两者的主要却别有两方面,第一是线程安全方面,第二是效率方面 线程安全方面: String 不是线程安全的,这意味着在不同线程共享一个String ...
- Java中的String,StringBuilder,StringBuffer三者的区别
最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...
- Java中的String,StringBuilder,StringBuffer三者的区别(转载)
最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...
- 转:Java中的String,StringBuilder,StringBuffer三者的区别
最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...
- [转载]Java中的String,StringBuilder,StringBuffer三者的区别
最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...
- Java中的String,StringBuilder,StringBuffer的区别
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > ...
- 【转】Java中的String,StringBuilder,StringBuffer三者的区别
https://www.cnblogs.com/su-feng/p/6659064.html 最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及String ...
- Java中的String,StringBuilder,StringBuffer三者的区别(转发:https://www.cnblogs.com/su-feng/p/6659064.html)
最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...
随机推荐
- inode block 软硬链接
inode block 软硬链接 1 inode 1.1 inode(索引节点)作用 (1)用于存储文件数据属性信息(2)用于存储数据指针信息 1.2 如何产生 格式化时,创建文件系统 1.3 如何查 ...
- Python Web实战:Python+Django+MySQL实现基于Web版的增删改查
前言 本篇使用Python Web框架Django连接和操作MySQL数据库学生信息管理系统(SMS),主要包含对学生信息增删改查功能,旨在快速入门Python Web,少走弯路.效果演示在项目实战最 ...
- Vue + Element-ui实现后台管理系统(3)---面包屑 + Tag标签切换功能
面包屑 + Tag标签切换功能 有关后台管理系统之前写过两遍博客,看这篇之前最好先看下这两篇博客.另外这里只展示关键部分代码,项目代码放在github上: mall-manage-system 1.V ...
- 【Spark】使用java语言开发spark程序
目录 步骤 一.创建maven工程,导入jar包 二.开发代码 步骤 一.创建maven工程,导入jar包 <properties> <scala.version>2.11.8 ...
- Day_10【常用API】扩展案例2_获取输入日期是哪一年的哪一天的星期几
分析以下需求,并用代码实现 1)已知日期字符串:"2015-10-20",将改日期字符串转换为日期对象 2)将(1)中的日期对象转换为日历类的对象 3)根据日历对象获取改日期是星期 ...
- STM32 串口USART DMA方式发送接收数据
硬件:stm32f103cbt6 软件:STM32F10x_StdPeriph_Lib_V3.5.0 文章目录 头文件 USART3_DR的地址 DMA的通道 DMA的中断 USART接收回调函数 头 ...
- 自动化测试工具-Selenium IDE 教程一
引言:这里介绍的是谷歌浏览种的插件,安装教程这里不再描述,网上有很多, 使用教程不是特别多,所以特地花时间整理此篇内容: 一:打开插件,欢迎界面 启动IDE后,将显示一个欢迎对话框. 如果这是您第一次 ...
- RocketMQ 入门
一.rocketMQ是什么 rocketmq是一款低延迟.高可靠.可伸缩.已使用的消息中间件.具有以下特性: 1.支持发布/订阅.点对点(p2p)消息模型 2.同一个队列中支持先进先出(FIFO)和严 ...
- .Net Core3.0 WebApi 项目框架搭建 一:实现简单的Resful Api
.Net Core3.0 WebApi 项目框架搭建:目录 开发环境 Visual Studio 2019.net core 3.1 创建项目 新建.net core web项目,如果没有安装.net ...
- GraphQL-- 使用Apollo Server搭建Node服务端
一.关于Apollo Server Apollo Server是一种使用JS创建GraphQL服务端的一个方案.它的兼容性比较好,可以很好地和GraphQL客户端进行兼容.同时它可以 独立作为服务端进 ...