您的位置:首页 > 编程语言 > Java开发

Java中String的探究

2016-01-09 16:38 225 查看
1. String的基本介绍

相关API

获取信息操作

字符串长度
length()

比较字符串引用
“==”

比较字符串内容
equals() 或 compareTo()

已知位置,找字符
charAt()

已知字符(串),找位置
indexOf() 或 lastIndexOf()

判断开头和结尾
startWith() 或 endWith()

其他类型转换为字符串
valueOf()

更改操作

连接字符串
“+” 或者 concat()

替换子字符串
replace()

获取子字符串
subString()

分割字符串
split()

更换大小写
toUpperCase()、toLowerCase()

去除空白符
trim()

String是常量,我们常称其为不可变性,意思是一旦创建就不能更改。

所以对于上面的“更改操作”,返回值都是字符串,但并不是对源字符串进行更改,而是返回了新的字符串。下面有测试代码。

小实验:如果查看Java API文档中对String的方法的说明,会发现用了很多的“return a new String ”。

2. 验证String的不可变性

所谓String的不可变性,是说一旦字符串被创建,对其所做的任何修改都会生成新的字符串对象。

代码如下:

public static void main(String[] args) {

String a = "abc";

String b = a.toUpperCase();

System.out.println("a: " + a);

System.out.println("b: " + b);

System.out.println("a==b: "+ (a==b));

//当a不发生变化时,不返回新字符串。

String c = a.toLowerCase();

System.out.println("c: " + c);

System.out.println("a==c: "+ (a==c));

}

运行结果:

[java] view plaincopyprint?

a: abc

b: ABC

a==b: false

c: abc

a==c: true

运行结果分析:

字符串a指向"abc",全为小写;字符串b由a得来,指向"ABC";这时a的内容并没有变化,也就证明了Java中String的不变性。

后面利用"a==b"来判断a,b是否指向同一个对象,返回值为false,也能证明String的不变性。

对于字符串c的例子说明如果a没有发生变化,那么不返回也不需要返回新字符串,所以"a==c"的返回值为tru

String类不可变性的解释:

对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method
Area,而不是堆中。常量池中保存着很多String对象; 并且可以被共享使用,因此它提高了效率。

当创建一个新的String时,首先会在JVM常量池中寻找。如果常量池中存在,则直接将字符串的引用指向该常量的地址;若常量池不存在,则创建新的字符串并存到常量池中,同时将字符串的引用指向新创建的常量的地址。在对已有引用重新赋值时,只是引用指向了新的常量,原来创建的常量并没有改变。

String s="abcd"; String s2=s;





