深入了解Java中的StringBuilder与StringBuffer

深入了解Java中的StringBuilder与StringBuffer
在这里插入图片描述

  1. StringBuffer和StringBuilder的区别
    因为字符串不可变,当字符串拼接(尤其是使用+号操作符)时,需要考量性能的问题,不多毫无顾忌的创建太多String对象,从而对内存造成不必要压力。

因此Java专门设计StringBuilder类来解决该问题

public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {public StringBuffer() {super(16);}public synchronized StringBuffer append(String str) {super.append(str);return this;}public synchronized String toString() {return new String(value, 0, count);}//...方法
}

从上面代码我们可以发现StringBuffer在进行字符串操作时,方法都添加上synchronized关键字进行同步,这主要是考虑到多线程环境下安全问题。因为加了synchronized,所以在非多线程下,执行效率就会比较低,这是添加了没必要的锁

考虑到性能问题,Java又给StringBuffer添加了一个孪生兄弟StringBuilder在方法上没有添加synchronized关键字,因此无论单线程还是多线程效率都会高

public final class StringBuilder extends AbstractStringBuilderimplements java.io.Serializable, CharSequence
{// ...public StringBuilder append(String str) {super.append(str);return this;}public String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);}// ...其他方法
}

但是因为方法上没有synchronized关键字,所以StringBuilder多线程情况不安全 ,如果要在多线程环境下修改字符串,你到时候可以使用 ThreadLocal 来避免多线程冲突

public class ThreadSafeStringBuilder {// 使用ThreadLocal为每个线程提供独立的StringBuilder对象private static final ThreadLocal<StringBuilder> threadLocalStringBuilder = ThreadLocal.withInitial(StringBuilder::new);public static void appendString(String str) {// 获取当前线程的StringBuilder对象StringBuilder stringBuilder = threadLocalStringBuilder.get();// 在StringBuilder对象上执行字符串拼接操作stringBuilder.append(str);}public static String getString() {// 获取当前线程的StringBuilder对象StringBuilder stringBuilder = threadLocalStringBuilder.get();// 返回StringBuilder对象的字符串表示return stringBuilder.toString();}public static void main(String[] args) {// 创建多个线程并发执行字符串拼接操作Runnable task = () -> {for (int i = 0; i < 10; i++) {appendString(Thread.currentThread().getName() + "-" + i + " ");}// 输出当前线程的字符串结果System.out.println(Thread.currentThread().getName() + ": " + getString());// 清空当前线程的StringBuilder对象,以便下次使用threadLocalStringBuilder.get().setLength(0);};// 启动多个线程Thread[] threads = new Thread[5];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(task);threads[i].start();}// 等待所有线程执行完成for (Thread thread : threads) {try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}}}
}

注意:实际开发中,StringBuilder 的使用频率也是远高于 StringBuffer,甚至可以说,StringBuilder 完全取代了 StringBuffer

  1. StringBuilder使用
    在深入解析 String.intern() 说过

编译器遇到 + 号这个操作符的时候,会将 new String(“spring”) + new String(“葵花宝典”) 编译代码如下:

new StringBuilder().append(“spring”).append(“葵花宝典”).toString();
虽然过程我们看不见,这正是 Java 的只能之处,Java可以在编译的时帮我们做很多优化,这样既可以提高我们的开发效率(+ 号写起来比创建 StringBuilder 对象便捷得多),也不会影响 JVM 的执行效率。

如果我们使用 javap 反编译 new String(“spring”) + new String(“葵花宝典”) 的字节码的时候,也是能看出 StringBuilder 的影子。

     0: new           #2                  // class java/lang/StringBuilder3: dup4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V7: new           #4                  // class java/lang/String10: dup11: ldc           #5                  // String spring13: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;19: new           #4                  // class java/lang/String22: dup23: ldc           #8                  // String 葵花宝典25: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;31: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;34: astore_135: return

可以发现Java 编译器将字符串拼接操作(+)转换为了 StringBuilder 对象的 append 方法,然后再调用 StringBuilder 对象的 toString 方法返回拼接后的字符串

  1. StringBuilder的内部实现
    3.1. StringBuilder的toString()方法
public String toString() {return new String(value, 0, count);
}

value 是一个 char 类型的数组

/*** The value is used for character storage.*/
char[] value;

StringBuilder创建对象是,会给value分配内存空间(初始容量16),来存储字符串

public StringBuilder() {super(16);
}

