String类详解

news2024/12/23 14:29:58

在Java编程中,除Object类外,最常用的类就是String类了。本文将从String类源码出发,对String类进行一个全面的分析,以帮忙我们更好的理解和使用String类。

String类概述

Java 使用 String 类代表字符串。Java 中的所有字符串字面值(如 “abc” )都使用此类实现。字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。

String的不可变性(immutable)

在Java中,String是不可变的,这主要体现在三个方面:(1)String类使用final关键字修饰,表示其不可继承;(2)String类使用字节数组存储数据,且使用final关键字修饰,表示该字段创建后引用地址不可变。另外该字符数组的访问权限为 private,表示外部无法访问,且 String 没有对外提供可以修改该属性的方法。关键源码如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;

    // ...
}

注意,字符串的底层实现使用字节数组存储。使用字节数字而非字节数组的好处是,字节数组与数据的底层存储保持一致,无需额外转换。使用字节数组,保证使用指定的编码、解码方式,屏蔽了底层设备的差异。对于网络传输场景,无需进行额外的转换(网络数据传输使用字节流)。

String不可变性的好处

将String设计成不可变,主要有以下方面的考虑:(1) 出于性能方面的考虑。可以实现多个变量引用堆内存中的同一个字符串实例,避免创建的开销。因为String太过常用,JAVA类库的设计者在实现时做了个小小的变化,即采用了享元模式,每当生成一个新内容的字符串时,他们都被添加到一个共享池中,当第二次再次生成同样内容的字符串实例时,就共享此对象,而不是创建一个新对象,注意,这种方式仅适用于"="操作符创建的String对象。(2) 出于安全方面的考虑。由于String的不可变性,可以确保创建后不会被篡改(当然,这也不是绝对,使用反射仍可修改对象的值)。(3) 防止内存泄露。HashMap 的 key 为String类型,如果String对象可变,则会造成该key无法手动删除,从而造成内存泄露。(4)出于并发安全的考虑。由于String的不可变性,多个线程可以安全的共享String对象,则不用担心被修改。

字符串的创建

使用字符串时,遇到的第一个问题就是字符串的创建。根据是否使用运算符创建,可以将其分为两类:(1) 直接赋值创建字符串;(2)使用构造方法创建字符串。

直接赋值创建字符串

直接赋值创建字符串就是使用运算符直接赋值,可以使用的运算符有等号和加号。示例代码如下:

String strWithEqualOperator = "foo";
String strWithAddOperator = strWithEqualOperator + "test";

直接赋值创建字符串时,会优先从字符串常量池中获取已存在的字符串,如果不存在,则会将新生成的字符串添加到常量池,方便下次使用。字符串常量池是享元模式的具体应用,后面会进一步介绍。
注意,这里使用"+"运算符的语法,在Java编译阶段,会将变量替换成真实的字符串并完成拼接。

使用构造方法创建字符串

除了直接赋值创建字符串外,还可以使用构造方法创建字符串。String类支持多种场景的创建。如字节数组、字符数组、StringBuilder实例等,这里不再一一列举。关键源码如下(为避免方法过长,影响阅读,仅展示方法声明,具体实现可以参考源码):

    // 创建空字符串
    public String();

    // 基于字符串对象创建字符串对象
    @HotSpotIntrinsicCandidate
    public String(String original);

    // 基于字符数组创建字符串对象
    public String(char value[]);
    
    // 基于字符数组指定长度创建字符串对象
    public String(char value[], int offset, int count);

    // 基于整数数组指定长度创建字符串对象
    public String(int[] codePoints, int offset, int count);

    // 基于字节数组指定长度创建字符串对象
    public String(byte bytes[], int offset, int length, String charsetName)
            throws UnsupportedEncodingException;

    // 基于字节数组指定长度创建字符串对象
    public String(byte bytes[], int offset, int length, Charset charset);

    // 基于字节数组创建字符串对象
    public String(byte bytes[], String charsetName)
            throws UnsupportedEncodingException;

    // 基于字节数组创建字符串对象
    public String(byte bytes[], Charset charset);

    // 基于字节数组指定长度创建字符串对象
    public String(byte bytes[], int offset, int length);

    // 基于字节数组创建字符串对象
    public String(byte[] bytes);

    // 基于StringBuffer实例创建字符串对象
    public String(StringBuffer buffer);

    // 基于StringBuilder实例创建字符串对象
    public String(StringBuilder builder);

需要说明的是,使用构造方法创建字符串对象时,不会复用字符串常量池。如果两个字符串对象分别使用相同的字符串字面量由构造函数创建,且使用等号运算进行比较,因为是两个不同的对象,所以尽管其值相同,但不相等。示例如下:

String str1 = new String("foo");
String str2 = new String("foo");
// 打印false
System.out.println(str1 == str2);

所以,在进行字符串比较时,尽量使用equals方法,而不要使用相等运算符。

字符串常量池与享元模式

