【JavaSE】浅析String与StringTable

news/2024/11/19 6:25:16/

文章目录

  • 1. 前言
  • 2. String的两种创建方式
    • 2.1 通过new关键字创建一个字符串对象
    • 2.2 采用双引号的方式来创建字符串对象
    • 2.3 两种方式的区别
  • 3. StringTable的位置
  • 4. String的intern()方法
  • 5. 判断两个字符串是否相等
    • 5.1 equals
    • 5.2 ==

1. 前言

String类是开发中经常使用的一个类。

String稍加理解的话,都会听到这样的一个词——字符串常量池(也叫StringTable)

StringTable是用来存放字符串常量的,当我们使用相同字符串对象的时候,就不需要重新创建字符串对象,而是直接在常量池中获取,这一点和Integer的缓存有点类似。


2. String的两种创建方式

String类用得最多的两种创建方式

  1. 通过new关键字创建一个字符串对象
  2. 采用双引号的方式来创建字符串对象

2.1 通过new关键字创建一个字符串对象

String s = new String("zhangsan")

通过new关键词创建一个字符串对象,表面上是创建了一个对象,但是实际上创建了两个对象!!

创建的过程如下:

  1. Java虚拟机会先在字符串常量池中查找有没有zhangsan这个字符串对象
  2. 如果有,就不会在StringTable中创建zhangsan这个字符串对象,并直接在堆中创建zhangsan这个字符串对象。
  3. 如果没有,就会在StringTable中创建zhangsan这个字符串对象,并直接在堆中创建zhangsan这个字符串对象。

到这里可能会感觉到奇怪,为什么要在StringTable中创建对象,又在堆中创建对象呢?

这是因为字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一个字符串常量池


2.2 采用双引号的方式来创建字符串对象

String s = "zhangsan";

采用双引号的方式来创建字符串对象,创建过程如下:

  1. 首先,Java虚拟机会先在StringTable中查看是否存在zhangsan这个字符串
  2. 如果有,直接将StringTable中的该字符串的地址返回并赋值
  3. 如果没,则在字符串常量池中创建该字符串对象,然后将地址返回并赋值

2.3 两种方式的区别

  • 对于通过new关键字创建一个字符串对象这种方式来说,不管StringTable中是否存在该字符串对象,都需要在堆中创建字符串对象。
  • 而对于采用双引号的方式来创建字符串对象这种方式来说,如果StringTable中存在该字符串对象,则不需在堆中创建字符串对象

3. StringTable的位置

在Java8之前,StringTable在永生代中

image-20230122114303812

在Java8之后,移除了永生代,StringTable被移动到堆中

image-20230122114348533


4. String的intern()方法

public native String intern();

Stringintern方法是一个native方法,它的作用就是如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。

intern方法的实现,是JAVA 使用 jni 调用c++实现的StringTableintern方法, StringTableintern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。

**在JDK7之前,**调用intern方法,不管对象在堆中是否已经创建,字符串常量池中仍然会创建一个内容完全相同的新对象。

但是,在JDK7之后由于字符串常量池放在了堆中,执行 String.intern() 方法的时候,如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间。

String s1 = new String("zhangsan");
String s2 = s1.intern();
System.out.println(s1 == s2);

上面的代码执行流程如下(假设zhangsanStringTable中不存在):

  1. 首先,StringTable中会创建一个zhangsan字符串,然后堆中也会创建一个zhangsan字符串,s1引用是堆中的对象。
  2. 接着,对s1执行intern方法,则会在StringTable中寻找是否存在zhangsan这个字符串对象,此时是存在的,于是s2引用是StringTable中的对象。
  3. 因此s1和s2的引用地址不一样,最后输出的结果为false

image-20230122121818828

String s1 = new String("zhang") + new String("san");
String s2 = s1.intern();
System.out.println(s1 == s2);

上面的代码的执行流程如下(假设zhang和sanStringTable中不存在)

  1. 首先,会在StringTable中创建两个对象zhangsan,堆中也会创建两个对象zhangsan。还有一个zhangsan的对象(为什么会有这个呢?稍后就会知道了),这时候s1引用的是堆中的zhangsan这个字符串对象。
    1. 使用+号的String字符串拼接,底层其实都是先创建一个StringBuilder对象,然后调用append方法把要+的字符串都append进去,最后toString创建一个新的String对象
  2. 接着,执行s1的intern的方法,这时候,会到StringTable中寻找是否存在zhangsan这个字符串对象,此时很明显是不存在的,但是堆中存在,因为我这里用的是JDK8,所以字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用(有点懵逼的可以看回上面JDK7前后intern的区别),也就是说直接保存堆中zhangsan对象引用。
  3. 因此s1和s2的引用地址一样,最后输出结果为true

image-20230122121908428


5. 判断两个字符串是否相等

这是面试的高频考点,判断两个字符串有两种方法

  1. ==操作符用于比较两个对象的地址是否相等。
  2. .equals() 方法用于比较两个对象的内容是否相等。