随着字符串不断拼接,value数组长度会自动进行扩容操作,将字符数组长度增加到足够容纳新字符串的大小。value动态扩容的过程类似于ArrayList中的扩容机制,确保了在拼接大量字符串时的高效性

3.2. StringBuilder的append(String str) 方法

public StringBuilder append(String str) {super.append(str);return this;
}

StringBuilder类的append(String str) 方法实际调用AbstractStringBuilder类中append方法。该方法会检查当前字符序列中的字符是否够用,如果不够用则会进行扩容,并将指定字符串追加到字符序列的末尾

public AbstractStringBuilder append(String str) {if (str == null) {return appendNull();}int len = str.length();ensureCapacityInternal(count + len);putStringAt(count, str);count += len;return this;
}

AbstractStringBuilder类的append(String str) 方法将指定的字符串追加到当前字符序列中。如果指定字符串为 null,则追加字符串 “null”;否则,该方法会检查指定字符串的长度,根据当前字符序列中已有字符的数量以及指定字符串的长度来判断是否需要扩容。如果需要扩容,则会分配一个新的字符数组,将原有字符序列的内容复制到新的字符数组中,并将指定字符串的内容追加到新字符数组的末尾。这样就确保了在追加字符串时,字符序列的容量始终能够满足当前字符数量的需求,避免了不必要的内存浪费说明:扩容调用方法ensureCapacityInternal(int minimumCapacity)方法,扩容之后,将指定字符串的字符拷贝到字符序列中

3.3. AbstractStringBuilder的ensureCapacityInternal(int minimumCapacity)方法

private void ensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeint oldCapacity = value.length >> coder;if (minimumCapacity - oldCapacity > 0) {value = Arrays.copyOf(value,newCapacity(minimumCapacity) << coder);}
}private int newCapacity(int minCapacity) {// overflow-conscious codeint oldCapacity = value.length >> coder;int newCapacity = (oldCapacity << 1) + 2;if (newCapacity - minCapacity < 0) {newCapacity = minCapacity;}int SAFE_BOUND = MAX_ARRAY_SIZE >> coder;return (newCapacity <= 0 || SAFE_BOUND - newCapacity < 0)? hugeCapacity(minCapacity): newCapacity;
}

ensureCapacityInternal(int minimumCapacity) 方法用于确保当前字符序列的容量至少等于指定的最小容量 minimumCapacity。如果当前容量小于指定的容量,就会为字符序列分配一个新的内部数组。新容量的计算方式如下:

如果指定的最小容量大于当前容量,则新容量为两倍的旧容量加上 2。为什么要加 2 呢?这是因为在某些情况下,仅仅将容量加倍可能仍然不足以容纳更多的字符。例如,对于非常小的字符串(比如空的或只有一个字符的 StringBuilder),仅仅将容量加倍可能仍然不足以容纳更多的字符。因此,加上 2 提供了一个最小的增长量,确保即使对于很小的初始容量,扩容后也能至少添加一些字符而不需要立即再次扩容。
如果指定的最小容量小于等于当前容量,则不会进行扩容,直接返回当前对象。这样做是为了避免不必要的内存浪费和性能开销。
3.4 StringBuilder的 reverse 方法

public StringBuilder reverse() {super.reverse();return this;
}

StringBuilder类的reverse() 方法实际调用AbstractStringBuilder类中reverse()方法。该方法会检查当前字符序列中的字符是否够用,如果不够用则会进行扩容,并将指定字符串追加到字符序列的末尾

