57从零开始学Java之一文详解String字符串的底层实现原理

news2024/11/17 9:34:17

作者:孙玉昌,昵称【一一哥】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

在之前的两篇文章中,壹哥给大家介绍了String字符串及其常用的API方法、常用编码、正则表达式等内容,但这些内容都是停留在”如何用“的阶段,没有涉及到”为什么“的层面。实际上,我们在求职时,面试官很喜欢问我们关于String的一些原理性知识,比如String的不可变性、字符串的内存分配等。为了让大家更好地应对面试,并理解String的底层设计,接下来壹哥会给大家聊聊String的一些原理,比如String为什么具有不可变性?

---------------------------------------------前戏已做完,精彩即开始-------------------------------------------

全文大约【4000】字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github:

https://github.com/SunLtd/LearnJava

Gitee:

一一哥/从零开始学Java

一. String源码中的final关键词

为了弄清楚String为什么具有不可变性,我们先来看看String的源码,尤其是源码中带有final关键词的地方。

1. final的特点

为了更好地理解String相关的内容,在阅读String源码之前,我们先来复习一下final关键词有哪些特点,因为在String中会涉及到很多final相关的内容。

  1. final关键词修饰的类不可以被其他类继承,但是该类本身可以继承其他类,通俗的说就是这个类可以有父类,但是不能有子类;
  2. final关键词修饰的方法不可以被覆盖重写,但是可以被继承使用;
  3. final关键词修饰的基本数据类型变量称为常量,只能被赋值一次;
  4. final关键词修饰的引用数据类型的变量值为地址值,地址值不能改变,但是地址内的数据对象可以被改变;
  5. final关键词修饰的成员变量,需要在创建对象前赋值,否则会报错(即需要在定义时直接赋值,如果是在构造方法中赋值,则多个构造方法均需赋值)。

复习了final的特点之后,接下来我们就可以阅读String的源码了。

2. String源码解读

接下来就请大家请跟着壹哥来看看String源码中关于不可变性的内容吧。

2.1 final修饰的String类

/**
 * ......其他略......
 *
 * Strings are constant; their values cannot be changed after they
 * are created. String buffers support mutable strings.
 * Because String objects are immutable they can be shared. For example:
 * 
 * ......其他略......
 *
 */
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    ......

壹哥先对上面的源码及其注释进行简单的解释:

  • final:请参考第1小节对final特点的介绍;
  • Serializable:用于序列化;
  • Comparable<String>:默认的比较器;
  • CharSequence: 提供对字符序列进行统一、只读的操作。

从这一段源码及注释中,我们可以得出如下结论:

  • String类用final关键字修饰,说明String不可被继承;
  • String字符串是常量,字符串的值一旦被创建,就不能被改变;
  • String字符串缓冲区支持可变字符串;
  • 因为String对象是不可变的,所以它们是可以被共享的。

2.2 final修饰的value[]属性

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    
    ......

从源码中可以看出,value[]是一个私有的字符数组,String类其实就是通过这个char数组来保存字符串内容的。简单的说,我们定义的字符串会被拆成一个一个的字符,这些字符都被存放在这个value字符数组里面。

这里的value[]数组被final修饰,初始化之后就不能再被更改。但是大家注意,我们这里说的value[]不可变,指的是value的引用地址不可变,但是value数组里面的数据元素其实是可变的!这是因为value是数组类型,根据我们之前学过的知识,value的引用地址会分配在栈中 ,而其对应的数据是在常量池中保存的。所以我们说String不可变,指的就是value在栈中的引用地址不可变,而不是说常量池中数组本身的数据元素不可变。

另外我们要注意,Java中的字符串常量池,用来存储字符串字面量! 但是由于JDK版本的不同,常量池的位置也不同:

