【JavaSE】浅析String与StringTable

news2024/11/15 13:40:14

文章目录

  • 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.coloradmin.cn/o/175706.html

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

相关文章

第七层:多态

文章目录前情回顾多态多态的基本概念动态多态的满足条件动态多态的使用虚函数多态的优点纯虚函数和抽象类抽象类特点虚析构和纯虚析构虚析构和纯虚析构的共性虚析构和纯虚析构的区别面向对象结束,接下来是什么?本章知识点(图片形式)&#x1…

数据结构进阶 哈希桶

作者:小萌新 专栏:数据结构进阶 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:模拟实现高阶数据结构 哈希桶 哈希桶哈希冲突的另一种解决方法开散列 -- 链地址法举例哈希表的开散列实现 --哈希桶哈希表的结构…

自动化测试Selenium【基础篇二】

自动化测试Selenium【基础篇二】🍎一.Selenium基础使用🍒1.1 信息打印🍉 1.1.1打印标题🍉 1.1.1打印当前网页标题🍒1.2 窗口🍉1.2.1 获取句柄🍉1.2.2 窗口切换🍉1.2.3 窗口大小设置&…

当你点击浏览器的瞬间都发生了什么----- 网络学习笔记

计算机网络前言web 浏览器协议栈创建套接字阶段。连接阶段。断开阶段。IP模块网卡网络设备 --- 集线器、交换器和路由器集线器交换器路由器路由器的附加功能一 :地址转换路由器的附加功能一 :包过滤功能互联网内部接入网光纤接入网(FTTH&…

JDK8 前后的 Date 日期时间 API

JDK8 前后的 Date 日期时间 API 每博一文案 师父说:人只要活在世界上,就会有很多的烦恼,痛苦或是快乐,取决于逆的内心,只要心里拥有温暖灿烂的阳光, 那么悲伤又有什么好畏惧的呢? 人生如行路&a…

vue学习笔记(更新中)

目录 简介 使用Vue写一个"hello,world" 前置准备 代码书写 MVVM模型理解 插值语法和指令语法 插值语法 指令语法 指令:v-bind 指令:v-model vue中的el和data的两种写法 数据代理 方法:defineProperty() 说明…

新年礼物已收到!2022 Apache IoTDB Commits 数量排名 3/364!

社区喜报!据 The Apache Software Foundation 官方 Projects Statistics(项目信息统计网站)的实时数据显示,Apache IoTDB 在过去 12 个月(即 2022 年度)共发表 6829 Commits,排名 2022 年度 Apa…

2、Three.js开发入门与调试设置

一、添加坐标轴辅助器 AxesHelper 用于简单模拟3个坐标轴的对象. 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴. 构造函数 AxesHelper( size : Number ) size -- (可选的) 表示代表轴的线段长度. 默认为 1. //添加坐标轴 const axesHelper new THREE.AxesHelper(5); sc…

CSS 特效之心形-彩虹-加载动画

CSS 特效之心形-彩虹-加载动画参考描述效果HTMLCSS重置元素的部分默认样式bodyli动画定义指定animationul抖动代码总汇参考 项目描述搜索引擎BingMDNMDN Web Docs 描述 项目描述Edge109.0.1518.61 (正式版本) (64 位) 效果 HTML <!DOCTYPE html> <html lang"e…

Keil C51工程转VSCode Keil Assistant开发全过程

Keil C51工程转VSCode Keil Assistant开发全过程✨这里以stc15W408AS为例。&#x1f4cc;相关篇《【开源分享】自制STC15W408AS开发板》 &#x1f4fa;编译-烧录演示&#xff1a; &#x1f4cb;转VSCODE开发环境主要原因可能代码提示以及代码跳转功能&#xff0c;或者其他。 &…

在java中操作redis

在普通项目中操作redis 1.导入maven坐标 <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.0</version> </dependency>2.打开redis 如果redis-server闪退&#xff0c;那就先打开re…

【Spring Security】如何使用Lambda DSL配置Spring Security

本期目录1. 概述2. 新老配置风格对比Lambda风格等效的旧配置风格3. WebFlux Security4. Lambda DSL的目标1. 概述 在 Spring Security 5.2 中增强了 DSL 的功能&#xff1a;允许使用 Lambda 表达式来配置 HTTP security 。 需要注意的是&#xff1a;先前版本的配置风格仍然是…

二分查找——“C”

各位CSDN的uu们你们好呀&#xff0c;欢迎来到小雅兰的课堂&#xff0c;今天我们的内容是复习之前的内容&#xff0c;并把之前的内容的一些习题一起来做一做&#xff0c;现在&#xff0c;就让我们进入二分查找的世界吧 首先&#xff0c;我们介绍的题目就是二分查找&#xff0c;也…

module ‘tensorflow‘ has no attribute ‘Session‘

1. module ‘tensorflow‘ has no attribute ‘Session‘ 指定一个会话来运行tensorflow程序&#xff0c;在使用tensorflow1.x版本中用tensorflow.Session即可 但当我的库版本升级到2.x之后&#xff0c;就会出现标题式报错&#xff0c;于是我去查看了tensorflow库的源码&…

使用Keras搭建深度学习模型

前言 目前深度学习领域的主流框架 tensorflowkeraspytorchcaffetheanopaddlepaddle keras 代码架构 keras代码风格相比于其他框架更符合人的思维。 模型 模型的组成分为三部分&#xff1a;输入层、网络层、输出层。 输入层 输入层的作用时规定了模型输入的shape fro…

2022年智源社区年度热点推荐丨新春集锦

本文为2022年最受智源社区小伙伴喜爱的文章&#xff0c;根据文章质量和热门程度等维度计算得出。还有AI大佬的全年总结盘点总结&#xff0c;也一并推荐给你。虎年除旧&#xff0c;兔年迎新&#xff0c;藉此机会、智源编辑组全员谨祝大家新春快乐&#xff01;2022智源社区20篇最…

LINUX学习之网络配置(十一)

1.修改IP地址 使用ifconfig命令 例如要将eth0接口的IP地址更改为192.168.1.100&#xff0c;你可以使用以下命令 ifconfig eth0 192.168.1.100如果你想为IP地址设置子网掩码&#xff0c;可以使用“netmask”参数。例如&#xff0c;要将eth0接口的子网掩码设置为255.255.255.0…

[Linux]进程优先级 Linux中的环境变量

&#x1f941;作者&#xff1a; 华丞臧. &#x1f4d5;​​​​专栏&#xff1a;【LINUX】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文…

liunx centos9中安装Redis数据库,并在win10中连接redis图文详解

首先我们去Redis的官网点击download下载tar的压缩包 https://redis.io/download/#redis-downloads 用xftp将安装包上传到你的liunx服务器本地地址 解压 tar -xvf /root/redis-7.0.8.tar.gz cd进入你刚才解压的文件夹中 cd /root/redis-7.0.8 执行make进行编译 编译完成后cd进入…

普中科技MicroPython基于esp32的基础教程-03-字符串

目录 字符串 字符串的表示方式 普通字符串 原始字符串 长字符串 字符串与数字相互转换 将字符串转换为数字 将数字转换为字符串 格式化字符串 占位符% format方法 f-strings 操作字符串 字符串拼接 字符串查找 字符串替换 字符串分割 去处字符串两…