为了提高对象的复用率,减少重复对象的内存占用,Java语言引入了字符串常量池。针对使用赋值运算符创建的字符串对象,Java会优先从字符串常量池中尝试获取该对象,如果对象存在,则直接复用。如果对象不存在,则新生成一个字符串并将其放到常量池中。熟悉缓存使用的同学可能会发现,字符串常量池的使用与缓存的使用一致,这里缓存的对象是字符串对象。从设计模式角度来说,字符串常量池是享元设计模式的具体应用。
所以,在以后的字符串创建操作中,为了提高内存的复用率,尽量使用赋值运算符创建对象

字符串的比较

针对字符串,最常用的功能就是字符串的相等比较。针对字符串的相等比较有两种选择:使用==运算符和使用equals方法。对于Java语言来说,==运算符,对于“值类型”和“空类型”是比较他们的值;对于“引用类型”是是比较对象在内存中的存放地址,即是否指向指向同一个对象。对Object类的equals方法来说,其功能与 == 运算符一致,但是String类重写了该方法,使其可以进行值相等比较。关键源码如下:


public class Object {
    //...

    public boolean equals(Object obj) {
        return (this == obj);
    }
}

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // ...

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            // 优先比较hashCode是否相等
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                    : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }
}

所以,在对字符串进行相等比较时,为了减少字符串底层存储的优化带来的可能影响,尽量使用equals方法。更多equals方法和==运算符使用细节上的差异,可以参考笔者之前的文章。

除了相等比较,String类还实现了Comparable接口,支持自定义比较。关键代码如下:

public int compareTo(String anotherString) {
    byte v1[] = value;
    byte v2[] = anotherString.value;
    if (coder() == anotherString.coder()) {
        return isLatin1() ? StringLatin1.compareTo(v1, v2)
                            : StringUTF16.compareTo(v1, v2);
    }
    return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
                        : StringUTF16.compareToLatin1(v1, v2);
    }

其他常用方法

除了构造方法、比较方法,String类还提供了不少方便的功能,如:字符串分割、字符串转换、字符串格式化。这里不再进一步介绍,有兴趣的同学可以阅读相关源代码。
请添加图片描述

总结

String类是Java语言中使用频率极高的类,针对String类的源码分析有利于更好的理解和使用String类。String对象的最大特点是其不可变性。String的不可变性,使其在性能、不可篡改、并发安全等方面展现优越性。在使用String类时,要善于利用该特性。如HashMap使用String类型作为key。在查询key时,优先对其hash-code进行比较。在创建字符串时,为了提高内存的复用率,尽量使用赋值运算符创建对象。这种方式会优先从字符串常量池中获取重复对象,减少不必要的内存。不同的创建方式(直接赋值创建字符串、使用构造方法创建字符串)会带来字符串比较上的不同。为减少字符串底层存储的策略差异带来的影响,推荐使用equals方法来进行字符串的相等比较。为了更好的使用String类,还应熟悉其提供的常用方法,如:字符串比较、字符串分割、字符串转换、字符串格式化等。

参考

https://www.cnblogs.com/zhangyinhua/p/7689974.html Java常用类(二)String类详解
https://zhuanlan.zhihu.com/p/94228628 String 的不可变性
https://www.alpharithms.com/byte-array-sequences-of-8-bit-groups-183908/ 字节数组

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

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

相关文章

已解决MemoryError

已解决Python读取20GB超大文件报错&#xff1a;MemoryError 文章目录报错问题报错翻译报错原因解决方法1解决方法2&#xff08;推荐使用&#xff09;帮忙解决报错问题 日常数据分析工作中&#xff0c;难免碰到数据量特别大的情况&#xff0c;动不动就2、3千万行&#xff0c;…

Linux——网络配置篇

1、前情提要&#xff1a; 今晚在配置Linux &#xff08;CentOS7完整版&#xff09;的时候 明明已经配好了网络环境&#xff0c;重启虚拟机后&#xff0c;又出现了Ping不通 主机、Ping不通网关&#xff0c;外网的情况 &#xff08;NAT&#xff09;。 让我很费解的一个情况是&am…

数据结构与算法基础(王卓)(8):线性表的应用

PPT&#xff1a;第二章P173&#xff1b; 并集集合&#xff1a;线性表的合并&#xff08;无需有序&#xff0c;不能重复&#xff09; 线性表&#xff1a; Status Union(Sqlist& A, Sqlist& B)//并集 {int len_A A.length;int len_B B.length;for (int i 1; i < …

SpringCloud学习(1)

SpringCloud学习 软件架构演进之路 对于单体架构&#xff0c;我们根据设计期和开发实现期的不同模式和划分结构&#xff0c;可以分为&#xff1a; 简单单体模式&#xff1a; 代码层面没有拆分&#xff0c;所有的业务逻辑都在一个项目&#xff08;Project&#xff09;里打包…

​力扣解法汇总2293. 极大极小游戏

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a; 力扣 描述&#xff1a; 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 n…

伪随机码序列

伪随机码又称伪随机序列&#xff0c;是用确定性方法产生的在一段周期内具有类似白噪声的随机特性的二&#xff08;或多&#xff09;进制数据序列。 伪随机序列在码分复用、码分多址和扩频通信中都有重要应用。常用的伪随机序列有m序列、M序列和Gold序列。 作为地址码和扩频码…