JDK 6 及以下版本的字符串常量池是在方法区(Perm Gen)中,此时常量池中存储的是字符串对象;在 JDK 8.0 中,方法区(永久代被元空间取代了;

JDK 7、8以后的字符串常量池被转移到了堆中,此时常量池存储的就是字符串对象的引用,而不是字符串对象本身。

至此,壹哥 就带各位把String类中的核心源码分析完了,接下来我们再进一步分析String不可变的原因,及其他底层原理设计。

二. String的不可变性

1. 实验案例

了解了上面的这些核心源码之后,接下来 壹哥 再带各位来验证一下,看看String到底能不能变!我先给各位来一段案例代码,代码案例如下图所示。

结果s的内容变了,好像是啪啪打脸了???!!!咋回事,壹哥 不是说了String不可变吗?怎么这么快就翻车打脸了?别急,让我们好好来分析一下。

2. 结果剖析

首先我们从结果上来看String s 变量的结果好像改变了,但为什么我们又说String是不可变的呢?

要想明白这个问题,我们得先弄清楚一个点,即引用和值的区别!在上面的代码中,我们先是创建了一个 "yiyige" 为内容的字符串引用s,s其实先是指向了value对象,而value对象则指向存储了 "y,i,y,i,g,e" 字符的字符数组。因为value被final修饰,所以value的值不可被更改。因此,上面代码中改变的其实是s的引用指向而不是改变了String对象的值换句话说,上面实例中 s的值 只是 value的引用地址,并不是String内容本身!当我们执行 s = "yyg"; 语句时,Java中会创建一个新的字面量对象 "yyg",而原来的 "yiyige" 字面量对象依然存在于内存的intern缓存池中。在Java中,因为数组也是对象, 所以value中存储的也只是一个引用,它指向一个真正的数组对象。在执行了String s = “yiyige”; 这句代码之后,真正的内存布局应该是下图这样的:

因为value是String封装的字符数组,value中的所有字符都属于String这个对象。由于value是private的,且没有提供setValue等公共方法来修改这个value值,所以在String类的外部是无法修改value值的,也就是说一旦初始化就不能被修改。此外,value变量是final的, 也就是说在String类内部,一旦这个值初始化了,value这个变量所引用的地址就不会改变了,即一直引用同一个对象。正是基于这一层,所以说String对象是不可变的对象。但其实value所引用对象的内容完全可以发生改变,我们可以利用 反射来消除String类对象的不可变特性

所以String的不可变性,指的是value在栈中的引用地址不可变,而不是说常量池中array本身的数据元素不可变!

而String对象的改变实际上是通过内存地址的“断开-连接”变化来完成的,这个过程中原字符串中的内容并没有任何的改变。String s = "yiyige"; 和 s = "yyg"; 实质上是开辟了2个内存空间,s 只是由原来指向 "yiyige" 变为指向 "yyg" 而已,而其原来的字符串内容,是没有改变的,如下图所示。

因此,我们在以后的开发中,如果要经常修改字符串的内容,请尽量少用String,因为字符串的指向“断开-连接”会大大降低性能,建议使用:StringBuilder、StringBuffer。

那么String一定不可变吗?有没有办法让String真的可变呢?我们继续往下学习!

三. String真的不可变吗?

1. 实验案例

我在前面的章节中给大家说,String的不可变,其实指的是String类中value属性在栈中的引用地址不可变,而不是说常量池中array本身的数据元素不可变!也就是说String字符串的内容其实是可变的!那怎么实现呢?利用反射就可以实现,我们通过一个案例来证明一下。

try {
    String str = "yyg";
    System.out.println("str=" + str + ", 唯一性hash值=" + System.identityHashCode(str));

    Class stringClass = str.getClass();
    //获取String类中的value属性
    Field field = stringClass.getDeclaredField("value");
    //设置私有成员的可访问性,进行暴力反射
    field.setAccessible(true);
    //获取value数组中的内容
    char[] value = (char[]) field.get(str);
    System.out.println("value=" + Arrays.toString(value));

    value[1] = 'z';
    System.out.println("str=" + str + ", 唯一性hash值=" + System.identityHashCode(str));
} catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}

2. 结果剖析

上面案例的执行结果如下图所示:

我们可以看到,String字符串的字符数组可以通过反射进行修改,导致字符串的“内容”真的发生了变化!并且我们又利用底层的java.lang.System#identityHashCode()方法(不管是否重写了hashCode方法)获取了对象的唯一哈希值,该方法获取的hash值与hashCode()方法是一样的。我们可以看到两个字符串的唯一性hash值是一样的,证明字符串引用地址没有发生改变!所以在这里,我们并不是像之前那样创建了一个新的String字符串,而是真的改变了String的内容。这个代码案例进一步说明,String类的不可变指的是中value属性在栈中的引用地址不可变,而不是说常量池中array本身的数据元素不可变!也就是说String字符串的内容其实是可变的!

四. 结语

String作为Java中使用最为广泛的一个类,之所以设计为不可变,主要是出于效率与安全性方面考虑。这种设计有优点,也有缺点。

1. 不可变性的优点

  1. 只有当字符串是不可变的,字符串池才有可能实现字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串引用都可以指向池中的同一个字符串。但如果字符串是可变的,如果一个引用变量改变了字符串的值,那么其它指向这个值的变量内容也会跟着一起改变。
  2. 如果字符串是可变的,那么可能会引起很严重的安全问题譬如,数据库的用户名、密码都是以字符串的形式传入数据库,以获得数据库的连接;或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象值,造成安全漏洞。
  3. 因为字符串是不可变的在物理上是绝对的线程安全,所以同一个字符串实例可以被多个线程共享由于不可变对象不可能被修改,因此能够在多线程中被任意自由访问而不导致线程安全问题,不需要多余的同步操作。即在并发场景下,多个线程同时读一个资源,并不会引发竞态条件,只有对资源进行读写才有危险。不可变对象不能被写,所以线程安全。
  4. 类加载器要用到字符串,不可变性也提供了安全性,以便正确的类可以被加载譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
  5. 因为字符串是不可变的,所以在字符串对象创建的时候hashCode()就被执行并把执行结果缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,所以字符串的处理速度要快过其它的键对象,这就是HashMap中的键往往都使用字符串的原因,当我们需要频繁读取访问任意键值对时,能够节省很多的CPU计算开销。
  6. Sting的不可变性会提高执行性能和效率,基于Sting不可变,我们就可以用缓存池将String对象缓存起来,同时把一个String对象的地址赋值给多个String引用,这样可以安全保证多个变量共享同一个对象。因此,构造一万个string s = "xyz",实际上得到都是同一个字符串对象,避免了很多不必要的空间开销。

