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); // true
        System.out.println(str1 == str3); // false
        System.out.println(str3 = str4); // false
        System.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()方法把结果返回。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pandas深度解析GroupBy函数的妙用技巧【第75篇—GroupBy函数】

Pandas深度解析GroupBy函数的妙用技巧 数据处理和分析中&#xff0c;Pandas是一款非常强大的Python库&#xff0c;提供了丰富的数据结构和功能&#xff0c;使得数据分析变得更加简便高效。其中&#xff0c;GroupBy函数是Pandas中一个重要且常用的功能&#xff0c;通过它我们可…

【2024.02.11】定时执行专家 V6.9 龙年春节版 - 下载地址更新日志

目录 ◆ 最新版下载链接 ◆ 软件更新日志 – TimingExecutor Full Change Log ▼2024-02-11 V6.9 ▼2023-06-16 V6.8.2 ▼2023-02-27 V6.7 ▼ 2023-01-23 V6.6 ▼ 2023-01-20 V6.5 ▼ 2022-12-25 V6.4 ▼ 2022-11-15 V6.3 ▼ 2022-10-01 V6.2 ▼ 2022-07-…

wsl 安装minikube

Minikube是一种轻量化的Kubernetes集群&#xff0c;专为开发者和学习者设计&#xff0c;以便他们能够更好地学习和体验Kubernetes的功能。它利用个人PC的虚拟化环境&#xff0c;实现了Kubernetes的快速构建和启动。目前&#xff0c;Minikube已经支持在macOS、Linux和Windows平台…

Python中使用opencv-python进行人脸检测

Python中使用opencv-python进行人脸检测 之前写过一篇VC中使用OpenCV进行人脸检测的博客。以数字图像处理中经常使用的lena图像为例&#xff0c;如下图所示&#xff1a; 使用OpenCV进行人脸检测十分简单&#xff0c;OpenCV官网给了一个Python人脸检测的示例程序&#xff0c;…

每日一个shell脚本之计算器

每日一个shell脚本之计算器 源码 read -p "请输入需要计算的数字公式:" numnumsecho "$num" | bc -lecho "${num}${nums} "使用 复制粘贴进一个.sh结尾的文件,sh 文件名.sh即可运行

人工智能三子棋-人机对弈-人人对弈,谁会是最终赢家?

✅作者简介&#xff1a;大家好我是原始豌豆&#xff0c;感谢支持。 &#x1f194;本文由 原始豌豆 原创 CSDN首发&#x1f412; 如需转载还请通知⚠ &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​ &#x1f4e3;系列专栏&#xff1a;C语言项目实践…

第9讲用户信息修改实现

用户信息修改实现 后端修改用户昵称&#xff1a; /*** 更新用户昵称* param wxUserInfo* param token* return*/ RequestMapping("/updateNickName") public R updateNickName(RequestBody WxUserInfo wxUserInfo,RequestHeader String token){if(StringUtil.isNot…

依赖注入的艺术:编写可扩展 JavaScript 代码的秘密

1. 依赖注入 在 JavaScript 中&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是一种软件设计模式&#xff0c;通过这种模式&#xff0c;可以减少代码模块之间的紧耦合。依赖注入允许开发者将模块的依赖关系从模块的内部转移到外部&…

JAVA学习笔记9

1.Java API 文档 1.java类的组织形式 2.字符类型(char) 1.基本介绍 ​ *字符类型可以表示单个字符&#xff0c;字符类型是char&#xff0c;char是两个字节&#xff08;可以存放汉字&#xff09;&#xff0c;多个字符我们用字符串String ​ eg:char c1 ‘a’; ​ char c2…

机器学习:过拟合和欠拟合的介绍与解决方法

过拟合和欠拟合的表现和解决方法。 其实除了欠拟合和过拟合&#xff0c;还有一种是适度拟合&#xff0c;适度拟合就是我们模型训练想要达到的状态&#xff0c;不过适度拟合这个词平时真的好少见。 过拟合 过拟合的表现 模型在训练集上的表现非常好&#xff0c;但是在测试集…