public AbstractStringBuilder reverse() {byte[] val = this.value;int count = this.count;int coder = this.coder;int n = count - 1; // 字符序列的最后一个字符的索引if (COMPACT_STRINGS && coder == LATIN1) {for (int j = (n-1) >> 1; j >= 0; j--) {int k = n - j; // 计算相对于 j 对称的字符的索引byte cj = val[j];  // 获取当前位置的字符val[j] = val[k]; // 交换字符val[k] = cj; // 交换字符}} else {StringUTF16.reverse(val, count);}return this;  // 返回反转后的字符串构建器对象}

1.初始化:

n表示字符串中最后一个字符索引

2.字符串反转:
方法通过一个 for 循环遍历字符串的前半部分和后半部分,这是一个非常巧妙的点,比从头到尾遍历省了一半的时间。(n-1) >> 1 是 (n-1) / 2 的位运算表示,也就是字符串的前半部分的最后一个字符的索引。
在每次迭代中,计算出与当前索引 j 对称的索引 k,并交换这两个索引位置的字符。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最长异或路径 ---- (字典树求异或最大)

目录 最长异或路径&#xff1a; 题目大意&#xff1a; 思路解析&#xff1a; 代码实现&#xff1a; 最长异或路径&#xff1a; 题目大意&#xff1a; 思路解析&#xff1a; 现在假设有一棵这样的树&#xff0c;我们并不关心每条边的路径权值为多少&#xff0c;假设划红线的…

blast原理与使用技巧,最全最详细

BLAST 序列比对 在生物信息学领域&#xff0c;序列比对是一项基础而关键的任务。它帮助研究人员识别基因、理解蛋白质功能&#xff0c;并揭示物种之间的进化关系。 本文旨在介绍BLAST&#xff08;Basic Local Alignment Search Tool&#xff09;的原理及其不同变体&#xff0c;…

3.7Code

基于顺序存储结构的图书信息表的排序 #include<iostream> #include<sstream> #include<stdlib.h> #include<string.h>#define MAXSIZE 1000 //顺序表所能容纳的最大长度 #define OK 0 typedef int status;using namespace std;typedef struct{char n…

评测本地部署的语音识别模型

1 引言 最近&#xff0c;朋友给我发来了一段音频&#xff0c;想转录成文字&#xff0c;并使用大型润色文本。音频中的普通话带有一定的口音&#xff0c;并且讲解内容较为专业&#xff0c;所以一般的语音识别工具很难达到较高的识别率。 于是试用了两个大模型。Whisper 是目前…

Python之Web开发中级教程----搭建SSH环境

Python之Web开发中级教程----搭建SSH环境 SSH 的全称是 “安全的 Shell(Secure Shell)”&#xff0c;它功能强大、效率高&#xff0c;这个主流的网络协议用于在两个远程终端之间建立连接。让我们不要忘记它名称的“安全”部分&#xff0c;SSH 会加密所有的通信流量&#xff0c…

详解float函数类型转换

函数描述 float([x]) 函数将数字或数字的字符串表示形式转换为与它等效的有符号浮点数。如果参数x是一个字符串&#xff08;十进制表示的数字串&#xff09;&#xff0c;数字前面可以添加符号来表示正数&#xff0c;或负数。符号和数字之间不能出现空格&#xff0c;但是符号前…

spring-jpa

一、介绍 1.1ORM 1.2 Java Persistence API 放在javaee版本 优点 支持持久化复杂的Java对象&#xff0c;简化Java应用的对象持久化开发支持使用JPQL语言进行复杂的数据查询使用简单&#xff0c;支持使用注解定义对象关系表之间的映射规范标准化&#xff0c;由Java官 方统一规…

两天学会微服务网关Gateway-Gateway简介

锋哥原创的微服务网关Gateway视频教程&#xff1a; Gateway微服务网关视频教程&#xff08;无废话版&#xff09;_哔哩哔哩_bilibiliGateway微服务网关视频教程&#xff08;无废话版&#xff09;共计17条视频&#xff0c;包括&#xff1a;1_Gateway简介、2_Gateway工作原理、3…

Vue2+3

vue相关介绍 Vue的两种使用方式&#xff1a; 1、vue核心包开发 场景&#xff1a;局部模块改造 2、vue核心包&vue插件工程化开发 场景&#xff1a;整站开发 概念&#xff1a;vue是用于构建用户界面的渐进式框架 创建vue实例 创建Vue实例&#xff0c;初始化渲染步骤&am…

DAWG库下载出现的问题

今天配置一些环境出现了报错需要下载DAWG-0.8.0-cp38-cp38-win_amd64&#xff0c; PyPi官网https://pypi.org/project/DAWG/#files 只找到了mac版本&#xff0c;没发现windows版本 于是找到了非官方网址&#xff1a; https://www.lfd.uci.edu/~gohlke/pythonlibs/#dawg 下载DA…

字节后端实习 一面凉经

心脏和字节永远都在跳动 深圳还有没有大厂招后端日常实习生啊&#xff0c;求捞&#xff5e;&#xff08;boss小公司也不理我&#xff09; 很纠结要不要干脆直接面暑期实习&#xff0c;又怕因为没有后端实习经历&#xff0c;面不到大厂实习。死锁了

专访|云安全攻防:从理论到应用的全面探索

2023年11月&#xff0c;美国核研究实验室&#xff08;INL&#xff09;遭遇数据泄露。同年10月&#xff0c;索尼的员工数据在MOVEit攻击事件中被泄露。2024年2月&#xff0c;某知名制造商因云存储服务器的配置错误导致了敏感数据泄露。 这些事件表示企业必须重视云安全建设&…