Java String源码剖析+面试题整理
由于字符串操作是计算机程序中最常见的操作之一,在面试中也是经常出现。本文从基本用法出发逐步深入剖析String的结构和性质,并结合面试题来帮助理解。
String基本用法
在Java中String的创建可以直接像基本类型一样定义,也可以new一个
String s1 = "Hello World";
String s2 = new String("Hello World");
String可以通过+实现合并
String s = "Hello";
s += "World";
String包装方法
为了方便操作,String包装了一堆操作,大多可以看名字直接使用
public boolean isEmpty()//判断字符串是否为空
public int length() //获取字符串长度
public String substring(int beginIndex) //取子字符串
public String substring(int beginIndex, int endIndex) //取子字符串
public int indexOf(int ch)//查找字符,返回第一个找到的索引位置,没找到返回-1
public int indexOf(String str)//查找子串,返回第一个找到的索引位置,没找到返回-1
public int lastIndexOf(int ch)//从后面查找字符
public int lastIndexOf(String str)//从后面查找子字符串
public boolean contains(CharSequence s)//判断字符串中是否包含指定的字符序列
public boolean startsWith(String prefix) //判断字符串是否以给定子字符串开头
public boolean endsWith(String suffix) //判断字符串是否以给定子字符串结尾
public boolean equals(Object anObject) //与其他字符串比较,看内容是否相同
public boolean equalsIgnoreCase(String anotherString)//忽略大小写比较是否相同
public int compareTo(String anotherString) //比较字符串大小
public int compareToIgnoreCase(String str) //忽略大小写比较
public String toUpperCase()//所有字符转换为大写字符,返回新字符串,原字符串不变
public String toLowerCase()//所有字符转换为小写字符,返回新字符串,原字符串不变
public String concat(String str) //字符串连接,返回当前字符串和参数字符串合并结果
public String replace(char oldChar, char newChar) //字符串替换,替换单个字符
public String replace(CharSequence target, CharSequence replacement)//字符串替换,替换字符序列,返回新字符串,原字符串不变
public String trim()//删掉开头和结尾的空格,返回新字符串,原字符串不变
public String[] split(String regex)//分隔字符串,返回分隔后的子字符串数组
String内部结构
String类内部用一个字符数组表示字符串,实例变量定义为:
private final char value[];
String有两个使用字符数组的构造方法,会根据参数新创建一个数组,并复制内容,而不会直接用参数中的字符数组。
public String(char value[])
public String(char value[], int offset, int count)
可以看出String底层是由字符数组实现,并结合构造方法实现常用方法:
length()方法返回的是这个数组的长度
indexOf()方法查找字符或子字符串时是在这个数组中进行查找
substring()方法是根据参数,调用构造方法String(char value[], int offset, int count)新建了一
个字符串
String不可变性
与包装类类似,String类也是不可变类,即对象一旦创建,就没有办法修改了。根据上面的源码,String类也声明为了final,不能被继承,内部char数组value也是final的,保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
String类中提供了很多看似修改的方法,其实是通过创建新的String对象来实现的,原来的String对象不会被修改。比如,concat()方法的代码,通过Arrays.copyOf方法创建了一块新的字符数组,复制原内容,然后通过new创建了一个新的String。
public String concat(String str) {int otherLen = str.length();if(otherLen == 0) {return this;}int len = value.length;char buf[] = Arrays.copyOf(value, len + otherLen);str.getChars(buf, len);return new String(buf, true);
}
与包装类类似,定义为不可变类,程序可以更为简单、安全、容易理解。但如果频繁修改字符串,而每次修改都新建一个字符串,那么性能太低,这时,应该考虑Java中的另两个类StringBuilder和StringBuffer。
字符串常量
Java中的字符串常量是非常特殊的,除了可以直接赋值给String变量外,它自己就像一个String类型的对象,可以直接调用String的各种方法。
System.out.println("a".length());
实际上,这些常量就是String类型的对象,在内存中,它们被放在一个共享的地方,这个地方称为字符串常量池,它保存所有的常量字符串,每个常量只会保存一份,被所有使用者共享。当通过常量的形式使用一个字符串的时候,使用的就是常量池中的那个对应的String类型的对象。
所以下面的代码会输出true,因为是同一个对象。
String namel = "a";
String name2 = "a";
System.out.println(name1==name2);
需要注意的是,如果不是通过常量直接赋值,而是通过new创建,==就不会返回true了
String namel = new String("a");
String name2 = new String("a");
System.out.println(namel==name2);
这是因为String类中以String为参数的构造方法代码如下,hash是String类中另一个实例变量,表示缓存的hashCode值。
public String(String original) {this.value = original.value;this.hash = original.hash;
}
hash变量缓存了hashCode方法的值,也就是说,第一次调用hashCode方法的时候,会把结果保存在hash这个变量中,以后再调用就直接返回保存的值。
public int hashCode() {int h = hash;if(h == 0 && value.length > 0) {char val[] = value;for(int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h; }return h;
}
如果缓存的hash不为0,就直接返回了,否则根据字符数组中的内容计算hash,计算方法是:
s[0]*31^(n-1)+ s[1]*31^(n-2)+ ...+ s[n-1]。使用这个式子,可以让hash值与每个字符的值有关,也与每个字符的位置有关,位置i(i>=1)的因素通过31的(n-i)次方表示。使用31大致是因为两个原因:一方面可以产生更分散的散列,即不同字符串hash值也一般不同;另一方面计算效率比较高,31*h与32*h-h即(h<<5)-h等价,可以用更高效率的移位和减法操作代替乘法操作。
从下图可以看出,通过new创建name1和name2指向两个不同的String对象,只是这两个对象内部的value值指向相同的char数组。所以name1!=name2,但是name1.equals(name2)的值是true。

String的八股考点
string为什么要设计成不可变类
在Java中将 String设计成不可变的是综合考虑到各种因素的结果。主要的原因主要有以下三点:
(1)字符串常量池的需要:字符串常量池是Java堆内存中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;
(2)允许 String对象缓存 HashCode: Java中String对象的哈希码被频繁地使用,比如在HashMap等容器中。字符串不变性保证了 hash码的唯一性,因此可以放心地进行缓存。这也是一种性能优化手段,意味着不必每次都去计算新的哈希码;
(3)String被许多的 Java类(库)用来当做参数,例如:网络连接地址URL、文件路径path、还有反射机制所需要的 String参数等,假若 String不是固定不变的,将会引起各种安全隐患。
String s1 = new String("abc");这行代码创建了几个对象
如果字符串常量池中不存在字符串对象“abc”的引用,那么它会在堆上创建两个字符串对象,其中一个字符串对象的引用会被保存在字符串常量池中。
如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。
String a = new String("aa") + "bb";这行代码创建了多少个对象
共创建了3-4个String对象,假设常量池中没有对象“aa”,第一个是常量池中的对象“aa”,如果常量池中没有就创建并添加进常量池中。第二个是new出来的以“aa”为初始值创建的 String对象,第三个“bb”同“aa”,第四个是通过“+”拼接前两个对象,创建出的新String对象。
String的equals() 和 Object的equals() 有何区别
String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址。
String对象最多可以存放多少个字符
String在源码中使用 char[]来维护字符序列的,而char[]的长度是 int类型,所以理论上 String的长度最大为2^31-1,占用空间大约为4GB,不过根据实际JVM的堆内存限制,编译时,String长度最多可以是2的16次方减2,运行时长度最多可以是2的31次方减1,意思是可以在编译时定义一些短的字符串,运行时可以进行拼接,长一点也可以。
string常量池放在哪
Java 8以前被放在永久代中,Java 8及以后被放在方法区的元数据 metadata中。
String str = "i" 和 String str = new String("i")有什么区别
不一样,因为内存的分配方式不一样。String str =“i”的方式,Java虚拟机会将其分配到常量池中;而String str = new String(“i")则会被分到堆内存中。
public class StringTest {public static void main(String[] args) {String strl = "abc";String str2 = "abc";String str3 = new String("abc");String str4 = new String("abc");System.out.println(str1 == str2); // trueSystem.out.println(str1 == str3); // falseSystem.out.println(str3 = str4); // falseSystem.out.println(str3.equals(str4)); // true
在执行String str1=“abc”的时候,JVM会首先检查字符串常量池中是否已经存在该字符串对象,如果已经存在,那么就不会再创建了,直接返回该字符串在字符串常量池中的内存地址;如果该字符串还不存在字符串常量池中,那么就会在字符串常量池中创建该字符串对象,然后再返回。所以在执行Stringstr2=“abc”的时候,因为字符串常量池中已经存在“abc”字符串对象了,就不会在字符串常量池中再次创建了,所以栈内存中str1和str2的内存地址都是指向“abc”在字符串常量池中的位置,所以str1=str2 的运行结果为 true。
而在执行 String str3 = new String(“abc")的时候,JVM会首先检查字符串常量池中是否已经存在“abc”字符串,如果已经存在,则不会在字符串常量池中再创建了;如果不存在,则就会在字符串常量池中创建“abc”字符串对象,然后再到堆内存中再创建一份字符串对象,把字符串常量池中的“abc”字符串内容拷贝到内存中的字符串对象中,然后返回堆内存中该字符串的内存地址,即栈内存中存储的地址是堆内存中对象的内存地址。String str4 = newString(“abc”)是在堆内存中又创建了一个对象,所以 str3 == str4运行的结果是 false。
String修改的实现原理
当用String类型来对字符串进行修改时,其实现方法是首先创建一个StringBuilder,其次调用
StringBuilder的 append()方法,最后调用StringBuilder 的 toString()方法把结果返回。
相关文章:
Java String源码剖析+面试题整理
由于字符串操作是计算机程序中最常见的操作之一,在面试中也是经常出现。本文从基本用法出发逐步深入剖析String的结构和性质,并结合面试题来帮助理解。 String基本用法 在Java中String的创建可以直接像基本类型一样定义,也可以new一个 Str…...
探索未来:集成存储器计算(IMC)与深度神经网络(DNN)的机遇与挑战
开篇部分:人工智能、深度神经网络与内存计算的交汇 在当今数字化时代,人工智能(AI)已经成为科技领域的一股强大力量,而深度神经网络(DNN)则是AI的核心引擎之一。DNN是一种模仿人类神经系统运作…...
[C/C++] -- CMake使用
CMake(Cross-platform Make)是一个开源的跨平台构建工具,用于自动生成用于不同操作系统和编译器的构建脚本。它可以简化项目的构建过程,使得开发人员能够更方便地管理代码、依赖项和构建设置。 CMake 使用一个名为 CMakeLists.tx…...
笔记本选购配置参数详解
笔记本电脑的选购是一个技术活,涉及到众多的配置参数。本文将为您详细解析笔记本电脑的主要配置参数,帮助您在选购时做出明智的决策。 1. 处理器(CPU) 处理器是笔记本电脑的核心组件,负责执行计算任务…...
临睡之际的生死思索与生命哲学的启示
在人类生存体验中,有一种独特而深邃的感受——当人们准备进入梦乡时,会担忧第二天醒来是否还能感知到生命的律动。这种“入睡即未知”的心理状态,既是生命无常的深刻体现,也是对个体生命价值、生活态度及人生哲学的一种深度拷问。…...
QT学习(五)C++函数重载
一、 函数重载 在同一个作用域内,可以声明几个功能类似的同名函数, 这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来 重载函数。 下面的实例中,同名函数 print() 被用…...
微服务OAuth 2.1扩展额外信息到JWT并解析(Spring Security 6)
文章目录 一、简介二、重写UserDetailsService三、Controller解析JWT获取用户信息四、后记 一、简介 VersionJava17SpringCloud2023.0.0SpringBoot3.2.1Spring Authorization Server1.2.1Spring Security6.2.1mysql8.2.0 Spring Authorization Server 使用JWT时,前…...
Python@setter用法介绍
Pythonsetter是Python编程语言中的一个关键属性,它简化了Python开发者的编程过程,提高了编程效率。 一、Pythonsetter是什么 Pythonsetter是Python语言中的一个属性,它允许程序员设置Python中的类成员变量。在Python中,属性&…...
格子表单GRID-FORM | 文档网站搭建(VitePress)与部署(Github Pages)
格子表单/GRID-FORM已在Github 开源,如能帮到您麻烦给个星🤝 GRID-FORM 系列文章 基于 VUE3 可视化低代码表单设计器嵌套表单与自定义脚本交互文档网站搭建(VitePress)与部署(Github Pages) 效果预览 格…...
mac无法往硬盘里存东西 Mac硬盘读不出来怎么办 Mac硬盘格式 硬盘检测工具
mac有时候会出现一些问题,比如无法往硬盘里存东西,或者无法往硬盘上拷贝文件。这些问题会给用户带来很大的困扰,影响正常的工作和学习。那么,mac无法往硬盘里存东西,mac无法往硬盘上拷贝怎么办呢?软妹子将为…...
DataX源码分析 reader
系列文章目录 一、DataX详解和架构介绍 二、DataX源码分析 JobContainer 三、DataX源码分析 TaskGroupContainer 四、DataX源码分析 TaskExecutor 五、DataX源码分析 reader 六、DataX源码分析 writer 七、DataX源码分析 Channel 文章目录 系列文章目录前言Reader组件如何处理…...
openssl3.2 - exp - RAND_bytes_ex
文章目录 openssl3.2 - exp - RAND_bytes_ex概述笔记END openssl3.2 - exp - RAND_bytes_ex 概述 生成随机数时, 要检查返回值是否成功, 不能认为一定是成功的(官方文档上有说明). 生成随机数的API, 和库上下文有关系, 使用RAND_bytes_ex()比RAND_bytes()好些. 笔记 /*! * …...
Oracle中怎么设置时区和系统时间
在Oracle数据库中,设置时区和系统时间可以通过多种方法实现。下面是一些常见的方法: 1. 设置数据库的时区 Oracle数据库允许你为每个会话或整个数据库设置时区。 a. 为整个数据库设置时区 你可以使用ALTER DATABASE语句为整个数据库设置时区。例如&a…...
常见的物联网操作系统介绍
物联网(Internet of Things,IoT)是指将各种物理设备、车辆、家用电器、工业设备等通过网络连接起来,实现数据交换和通信的技术。物联网操作系统是管理这些设备并使其能够相互通信的软件平台。以下是一些常见的物联网操作系统&…...
二级C语言笔试10
(总分101,考试时间90分钟) 一、选择题 1. 设有如下关系表: A) TR∩S B) TR∪S C) TRS D) TR/S 2. 在一棵二叉树中,叶子结点共有30个,度为1的结点共有40个,则该二叉树中的总结点数共有( )个。 A) 89 …...
【WebSocket】微信小程序原生组件使用SocketTask 调用星火认知大模型
直接上代码 微信开发者工具-调试器-终端-新建终端 进行依赖安装 npm install base-64 npm install crypto-js 然后顶部工具栏依次点击 工具-构建npm // index.js const defaultAvatarUrl https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQ…...
[1-docker-01]centos环境安装docker
官方参考文档 可以在官方docker桌面版本指导文档里找到适合自己的电脑平台进行参考,或者你是老司机的话直接自己上车。 如果不需要桌面版,也可以在官方docker engine版本指导文档里找到适合自己的平台进行参考,同样,老司机可以自…...
深度学习基础之《深度学习介绍》
一、深度学习与机器学习的区别 1、特征提取方面 机器学习:人工特征提取 分类算法 深度学习:没有人工特征提取,直接将特征值传进去 (1)机器学习的特征工程步骤是要靠手工完成的,而且需要大量领域专业知识…...
4核8g服务器能支持多少人访问?2024新版测评
腾讯云轻量4核8G12M轻量应用服务器支持多少人同时在线?通用型-4核8G-180G-2000G,2000GB月流量,系统盘为180GB SSD盘,12M公网带宽,下载速度峰值为1536KB/s,即1.5M/秒,假设网站内页平均大小为60KB…...
Linux中pipe管道操作
管道的读写操作: 读操作: 有数据:read正常读,返回读出的字节数无数据:1 写段全部关闭:read解除阻塞,返回0,相当于文件读到了尾部 2 写段没有全部关闭…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...