pycharm中commit/push撤销+分支合并

一、现在想撤销到打印3怎么操作&#xff1f; 1复制修订号 2鼠标右键 3去项目下执行git bash here&#xff0c;再输入git push -f 4此时&#xff0c;github中的push就已经回退成功&#xff1b;再进行一次新代码的commit和push&#xff0c;master分支的颜色就变成黄色了&#x…

Windows中安装 MySQL8.0.30 数据库

下载安装文件 访问MySQL官网下载安装文件https://downloads.mysql.com/archives/community/。 如下图所示&#xff0c;点击页面中的“DOWNLOAD”按钮。 然后&#xff0c;会出现如下所示页面&#xff0c;点击页面底部的“No thanks, just start my download”&#xff0c;就可…

Vue3商店后台管理系统设计文稿篇(四)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第四篇&#xff0c;主要记录使用git与vscode将代码同步提交到GitHub上面 文章目录一、Git与Github建立连接二、配置开发工具三、实用Git命令仓库相关命令分支相关命令正文内容&#xff1a; 一、Git与Github建立连接 使…

从0到1【建站:AWS+Ubuntu+Python+Django+uwsgi+nginx+ssl】

目录一、创建服务器1、进入AWS官网2、启动实例3、创建新密钥对4、选择密钥对5、网络设置6、配置存储7、启动实例8、查看实例9、配置安全组二、连接服务器1、在AWS官网进行连接2、使用Xshell7进行连接3、设置允许root登录三、域名解析1、进行腾讯云官网2、管理域名解析3、绑定公…

xilinx ZYNQ 7000 XADC 片上模拟转数字模块

上图所示&#xff0c;XADC 属于 PL部分的资源 XADC是一种硬逻辑实现&#xff0c;位于PL功率域。PS- xadc接口是PS的一部分&#xff0c;可以被PS APU访问&#xff0c;而不需要对PL进行编程。PL必须上电才能配置PS-XADC接口、使用PL- jtag或DRP接口以及操作XADC。 上面的机构图能…

通关算法题之 ⌈回溯算法⌋

回溯算法 子集组合排列 78. 子集 给你一个整数数组 nums&#xff0c;数组中的元素互不相同 &#xff0c;返回该数组所有可能的子集&#xff08;幂集&#xff09;。解集不能包含重复的子集&#xff0c;你可以按任意顺序 返回解集。 输入&#xff1a;nums [1,2,3] 输出&…

LeetCode 5. 最长回文子串

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 5. 最长回文子串&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 LeetCode 5…

1590_AURIX_TC275_PMU_Flash的操作

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这里提到的这个复位读取操作&#xff0c;复位的对象其实是当前的操作&#xff0c;也就是命令序列。主要是命令序列的最后一个命令还没有被接收到&#xff0c;都可以被这个命令中断。在复位…

目标检测:特征金字塔网络(Feature Pyramid Network)

目标检测&#xff1a;特征金字塔网络&#xff08;Feature Pyramid Network&#xff09;概述核心思想概述 由于在目标检测任务中&#xff0c;对与大目标的检测&#xff0c;需要feature map每个点的感受野大一点&#xff08;高层语义特征&#xff09;&#xff0c;对于小目标&…

GPIO 八种工作模式及其硬件框图

参考资料: STM32F1xx 官方资料:《STM32中文参考手册V10》- 第 8 章通用和复用功能 IO(GPIO 和 AFIO) GPIO 是通用输入/输出端口的简称,是 STM32 可控制的引脚。GPIO 的引脚与外部硬件设备连接,可实现与外部通讯、控制外部硬件或者采集外部硬件数据的功能。 STM32 的八种 …

图像融合笔记(一):RFN-Nest

RFN-NestAbstractIntroductionRelated worksThe proposed fusion frameworkThe architecture of the fusion networkTwo-stage training strategy论文&#xff1a; RFN-Nest: An end-to-end residual fusion network for infrared and visible images代码&#xff1a; https://…

[Android Input系统]MotionEvent的序列化传送

这里从云游戏的触控操作看起&#xff0c;PC端的客户端支持按键和鼠标滑动操作&#xff0c;手机上的云游戏客户端则是和手机游戏一样的touch触控&#xff0c;客户端的touch操作是怎样处理给服务端的呢&#xff0c;猜测是把touch操作“实时”的传送给了服务器&#xff0c;Android…

COM,Component Object Model 简介

COM&#xff0c;Component Object Model 简介 1. COM 是什么 COM 的英文全称是&#xff0c;Component Object Model&#xff0c;中文译为&#xff0c;组件对象模型。它官方的概念是&#xff1a; The Microsoft Component Object Model (COM) is a platform-independent, dis…

一个简单、功能完整的开源WMS​仓库管理系统

今天给大家推荐一个简单、功能完整的仓库管理系统。 项目简介 这是基于.NetCore 7、Vue 3开发的开源项目&#xff0c;支持中英文&#xff0c;界面简单清爽&#xff0c;采用前后端分离架构。 该项目主要是针对小型物流仓储供应链流程&#xff0c;采用的技术也都比较新&#x…