2. 不可变性的缺点

  • 丧失了部分灵活性。我们平时使用的大部分都是可变对象,比如内容变化时,只需要利用setValue()更新一下就可以了,不需要重新创建一个对象,但是String很难做到这一点。当然,我们完全可以使用StringBuilder来弥补这个缺点。
  • 脆弱的不可变性,String其实可以利用JNI或反射来改变其不可变性。

另外,关于String源码的解读,及不可变性的相关面试题,壹哥在自己的高薪面试题专栏中有过非常详细地讲解,如果你想了解更多,请参考如下链接:

《高薪程序员&面试题精讲系列07之说说String为什么不可变及String底层原理?》

五.今日作业

理解String不可变的原理及优缺点,并通过代码来验证String的不可变性。

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

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

相关文章

rfc7234之http缓存

缓存概念 缓存处理请求步骤 缓存如果查询到某个请求已经有缓存&#xff0c;那么需要进一步检查该资源的新鲜度&#xff0c;根据新鲜度和请求中的字段综合评估是否要去服务端拉取新鲜的资源。 注意&#xff1a; 创建响应时候要注意版本匹配&#xff0c;如果服务器响应和客户端…

计算机竞赛 垃圾邮件(短信)分类算法实现 机器学习 深度学习

文章目录 0 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 垃圾邮件(短信)分类算…

docker 03(docker 容器的数据卷)

一、数据卷的概念和作用 删除后&#xff0c;数据也没了。 不能 数据卷 是宿主机中的一个目录或文件当容器目录和数据卷目录绑定后&#xff0c;对方的修改会立即同步一个数据卷可以被多个容器同时挂载 作用&#xff1a; 容器数据持久化 外部机器和容器间接通信 容器之间数据交换…

数据结构(2)

冒泡排序&#xff1a; 1.比较相邻的两个元素。如果前一个元素比后一个元素大&#xff0c;则交换两者位置。 2.对每一对相邻元素做相同工作&#xff0c;从第一对元素到最后一对元素&#xff0c;最后的一个元素就是最大的元素。 for(int ia.length-1;i>0;i--){for (int j 0…

Canvas绘制毛玻璃背景分享海报

最近重新设计了分享海报&#xff0c;用毛玻璃作为背景&#xff0c;使整体更有质感&#xff0c;如果没有用到canvas&#xff0c;毛玻璃效果其实很好实现&#xff0c;给元素添加一个滤镜即可&#xff08;比如&#xff1a;filter: blur(32px)&#xff09;&#xff0c;但是实践的过…

HTTPS代理搭建技巧分享

今天我们来分享一下如何搭建一个能够实现中间人 检测和防护的HTTPS代理。保护我们的网络通信安全是至关重要的&#xff0c;让我们一起学习如何构建一个安全可靠的HTTPS代理吧&#xff01; 什么是中间人 &#xff1f; 首先&#xff0c;让我们来了解一下什么是中间人 。中间人 是…

html表格中加入斜线,使用css给table表格表头单元格添加斜线

背景&#xff1a;业务给了90张word电子表格&#xff0c;需要用html设计出来。 如图所示&#xff0c;红色区域的下斜线如何实现&#xff1f; 先说结论&#xff1a;html中table没有直接的斜线表头标签&#xff0c;但结合css、svg之类的可以实现。 #lineTd{ background:#FFFFFF u…

Leetcode67 二进制求和