因为equals是比较内容,所以比较简单,考得最多还是==


5.1 equals

先看看Stringequals源码

public boolean equals(Object anObject) {if (this == anObject) {return true;}if (anObject instanceof String) {String anotherString = (String)anObject;int n = value.length;if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}return false;
}
  1. 首先,先判断地址是不是相同
  2. 接着,判断是不是String类,如果不是就可以直接返回false
  3. 如果是,则判断两个字符串长度是否相等,如果不相等也就证明两个字符串不相等了
  4. 如果字符串也相等,那就比较字符串的每个字符是不是相等

5.2 ==

new String("zhangsan") == "zhangsan"

左侧是在堆中的对象,右侧是在StringTable中的对象,而==比较的是地址,所以这个比较的结果是false

new String("zhangsan") == new String("zhangsan")

左右侧均为new出来的对象,也就是说是两个不同对象,不同对象肯定是不同的内存地址,因此结果是false

"zhangsan" == "zhangsan"
  1. 首先左侧的字符串在StringTable中不存在
  2. 需要在StringTable中创建该字符串
  3. 右侧字符串内容和左侧一样,StringTable中存放了左侧的字符串对象,字符串常量池中只会有一个相同内容的对象,因此为true
"zhangsan" == "zhang" + "san"

由于zhangsan都在字符串常量池,所以编译器在遇到+操作符的时候将其自动优化为zhangsan,所以返回 true

new String("zhangsan").intern() == "zhangsan"
  1. new String("zhangsan") 在执行的时候,会先在字符串常量池中创建对象,然后再在堆中创建对象
  2. 执行 intern() 方法的时候发现字符串常量池中已经有了zhangsan这个对象,所以就直接返回字符串常量池中的对象引用了
  3. 那再与字符串常量池中的zhangsan比较,会返回 true 了


http://www.ppmy.cn/news/18871.html

相关文章

C++第六讲——Demon和Angela的魔法之旅

因为作者最近很开心(我不说为什么,可以投票猜原因)所以就忘记更新了,这篇文章C含量为0,狗粮含量99%,密码学含量为1%,水含量为0,请读者放心食用。 话说Angela见到了Angelo的家长后和A…

扫盲-从零开始搭建阿里云流媒体服务器/音视频编解码/

1.基础概念 2.简单模式-HTTP文件服务器 存储采用NAS,服务器配置 采用FASTDFS (192条消息) Linux新手入门系列:FastDFS单机部署一键安装脚本_IT小胖豆的博客-CSDN博客 有几个坑: 常用命令: tail -20f /usr/local/nginx/logs/er…

JQuery总结(三)

jQuery 事件 on可以同时绑定多个事件&#xff0c;并且动态添加的元素也会自动添加事件 自动触发事件&#xff1a; 案例1&#xff1a; <style>div{width: 100px;height: 200px;background-color: violet;transition: all .5s;}.current{width: 200px;height: 100px;ba…

多线程(5)

文章目录前言 &#xff1a;常见锁策略了1.悲观锁 VS 乐观锁2. 轻量级锁 VS 重量级锁3.自旋锁 VS 挂起等待锁4. 读写锁 VS 普通的互斥锁5. 公平锁 和 非公平锁6. 可重入锁 VS 不可重入锁CAS1. CAS 的应用场景2. CAS 的典型问题 : ABA 问题synchronized 原理1.锁升级 / 锁膨胀2.锁…

python练习|面向对象--其他

面向对象---其他一、面向对象三大特性二、多态2.1 了解多态2.2 体验多态三、类属性和实例属性3.1 类属性3.1.1 设置和访问类属性3.1.2 修改类属性四、类方法和静态方法4.1 类方法4.1.1 类方法特点4.1.2 类方法使用场景4.2 静态方法4.2.1 静态方法特点4.2.2 静态方法使用场景五、…

pytorch 神经网络基础入门笔记【b站小土堆】

文章目录python深度学习配置环境anacondapycharmpytorchpython学习中的两大法宝函数加载数据Tensorboard使用torchvision中的transformstensor数据类型transform该如何使用为什么我们需要Tensor类型更好的使用transformsToTensorNormalizeResizeComposeRandomCrop总结torchvisi…

<Python的文件>——《Python》

目录 1.文件 1.1 文件是什么 1.2 文件路径 1.3 文件操作 1.3.1 打开文件 1.3.2 关闭文件 1.3.3 写文件 1.3.4 读文件 1.3.5 关于中文的处理 1.4 使用上下文管理器 1.文件 1.1 文件是什么 变量是把数据保存到内存中. 如果程序重启/主机重启, 内存中的数据就会丢失.…

分类算法有哪些? 回归与分类有什么区别?

一、分类算法 1. 朴素贝叶斯(Naive Bayesian Classifier) 朴素贝叶斯算法属于传统机器学习中非常经典的分类算法之一,具体的算法流程可以参考之前的blog:https://blog.csdn.net/m0_51339444/article/details/126436309 我们所熟悉的神经网络,支持向量机和Logistic Regres…