您的位置 首页 java

JAVA脱水学习-java字符串内存分配及常用操作

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

一、字符串内存存储

1.字符串实例

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

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

内存存储方式如图:

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

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

如图:

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

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

对应的内存存储方式:

打印下他们的比较值:

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

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

如图:

由此可知字符串比较:

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

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)位置。

图片来源网络

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

String s = "hello";
 

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

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

看下面这段代码:

public static void main(String[] args) {
 String s1 = new String("hello");
 s1.intern();
 String s2 = "hello";
 System.out.println(s1 == s2); // false
 String s3 = new String("hello") + new String(" world");
 s3.intern();
 String s4 = "hello world";
 System.out.println(s3 == s4); // true
}
 

上面说到字符串常量池存储方式有所改变,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” ,如下:

public static void main(String[] args) {
 String s3 = new String("ja") + new String("va");
 s3.intern();
 String s4 = "java";
 System.out.println(s3 == s4); // 返回 false
}
 

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

二、常用字符串操作

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

1.字符串比较

2.字符串拼接

3.字符串截取

4.字符串转换

5.字符串查找

6.字符串替换

7.字符串拆分

8.字节与字符串

9.其他

文章来源:智云一二三科技

文章标题:JAVA脱水学习-java字符串内存分配及常用操作

文章地址:https://www.zhihuclub.com/186595.shtml

关于作者: 智云科技

热门文章

网站地图