给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 代码 class Solution {public String addBinary(String a, String b) {StringBuilder res new StringBuilder();int carry 0;int i a.length() - 1, j b.length() - 1;while(i > 0 || j &…

『吴秋霖赠书活动 | 第一期』《强化学习:原理与Python实战》

文章目录 一、什么是RLHF&#xff1f;二、RLHF适用于哪些任务&#xff1f;三、RLHF和其他构建奖励模型的方法相比有何优劣&#xff1f;四、什么样的人类反馈才是好的反馈五、RLHF算法有哪些类别&#xff0c;各有什么优缺点&#xff1f;七、如何降低人类反馈带来的负面影响&…

2021电赛国一智能送药小车(F题)设计报告

2021电赛国一智能送药小车&#xff08;F题&#xff09;设计报告 【写在前面的话】 电赛是一个很奇妙的过程&#xff0c;可能有些人觉得电赛的门槛太高&#xff0c;那便意味着&#xff0c;当你决定要参加电赛的那一刻起&#xff0c;这一段路、这些日子就注定不会太轻松&#xf…

WPS中的表格错乱少行

用Office word编辑的文档里面包含表格是正常的&#xff0c;但用WPS打开里面的表格就是错乱的&#xff0c;比如表格位置不对&#xff0c;或者是表格的前几行无法显示、丢失了。 有一种可能的原因是&#xff1a; 表格属性里面的文字环绕选成了“环绕”而非“无”&#xff0c;改…

周易卦爻解读笔记——既济

第六十三卦既济 水火既济 坎上离下 既济卦由泰卦所变&#xff0c;泰卦六五与九二换位&#xff0c;象征已经完成。 地天泰 序卦传【有过物者必济&#xff0c;故受之以既济】 既&#xff0c;已经。《谷梁传》云&#xff1a;“既者&#xff0c;尽也。有继之辞也。”济者&#…

DNDC模型建模方法及在土壤碳储量、温室气体排放、农田减排、土地变化、气候变化中的应用

第一讲 DNDC模型介绍 ①碳循环模型简介 ②DNDC模型原理 ③DNDC下载与安装 ④DNDC注意事项 第二讲 DNDC初步操作 ①DNDC界面介绍 ②DNDC数据及格式 ③DNDC点尺度模拟 ④DNDC区域尺度模拟 ⑤DNDC结果查看 第三讲 遥感和GIS基础 ①DNDC中的遥感和GIS技术 ②ArcGIS软件界面 ③坐…

【开发笔记】ubuntu部署指定版本的前后端运行环境(npm nodejs mysql)

目录 1 背景2 环境要求3 部署流程3.1 npm的安装3.2 nodejs的安装3.3 MySQL的安装 4 可能的问题 1 背景 在远程服务器上的Ubuntu系统中&#xff0c;部署指定版本的前后端项目的运行环境 2 环境要求 npm 9.5.1Nodejs v18.16.1MySQL 8.0.33 3 部署流程 3.1 npm的安装 通过安…

docker导出、导入镜像、提交

导出镜像到本地&#xff0c;然后可以通过压缩包的方式传输。 导出&#xff1a;docker image save 镜像名:版本号 > /home/quxiao/javatest.tgz 导入&#xff1a;docker image load -i /home/quxiao/javatest.tgz 删除镜像就得先删除容器&#xff0c;当你每运行一次镜像&…

【三】关系模型 -- 基本概念

基本概念关系模型概述关系模型的提出关系模型研究什么关系模型的三要素 什么是关系概念引入1. 域2. 笛卡尔积3. 关系 关系模式 VS 关系关系的特性1. 列是同质2. R(A:D) 中&#xff0c;A 不可相同&#xff0c;D 可相同3. 行、列位置互换性4. 属性不可再分&#xff08;关系第一范…

用yolov4-tiny检测在电力输电线20种鸟类,灵活运用训练trick,实验较为完备,数据处理丰富度值得参考

Detection of bird species related to transmission line faults based on lightweight convolutional neural network Abstract 输电线路高效防鸟害是电网运行维护面临的长期挑战。本文提出了一种将轻量级卷积神经网络(CNN)、图像处理和目标检测相结合的方法来检测与输电线路…

基于GPT-4和LangChain构建云端定制化PDF知识库AI聊天机器人

参考&#xff1a; GitHub - mayooear/gpt4-pdf-chatbot-langchain: GPT4 & LangChain Chatbot for large PDF docs 1.摘要&#xff1a; 使用新的GPT-4 api为多个大型PDF文件构建chatGPT聊天机器人。 使用的技术栈包括LangChain, Pinecone, Typescript, Openai和Next.js…

Day12-2-面向对象编程

Day12-面向对象编程 一 回顾 变量,数组,对象都是容器,都可以用来存储数据 let n = 10 let arr = [3,5,7] let stu = {name:"张恒",age:18,sex:"女"}二 面向对象思想 面向过程:将开发的步骤按照顺序一步一步往下执行,直到程序结束 面向对象:将项目中…

第2步---MySQL卸载和图形化工具展示

第2步---MySQL卸载和图形化工具展示 1.MySQL的卸载 2.MySQL的图形化工具 2.1常见的图形化工具 SQLyog&#xff1a;简单。SQLyog首页、文档和下载 - MySQL 客户端工具 - OSCHINA - 中文开源技术交流社区 Mysql Workbench &#xff1a;MySQL :: MySQL Workbench DataGrip&…