【Java SE】Java中String的内存原理

参考笔记:

Java String 类深度解析:内存模型、常量池与核心机制_java stringx、-CSDN博客

解析java中String的内存原理_string s1 = new string("ab");内存分析-CSDN博客


目录

1.String初识

2.字符串字面量

3.内存原理图

4. 示例验证

4.1 字面量直接赋值

4.2 new方式赋值

4.3 new和直接赋值混合

4.4 字符串拼接

4.4.1 +号两边至少有一个变量

4.4.2 +号两边都是字面量

4.5 intern()方法

4.5.1 示例代码1

4.5.2 示例代码2


1.String初识

(1)JavaString 是引用数据类型

(2)因为字符串使用比较频繁,所以 Java 专门为字符串准备了一个字符串常量池。 Java 8 之前字符串常量池在方法区中,Java 8 之后在中,本文讲的是 Java 8 之前

(3)放在字符串常量池中的好处就是省去了对象的创建过程,从而提高程序的执行效率。常量池是一种缓存技术,缓存技术是提高程序执行效率的重要手段

(4)字符串一旦创建是不可变的(String 源码有一个属性:private final byte[] value)

 示例:String s = "hello"

其中 "hello" 存储在字符串常量池中,字符串常量池中的 "hello" 不可变,不能变成 "hello123"。而 s 仍然可以指向其他的字符串对象,例如 s = "xyz"

2.字符串字面量

字符串字面量:我们自己给出的字符串,也可以称作字符串常量。如 "123","abc"

判断方法:简单来说就是在程序中的任何位置,只要出现带上英文双引号的就可以算是字符串字面量

字符串常量池规则

字符串字面量一旦出现,会先去方法区里的字符串常量池找有没有该字符串常量。

(1)如果有,则直接返回字符串常量池中存放该字符串的空间的地址

(2)如果没有,则在字符串常量池里面开辟一块空间用来存放该字符串常量,并返回空间地址

示例代码

public class demo {public static void main(String[] args) {String s1 = "123";String s2 = new String("456");//"456"String s3 = "12";String s4 = "k";String s5 = s3+s4;//"12K"String s6 = s3+"马";//"12马"String s7 = "s"+"abc";//"sabc"}
}

经过上述代码,字符串常量池中有字符串:"123","456","12","k","马","s","abc","sabc"

这些都是字符串字面量,但是字符串常量池中不会有 "12k" 、"12马"(后面会作解释)

3.内存原理图

4. 示例验证

4.1 字面量直接赋值

注:s1 == s2 比较的是 s1s2 的引用地址是否相同, s1.eauals(s2) 比较的是 s1s2 的内容是否相同

示例代码 

public class demo {public static void main(String[] args){String s1="12";//字符串字面量12会先在方法区中的字符串常量池中找,//发现没有同内容的字符串常量,那么就开辟一个新的空间,存放12//然后再把这个地址赋值给s1String s2="12";//字符串字面量12会现在方法区中的字符串常量池中找,//发现已经存在了字符串常量12了,此时无需再区开辟空间//只需要把已经存在的字符串常量的地址赋值给s2就行了//s1与s2指向的是同一个字符串常量的地址,所以s1==s2,输出trueSystem.out.println(s1==s2);}
}

示例代码内存原理图

4.2 new方式赋值

注:Java 开发中很少使用 new 的方式给 String 赋值,因为在堆中会产生不必要的内存分配,直接使用字面量赋值更高效

示例代码

public class demo {public static void main(String[] args) {String s1 = new String("123");String s2 = new String("123");//只要有new就会在堆内存中开辟空间//字符串字面量在字符串常量池中开辟的空间的那个地址值会存放到开辟的堆内存中//s1,s2指向的都是自己堆内存中开辟的空间,并没有直接指向字符串常量池的"123"的那个地址//因此s1与s2进行 == 比较,输出为falseSystem.out.println(s1 == s2);//s1与s2内容相同,输出为trueSystem.out.println(s1.equals(s2));}
}

示例代码内存原理图 

4.3 new和直接赋值混合

示例代码

public class demo {public static void main(String[] args) {String s1=new String("123");String s2="12"+"3";//会在字符串常量池开辟"123","12","3"的空间,//"123"在字符串常量池中开辟的空间地址赋值到了s1中开辟的堆空间中,s1指向的是堆空间地址//"123"在字符串常量池中开辟的空间地址直接赋值给了s2//因此,s1与s2进行 == 比较,输出为falseSystem.out.println(s1==s2);}
}

示例代码内存原理图

4.4 字符串拼接

4.4.1 +号两边至少有一个变量

如果 + 号两边至少有一个是变量,则用 + 拼接生成的新的字符串不会被放到字符串常量池中,只会存放到堆中

示例代码

public class demo {public static void main(String[] args) {//字符串常量池中创建"123","456"String s1 = "123";String s2 = "456";//s3="123456"是拼接而来,所以"123456"不在字符串常量池中,存放在堆中String s3 = s1 + s2;//字符串常量池中创建"123456"//s4的引用是字符串常量池中存放"123456"的地址String s4 = "123456";//s3与s4的引用不同,所以输出为falseSystem.out.println(s3 == s4);//s3与s4的内容相同,输出为trueSystem.out.println(s3.equals(s4));}
}

4.4.2 +号两边都是字面量

如果 + 号两边都是字符串字面量(常量),编译器会进行自动优化。在编译阶段进行拼接。 +两边的字符串字面量、拼接后的新字符串都会被放到字符串常量池中,返回的引用也是来自字符串常量池

示例代码

public class demo {public static void main(String[] args) {//字符串常量池中创建"123"、"456"、"123456"//返回字符串常量池中存放"123456"的地址String s1 = "123"+"456";//字符串常量池中已存在"456"//返回字符串常量池中存放"456"的地址String s2 = "456";//s2与"456"的引用相同,所以输出为trueSystem.out.println(s2 == "456");//字符串常量池中已存在"123456"//返回字符串常量池中存放"123456"的地址String s3 = "123456";//s1与s3的引用相同,所以输出为trueSystem.out.println(s1 == s3);}
}

示例代码内存原理图 

4.5 intern()方法