s=s.concat("ef“)



关于String str1="hello"; 和 String str2= new String("hello");在JVM中内存分配的区别:

首先简要了解一下JVM的基本知识:

编译器(或者JVM)为了更高效地处理数据,会用不同的算法把内存分为各种区域,不同的区域拥有各自的特性,Java中,内存可以分为栈,堆,静态域和常量池等。(可能有不同的叫法,但逻辑是一致的)

不同内存区域的功能和特点:

栈区:存放局部变量(变量名,对象的引用等)特点:内存随着函数的调用而开辟,随着函数调用结束而释放。

堆区:存放对象(也就是new出来的东西)特点:可以跨函数使用,每个对象有自己对应的存储空间。

静态域:存放在对象中用static定义的静态成员。

常量池:存放常量。(常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。)

String str1 = "hello";:引用str1被存放在栈区,字符串常量"hello"被存放在常量池,引用str1指向了常量池中的"hello"(str1中的存放了常量池中"hello"的地址)。

Srtring str2 = new String ("hello");:引用str2被存放在栈区,同时在堆区开辟一块内存用于存放一个新的String类型对象。(同上,str2指向了堆区新开辟的String类型的对象)

如下图:



这两种方法的区别是什么?

第一种:常量池的字符串常量,不能重复出现,也就是说,在定义多个常量时,编译器先去常量池查找该常量是否已经存在,如果不存在,则在常量池创建一个新的字符串常量;如果该常量已经存在,那么新创建的String类型引用指向常量池中已经存在的值相同的字符串常量,也就是说这是不在常量池开辟新的内存。

String str1 = "hello";

String str2 = "hello";

示意图如图1

第二种:在堆中创建新的内存空间,不考虑该String类型对象的值是否已经存在。换句话说:不管它的 只是多少,第二种方法的这个操作已经会产生的结果是:在堆区开辟一块新的内存,用来存放新定义的String类型的对象。

String str1 = new String("hello");

String str2 = new String("hello");

示意图如果2





案例分析:

public static void main(String[] args) {
/**
* 情景一:字符串池
* JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象;
* 并且可以被共享使用,因此它提高了效率。
* 由于String类是final的,它的值一经创建就不可改变。
* 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。
*/
String s1 = "abc";
//↑ 在字符串池创建了一个对象
String s2 = "abc";
//↑ 字符串pool已经存在对象“abc”(共享),所以创建0个对象,累计创建一个对象
System.out.println("s1 == s2 : "+(s1==s2));
//↑ true 指向同一个对象,
System.out.println("s1.equals(s2) : " + (s1.equals(s2)));
//↑ true  值相等
//↑------------------------------------------------------over
/**
* 情景二:关于new String("")
*
*/
String s3 = new String("abc");
//↑ 创建了两个对象,一个存放在字符串池中,一个存在与堆区中;
//↑ 还有一个对象引用s3存放在栈中
String s4 = new String("abc");
//↑ 字符串池中已经存在“abc”对象,所以只在堆中创建了一个对象
System.out.println("s3 == s4 : "+(s3==s4));
//↑false   s3和s4栈区的地址不同,指向堆区的不同地址;
System.out.println("s3.equals(s4) : "+(s3.equals(s4)));
//↑true  s3和s4的值相同
System.out.println("s1 == s3 : "+(s1==s3));
//↑false 存放的地区多不同,一个栈区,一个堆区
System.out.println("s1.equals(s3) : "+(s1.equals(s3)));
//↑true  值相同
//↑------------------------------------------------------over
/**
* 情景三:
* 由于常量的值在编译的时候就被确定(优化)了。
* 在这里,"ab"和"cd"都是常量,因此变量str3的值在编译时就可以确定。
* 这行代码编译后的效果等同于: String str3 = "abcd";
*/
String str1 = "ab" + "cd";  //1个对象
String str11 = "abcd";
System.out.println("str1 = str11 : "+ (str1 == str11));
//↑------------------------------------------------------over
/**
* 情景四:
* 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。
*
* 第三行代码原理(str2+str3):
* 运行期JVM首先会在堆中创建一个StringBuilder类,
* 同时用str2指向的拘留字符串对象完成初始化,
* 然后调用append方法完成对str3所指向的拘留字符串的合并,
* 接着调用StringBuilder的toString()方法在堆中创建一个String对象,
* 最后将刚生成的String对象的堆地址存放在局部变量str3中。
*
* 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。
* str4与str5地址当然不一样了。
*
* 内存中实际上有五个字符串对象:
*       三个拘留字符串对象、一个String对象和一个StringBuilder对象。
*/
String str2 = "ab";  //1个对象
String str3 = "cd";  //1个对象
String str4 = str2+str3;
String str5 = "abcd";
System.out.println("str4 = str5 : " + (str4==str5)); // false
//↑------------------------------------------------------over
/**
* 情景五:
*  JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。
*  运行期的两个string相加,会产生新的对象的,存储在堆(heap)中
*/
String str6 = "b";
String str7 = "a" + str6;
String str67 = "ab";
System.out.println("str7 = str67 : "+ (str7 == str67));
//↑str6为变量,在运行期才会被解析。
final String str8 = "b";
String str9 = "a" + str8;
String str89 = "ab";
System.out.println("str9 = str89 : "+ (str9 == str89));
//↑str8为常量变量,编译期会被优化
//↑------------------------------------------------------over
}


注意:1、使用String不一定创建对象。String s ="abc";若此时常量池中有abc,则引用直接指向该常量,此时不必创建新的常量。

2、使用String s = new String("abc");一定创建新的对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: