java string 内存_理解java字符串内存分配及常用操作

本文深入探讨了Java中字符串的内存存储机制,包括String对象的创建、比较方式(==与equals())、字符串常量池与匿名对象。此外,还介绍了常用字符串操作方法及其原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

从数据类型可知 Java 语言中并没有字符串类型数据。那么字符串又是什么类型呢?Java 标准库中提供了一个预定义类 String,编译器将每个用双引号包裹的不同字符串都实现一个 String 类的一个实例。

一、字符串内存存储

1.字符串实例

既然字符串是个 String 类实例,那么每初始化一个字符串变量就会在堆中创建一个字符串对象,并同时在栈中声明一个变量值指向堆中的对象。初始化几个字符串:

String str1 = "hello";String str2 = "world";String str3 = str2;

内存存储方式如图:

29331b5ba5b60061bef64469ed9cdb9b.png

从图中可知变量 str1、str2、str3 保存的是 String 对象的引用地址。地址在内存中是个指针也就是整型,当用 == 判断字符串对象是否相同时,实际比较的是地址值。

System.out.println(str1 == str2); // falseSystem.out.println(str2 == str3); // true

如图:

35816700342273bc178154fceda796e2.png

我们都知道类可以通过 new 来创建其实例,那么 String 类当然也可以,而且 new 创建的实例都会重新开辟内存空间。如:

String str1 = "hello";String str2 = "hello";String str3 = new String("hello");System.out.println(str1 == str2); // trueSystem.out.println(str2 == str3); // false

对应的内存存储方式:

5cc672b3499b95108fe79c2be0035f57.png

打印下他们的比较值:

583bc35b5cb928c1bb7ba8b6c9cdd168.png

从上面的现象可知字符串 == 比较,实际上比较的是字符串的地址值,如果要比较字符串的存储值呢?String 类提供有比较方法 equals():

String str1 = "hello";String str2 = "hello";String str3 = new String("hello");System.out.println(str1.equals(str2)); // trueSystem.out.println(str2.equals(str3)); // true

如图:

3345d95e40e058531092862bfe90738a.png

由此可知字符串比较:

==:比较的是地址值,属于数值比较。

equals():比较的是字符串内容,属于内容比较。

2.字符串匿名对象,字符串常量

我们知道编译器会将双引号包裹的字符串解析成 String 对象,那么一个未赋值给变量的字符串也一样具备 String 类定义的方法,如:

String str1 = "hello";System.out.println("hello".equals(str1)); // true

这个未赋值给变量的 "hello" 就是个匿名 String 对象,也可称为字符串常量。因为在编译时就能确定它的值,且这个值不会改变。

从第 4 章变量的内存分配可知,JVM 将内存划分为:Method Area(方法区)、Heap(堆)、JVM Stack(JVM 栈)、Native Method Stack(本地方法栈)、Program Counter Register(程序计数器)。根据各区块的定义字符串常量应当是存放在方法区中,实际是方法区中运行时常量池(Runtime Constant Pool)的驻留字符串(Interned Strings)位置。

24b9d7dd1fd2d8fcfa57b00433a7ad73.png图片来源网络

如下,通过字符串字面值创建字符串对象:

String s = "hello";

在 JDK 1.6 及以前版本会在字符串常量池中查找有没相同值的字符串,有直接返回其引用,没有则创建新的字符串对象后返回其引用。而在 1.7 及以后版本中,字符串对象第一次创建时先在堆中被创建,然后会在字符串常量池中创建一个引用指向堆中的对象,所以字符串常量池中保存的都是引用地址。所以通过字符串字面值创建字符串对象时,只要内容一致,内存中只会保留一个对象,常量池中也仅会有一个引用地址。

通过 New String() 方式创建的字符串对象,常量池中并没有保存其引用。不过 String 类提供了一个入池的方法 intern()。通过调用 intern() 方法会先判断常量池中有没值相同的字符串引用,有则直接返回引用地址,没有则复制当前字符串的引用并返回。方法原文注释:

When the intern method is invoked, if the pool already contains astring equal to this {@code String} object as determined bythe {@link #equals(Object)} method, then the string from the pool isreturned. Otherwise, this {@code String} object is added to thepool and a reference to this {@code String} object is returned.

看下面这段代码:

8a44f1624350dd41d7e002efe9693c8f.png

上面说到字符串常量池存储方式有所改变,JDK 1.6 及以前版本返回:false、false,JDK 1.7 及以后版本返回:false、ture。下面说下这个代码的意思(1.7 以后版本):

1.编译执行字符串常量 "hello",在堆中创建 "hello" 字符串对象,在常量池中保存其引用。2.创建字符串 s1,在堆中创建一个新 "hello" 字符串对象,在栈中创建变量 s1 指向新串。3.执行 s1.intern(),先在常量池中查找有无值相同的(equals)引用。这里已经存中,直接返回引用地址。4.赋值 s2,先在常量池中查找有无值相同的字符串引用,有直接返回引用地址,这里已经有 "hello" 了,返回第1步中的引用地址给变量 s2。5.比较 s1 == s2,地址不同所以返回 false。6.创建字符串对象 s3,忽略 "hello"、" world",先在堆中创建 "hello world" 对象,在栈中创建其引用变量 s3。7.执行 s3.intern(),先在常量池中查找有无值相同的引用。这里没有,所以把 s3 对象的引用放入常量池。8.赋值 s4,先在常量池中查找有无值相同的字符串引用,有直接返回引用地址。这里第 7 步已经创建了,直接返回 s3 对象的引用地址。9.比较 s3 == s4,引用地址相同所以返回 true。

根据上面步骤不难理解为何结果是 false、true。这里还有个坑需要注意下,把 "hello world" 换成 "java" ,如下:

970a2a4e8055e3d59c930f14177d44bd.png

按照前面讲的这里也应该返回 true 才对,为什么是个 false?这里返回 false 那肯定是字符串 "java" 已经被加载过了。常量池在初始化的时候会内置一些字符串常量进去,在 rt.jar 里面已经用到了 "java" 这个字符串,所以二图中 s4 指向的是常量池的引用地址,s3 是 new 出的新字符串肯定不同,所以是 false。

二、常用字符串操作

既然字符串都是 String 类的实例,那么所有的字符串操作都可以通过 String 类中的方法进行。

1.字符串比较

1fb7cfb6f9f49b2ea4099091e740269d.png

2.字符串拼接

eb084397bc2be3700c6bdc0661b60505.png

3.字符串截取

3677db769762650044ab878d871e519a.png

4.字符串转换

02991d33f1b1397cad0299e36e8fef0d.png

5.字符串查找

8e7f511a38aae615b5a4c5fdd4ddd68f.png

6.字符串替换

5f72cbf49f26ca7c4eb0b15dde7207c0.png

7.字符串拆分

b1f4bd2f6b8bfbf25289e58604633ea8.png

8.字节与字符串

dc4f0bf424f09dd1557f2ba8a93d5cd6.png

9.其他

301364668e36b75948d74038fe5e2305.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值