 intern() 检查当前该字符串字面量是否已经存放于字符串常量池中

(1)存在:直接返回字符串常量池中存放该字符串字面量的空间地址
(2)不存在:将新的字符串字面量添加到常量池中,并返回引用

4.5.1 示例代码1

示例代码

public class demo {public static void main(String[] args) {String s1 = new String("123");//在字符串常量池开辟"123"的空间//"123"在字符串常量池中开辟的空间地址赋值到了s1中开辟的堆空间中,s1指向的是堆空间地址//字符串常量池中已有"123",调用intern()返回其在字符串常量池中的引用地址String s2 = new String("123").intern();//字符串常量池中已有"123",返回其在字符串常量池中的引用地址String s3 = "123";System.out.println(s1==s2);//falseSystem.out.println(s2==s3);//trueSystem.out.println(s1==s3);//false}
}

示例代码内存原理图 

4.5.2 示例代码2

4.4.1 提到,如果 + 号两边至少有一个是变量,则用 + 拼接生成的新字符串不会被放到字符串常量池中,只会存放到堆中

这种场景下就可以用 intern() 方法来将 + 拼接生成的新字符串手动添加到字符串常量池中,并且返回的引用就来自字符串常量池

示例代码 

public class demo {public static void main(String[] args) {String s1 = "hello";String s2 = "world";//拼接生成的"helloworld",存放在堆中String s3 = s1 + s2;//手动将拼接生成的"helloworld"添加到字符串常量池中,并返回引用String s4 = s3.intern();//字符串常量池中已有"helloworld",返回其在字符串常量池中的引用地址String s5 = "helloworld";//输出trueSystem.out.println(s4==s5);}
}

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

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

相关文章

Prometheus + Grafana 监控

Prometheus Grafana 监控 官网介绍:Prometheus 是一个开源系统 监控和警报工具包最初由 SoundCloud 构建。自 2012 年成立以来,许多 公司和组织已经采用了 Prometheus,并且该项目具有非常 活跃的开发人员和用户社区。它现在是一个独立的开源…

【Python爬虫(95)】Python爬虫进阶:构建大型垂直领域爬虫系统

【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取,还涉及数据处理与分析。无论是新手小白还是进阶开发…

Node.js定义以及性能优化

Node.js Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,广泛用于构建高性能的网络应用。以下是一些常见的 Node.js 面试题及其解答,帮助你准备面试: 1. 什么是 Node.js? Node.js 是一个基于 Chrome V8 引擎的 JavaSc…

开源|Documind协同文档(接入deepseek-r1、支持实时聊天)

Documind 🚀 项目介绍 Documind 一个支持实时聊天和接入deepseek-r1模型AI助手的协同文档编辑项目 前端:NextJS React TailwindCSS ShadcnUl Tiptap Zustand后端:NextJS Convex Liveblocks Clerk项目预览:Documind 预览…

JVM内存模型详解:各个区域的作用与原理

引言 Java虚拟机(JVM)是Java程序运行的核心环境,它负责管理程序的内存、执行字节码以及提供跨平台的支持。理解JVM的内存模型对于编写高效、稳定的Java程序至关重要。本文将详细介绍JVM的内存模型,并深入探讨各个内存区域的作用和…

机器学习之集成学习思维导图

学习笔记—机器学习-集成学习思维导图 20250227,以后复习看(周老师的集成学习) PS:图片看不清,可以下载下来看。 往期思维导图: 机器学习之集成学习Bagging(随机深林、VR-树、极端随机树&…

【http://noi.openjudge.cn/】4.3算法之图论——1538:Gopher II

[【http://noi.openjudge.cn/】4.3算法之图论——1538:Gopher II] 题目 查看提交统计提问 总时间限制: 2000ms 内存限制: 65536kB 描述 The gopher family, having averted the canine threat, must face a new predator. The are n gophers and m gopher holes, each at di…

Apache Spark中的依赖关系与任务调度机制解析

Apache Spark中的依赖关系与任务调度机制解析 在Spark的分布式计算框架中,RDD(弹性分布式数据集)的依赖关系是理解任务调度、性能优化及容错机制的关键。宽依赖(Wide Dependency)与窄依赖(Narrow Dependency)作为两种核心依赖类型,直接影响Stage划分、Shuffle操作及容…

【计算机网络】TCP协议相关总结,TCP可靠性的生动讲解

TCP 可靠性 确保快递不丢、不乱、不过载 机制作用(快递类比)防止的问题检验和检查包裹是否损坏,损坏就重新发数据出错序列号给每个包裹编号,按顺序整理乱序、重复确认应答每送到一件,就让收件人签收丢失滑动窗口控制…

Go基于协程池的延迟任务调度器

原理 通过用一个goroutine以及堆来存储要待调度的延迟任务,当达到调度时间后,将其添加到协程池中去执行。 主要是使用了chan、Mutex、atomic及ants协程池来实现。 用途 主要是用于高并发及大量定时任务要处理的情况,如果使用Go协程来实现每…

杰发科技AC7801——滴答定时器获取时间戳

1. 滴答定时器 杰发科技7801内部有一个滴答定时器,该定时器是M0核自带的,因此可以直接用该定时器来获取时间戳。 同样,7803也可以使用该方式获取时间戳。 2. 滴答定时器原理 SysTick是一个24位的递减计数器,它从预设的重装载值…

湖仓一体概述

湖仓一体之前,数据分析经历了数据库、数据仓库和数据湖分析三个时代。 首先是数据库,它是一个最基础的概念,主要负责联机事务处理,也提供基本的数据分析能力。 随着数据量的增长,出现了数据仓库,它存储的是…

第十五届蓝桥杯单片机组4T模拟赛三(第二套)

本套试题在4T平台中的名字为第15届蓝桥杯单片机组模拟考试三,不知道哪套是4T的模拟赛,所以两套都敲一遍练练手感。 为了代码呈现美观,本文章前面的各个模块在main函数中的处理函数均未添加退出处理,在最后给出的完整代码中体现。 …

CT技术变迁史——CT是如何诞生的?

第一代CT(平移-旋转) X线球管为固定阳极,发射X线为直线笔形束,一个探测器,采用直线和旋转扫描相结合,即直线扫描后,旋转1次,再行直线扫描,旋转180完成一层面扫描,扫描时间3~6分钟。矩阵象素256256或320320。仅用于颅脑检查。 第二代CT (平移-旋转) 与第一代无质…

Virtual Box虚拟机安装苹果Monterey和big sur版本实践

虚拟机安装苹果实践,在Windows10系统,安装Virtual Box7.1.6,安装虚拟苹果Monterey版本Monterey (macOS 12) 。碰到的主要问题是安装光盘不像Windows那么容易拿到,而且根据网上很多文章制作的光盘,在viritualBox里都无法…

dify基础之prompts

摘要:在大型语言模型(LLM)应用中,Prompt(提示词)是连接用户意图与模型输出的核心工具。本文从概念、组成、设计原则到实践案例,系统讲解如何通过Prompt解锁LLM的潜能,提升生成内容的…

【学写LibreCAD】0 仿写LibreCAD简介

一、LibreCAD 核心模块: 核心模块(Core) 功能:处理 CAD 的核心逻辑,如几何计算、图形对象管理、坐标系转换等。关键组件: 图形对象:如直线、圆、圆弧、多段线等。数学工具:向量、矩…

HTML元素,标签到底指的哪块部分?单双标签何时使用?

1. 标签&#xff08;Tag&#xff09; vs 元素&#xff08;Element&#xff09; 标签&#xff08;Tag&#xff09; 标签是 HTML 中用于定义元素的符号&#xff0c;用尖括号 < > 包裹。例如 <img> 是标签。元素&#xff08;Element&#xff09; 元素是由 标签 内容…

Android APK组成编译打包流程详解

Android APK&#xff08;Android Package&#xff09;是 Android 应用的安装包文件&#xff0c;其组成和打包流程涉及多个步骤和文件结构。以下是详细的说明&#xff1a; 一、APK 的组成 APK 是一个 ZIP 格式的压缩包&#xff0c;包含应用运行所需的所有文件。解压后主要包含以…

Token相关设计

文章目录 1. 双Token 机制概述1.1 访问令牌&#xff08;Access Token&#xff09;1.2 刷新令牌&#xff08;Refresh Token&#xff09; 2. 双Token 认证流程3. Spring Boot 具体实现3.1 生成 Token&#xff08;使用 JWT&#xff09;3.2 解析 Token3.3 登录接口&#xff08;返回…