京东云StarDB for openGauss实现混合多云场景元数据安全可控

推进产业数字化&#xff0c;构筑数智供应链技术底座&#xff0c;绕不开全球信息技术基础三大件之一的数据库。以技术创新为手段&#xff0c;以满足未来业务需求为目标&#xff0c;京东云自主研发的分布式数据库StarDB在长期的业务实践过程中不断进化&#xff0c;在海量且复杂的…

Linux中FIFO管道

介绍&#xff1a; FIFO被称为命名管道&#xff0c;pipe只能用于有血缘关系的进程间通信&#xff0c;但通过FIFO&#xff0c;不相关的进程也可以进程间通信。 FIFO是linux基础文件类型的一种&#xff08;文件类型为p&#xff09;&#xff0c;FIFO文件在磁盘上没有数据块&#…

五(一)java高级-集合-集合与迭代器(二)

5.1.2 Iterator迭代器 1、Iterator 所谓迭代器&#xff1a;就是用于挨个访问集合元素的工具/对象 方法&#xff1a; boolean hasNext():判断当前遍历集合后面是否还有元素可以迭代Object next():取出当前元素&#xff0c;并往后移→noSuchelementExceptionvoid remove():删…

阿里云带宽计费模式怎么选?如何收费的?

阿里云服务器带宽计费模式分为“按固定带宽”和“按使用流量”&#xff0c;有什么区别&#xff1f;按固定带宽是指直接购买多少M带宽&#xff0c;比如1M、5M、10M、100M等&#xff0c;阿里云直接分配用户所购买的带宽值&#xff0c;根据带宽大小先付费再使用&#xff1b;按使用…

锐捷(二十一)全局地址绑定

vlan划分和vlanif接口配置略&#xff0c;注意vlanif接口里要no shutdown配置如下&#xff1a; Address-bind 192.168.1.1 AAAA.BBBB.CCCCAddress-bind uplink g0/0Address-bind binding-filter loggingAddress-bind install 此时&#xff0c;IP为192.168.1.1 mac地址为AAAA.B…

Win10截图的四种方式

截图不一定要依靠通讯软件&#xff0c;现在系统自己就带有这些功能。 1.Win Shift S组合键&#xff1a;选择微信截图&#xff0c;部分截图&#xff0c;随心所欲&#xff1b; 2.Win W组合键&#xff1a;呼出屏幕右侧的工作区&#xff0c;选择屏幕草图&#xff0c;支持裁剪、编辑…

《SQLi-Labs》05. Less 29~37

title: 《SQLi-Labs》05. Less 29~37 date: 2024-01-17 22:49:10 updated: 2024-02-12 18:09:10 categories: WriteUp&#xff1a;Security-Lab excerpt: HTTP 参数污染&#xff0c;联合注入、宽字节注入。 comments: false tags: top_image: /images/backimg/SunsetClimbing.p…

【深度学习】基于多层感知机的手写数字识别

案例2&#xff1a;构建自己的多层感知机: MNIST手写数字识别 相关知识点: numpy科学计算包&#xff0c;如向量化操作&#xff0c;广播机制等 1 任务目标 1.1 数据集简介 ​ MNIST手写数字识别数据集是图像分类领域最常用的数据集之一&#xff0c;它包含60,000张训练图片&am…

【C语言】实现单链表

目录 &#xff08;一&#xff09;头文件 &#xff08;二&#xff09;功能实现 &#xff08;1&#xff09;打印单链表 &#xff08;2&#xff09;头插与头删 &#xff08;3&#xff09;尾插与尾删 &#xff08;4&#xff09; 删除指定位置节点 和 删除指定位置之后的节点 …

DevOps:CI、CD、CB、CT、CD

目录 一、软件开发流程演化快速回顾 &#xff08;一&#xff09;瀑布模型 &#xff08;二&#xff09;原型模型 &#xff08;三&#xff09;螺旋模型 &#xff08;四&#xff09;增量模型 &#xff08;五&#xff09;敏捷开发 &#xff08;六&#xff09;DevOps 二、走…