一、String类简介
字符串类型在严格意义上来说并不算是一个基本类型,Java当中的字符串类型实际上是为了方便开发者开发而编写的一个类。字符串类型在Java中存储实际上是以一种数组的形式保存的。在jdk1.8及以前用来保存字符串的数组是char类型的,而1.9及更高版本的字符串数组则是byte型的。
二、String类型的实例化方法比较
- 直接赋值
String str = "hello";
当采用上述形式进行赋值时,“hello”这个字符串实际上是一个匿名对象,存储在堆内存当中。使用这种方式进行字符串存储的一个优势就是只产生一个字符串对象,而且多个引用可以共享这一个字符串,减少了垃圾内存的产生。为了验证多个引用可以共享同一个字符串我们来看下面一段代码:
String str1 = "hello";
String str2 = "hello";
//比较内存地址
System.out.println(str1==str2);
---------------
输出结果:true
上述代码表明,str1和str2引用的是同一个内存地址的内容,也就是说二者指向的是同一个对象。
那么这究竟是为什么呢。下面我们来进行内存分析。首先前面讲过,字符串"hello"实际上是一个匿名对象,既然是对象,那么他肯定是要在堆中开辟一个内存空间进行存储的,实际上在Java中堆内存当中存在一个字符串常量池,使用直接赋值方式产生的String对象是存储在常量池当中的,当str2也要引用一个"hello"对象时,jvm首先会去常量池中寻找这个对象,如果有则直接引用,如果没有则在池中添加一个对象。
- 使用构造方法实例化
String str = new String("hello");
当采用上述代码实例化一个字符串对象时,关键字new在堆内存中开辟了一个空间进行存储,要注意此时str引用的对象并没有存储在常量池当中。看下面一段代码:
String str1 = "hello";
String str2 = new String("hello");
//检测存储地址是否相同
System.out.println(str1==str2);
------------------------------------
结果:false
上述代码表明,str1和str2引用的不是同一个内存地址的内容,也就是说二者指向的并非同一个对象。
实际上使用构造方法创建字符串对象会在内存中开辟两块空间进行存储,一块空间在常量池当中,一块空间在非常量池的堆空间。
对于代码中的"world"部分Java认为这是一个匿名对象,所以会在常量池当中存储一个"world",而str则是引用由关键字new所创建的对象。也就是说在这一过程中造成了内存的浪费。
三、String对象池
对象(常量)池存在的目的是为了数据共享。在Java当中存在两种常量池:
- 静态常量池
指的是在程序(*.class)加载的时候自动将此程序中保存的字符串或者常量、类和方法信息等静态描述的数据全部进行分配。 - 动态常量池
指的是在程序(*.class)加载后,里面可能存在一些变量。这些变量就存储在动态池当中。
String str1 = "hello"+"my"+"world";
String str2 = "hellomyworld";
//检测存储地址是否相同
System.out.println(str1==str2);
---------------------------
结果:true
上述代码中str1为三个静态常量拼接而成的字符串,这种字符串在拼接完成后,系统会将其与静态常量池中的对象进行比对,当发现有相同对象时,Java自动引用。
//定义一个变量
String str="my";
String str1 = "hello"+str+"world";
String str2 = "hellomyworld";
//检测存储地址是否相同
System.out.println(str1==str2);
------------------------------
结果:false
上述代码,虽然str1与str2的内容相同,但是地址比较的结果却是false。这是因为str是一个变量,程序在加载的时候并不能确定变量的内容,所以不认为str1是最终想要的结果,str1也就存储到了动态常量池当中。