但行好事
莫论前程❤

Java基础学习总结-String字符串常量池-intern()方法

首先查看官方API那个的解释:

inter
public String intern()
返回字符串对象的规范化表示形式。
一个初始时为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
所有字面值字符串和字符串赋值常量表达式都是内部的。
返回:
一个字符串,内容与此字符串相同,但它保证来自字符串池中。

常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。
但我们可以通过intern()方法扩展常量池。

例1:

String ok="apple";               //创建方式1
String ok1=new String("apple");  //创建方式2
System.out.println(ok==ok1);    //false  
ok1=ok.intern();                //获取常量池ok的引用  
System.out.println(ok==ok1);    //true--指向同一个对象 

输出结果:
false
true

intern()是扩充常量池的一个方法,尽管在输出中调用intern方法并没有什么效果,但是实际上后台这个方法会做一系列的动作和操作。当一个String实例str调用intern()方法时,java会检查常量池中是否有相同的字符串,如果有则返回其引用,如果没有则在常量池中增加一个str字符串并返回它的引用。

例2:

String str1 ="a";
String str2 ="b";
String str3 ="ab";
String str4 = str1 + str2;
String str5 =new String("ab");

System.out.println(str5.equals(str3));
System.out.println(str5 == str3);
System.out.println(str5.intern() == str3);
System.out.println(str5.intern() == str4);

得到的结果:

true
false
true
false

在调用”ab”.intern()方法的时候会返回”ab”,但是这个方法会首先检查字符串池中是否有”ab”这个字符串,如果存在则返回这个字符串的引用,否则就将这个字符串添加到字符串池中,然会返回这个字符串的引用。

为什么会得到这样的一个结果呢?我们一步一步的分析。

  • 第一、str5.equals(str3)这个结果为true,不用太多的解释,因为字符串的值的内容相同。
  • 第二、str5 == str3对比的是引用的地址是否相同,由于str5采用new String方式定义的,所以地址引用一定不相等。所以结果为false。
  • 第三、当str5调用intern的时候,会检查字符串池中是否含有该字符串。由于之前定义的str3已经进入字符串池中,所以会得到相同的引用。
  • 第四,当str4 = str1 + str2后,str4的值也为”ab”,但是为什么这个结果会是false呢?先看下面代码:
String a =new String("ab");
String b =new String("ab");
String c ="ab";
String d ="a"+"b";
String e ="b";
String f ="a"+ e;

System.out.println(b.intern() == a);
System.out.println(b.intern() == c);
System.out.println(b.intern() == d);
System.out.println(b.intern() == f);
System.out.println(b.intern() == a.intern());

运行结果:

false
true
true
false
true

由运行结果可以看出来,b.intern() == a和b.intern() == c可知,采用new 创建的字符串对象不进入字符串池,并且通过b.intern() == d和b.intern() == f可知,字符串相加的时候,都是静态字符串的结果会添加到字符串池,如果其中含有变量(如f中的e)则不会进入字符串池中。但是字符串一旦进入字符串池中,就会先查找池中有无此对象。如果有此对象,则让对象引用指向此对象。如果无此对象,则先创建此对象,再让对象引用指向此对象。

​ 当研究到这个地方的时候,突然想起来经常遇到的一个比较经典的Java问题,就是对比equal和==的区别,当时记得老师只是说“==”判断的是“地址”,但是并没说清楚什么时候会有地址相等的情况。现在看来,在定义变量的时候赋值,如果赋值的是静态的字符串,就会执行进入字符串池的操作,如果池中含有该字符串,则返回引用。

执行下面的代码:

String a ="abc";
String b ="abc";
String c ="a"+"b"+"c";
String d ="a"+"bc";
String e ="ab"+"c";

System.out.println(a == b);
System.out.println(a == c);
System.out.println(a == d);
System.out.println(a == e);
System.out.println(c == d);
System.out.println(c == e);

运行的结果:

true
true
true
true
true
true

运行的结果刚好验证了我刚才的猜想。

创建方式

(1)String ok1=new String(“ok”);
(2)String ok2=“ok”;

我相信很多人都知道这两种方式定义字符串,但他们之间的差别又有多少人清楚呢。画出这两个字符串的内存示意图:

img

String ok1=new String(“ok”)。首先会在堆内存申请一块内存存储字符串ok,ok1指向其内存块对象。同时还会检查字符串常量池中是否含有ok字符串,若没有则添加ok到字符串常量池中。所以 new String()可能会创建两个对象.

​ String ok2=“ok”。 先检查字符串常量池中是否含有ok字符串,如果有则直接指向, 没有则在字符串常量池添加ok字符串并指向它.所以这种方法最多创建一个对象,有可能不创建对象

所以

   String ok2=“ok”;//没有创建对象

来两题习题看你是否真的明白了

1.下面代码创建了多少个对象?

 String temp="apple";  
 for(int i=0;i<1000;i++)  
 {  
       temp=temp+i;  
 }  

答案:1001个

2.下面代码创建了多少个对象?

String temp=new String("apple")  
for(int i=0;i<1000;i++)  
{  
       temp=temp+i;  
}  

答案:1002个

Java内存模型-常量池

img

运行时常量池(Runtime constant pool)

​ 位于方法区中,是每一个类或接口的常量池(Constant_Pool)的运行时表现形式,它包括了若干种常量:编译器可知的数值字面量到必须运行期解析后才能获得的方法或字段的引用。

简而言之,当一个方法或者变量被引用时,JVM通过运行时常量区来查找方法或者变量在内存里的实际地址。

在类和接口被加载到JVM后,对应的运行时常量池就被创建。

String中的字符串常量池就是jvm虚拟机内存中方法区内的运行时常量池
详情见

jvm内存模型

String连接案例解析

String常量
  • String常量的值是在常量池中的

JVM中的常量池在内存当中是以表(hashtable)的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。
常量池中保存着很多String对象,并且可以被共享使用,因此它提高了效率。

  • 在Java中,String对象是不可变的(Immutable)

在代码中,可以创建多个某一个String对象的别名,但是这些别名的引用是相同的。

String的连接
  • JAVA虚拟机处理String的连接符+(concatenation)时会有不同处理

如果都是字符常量,那么只会生成一个。
如果有变量,那么会调用StringBuilder,最后调用Sb的tostring。
连接符两边只要有一个不是字符串常量,那就是说明那个变量是个地址的引用,引用指向的值编译时无法知道的。

 String a = "123"       // (生成一个字符串常量)
 String a = "123" + b   //(调用StringBuilder)
 String a="a"+"b"+"c"   // 在内存中创建几个对象?

甲骨文jdk(1.7),javac会进行常量折叠,全字面量字符串相加是可以折叠为一个字面常量,而且是进入常量池的。
优化进行在编译器编译.java到bytecode时,通过编译器优化后,得到的效果是String a=”abc” 。此时,如果字符串常量池中存在abc,则该语句并不会创建对象,只是将字符串常量池中的引用返回而已。
字符串常量池存放的是对象引用,不是对象。在Java中,对象都创建在堆内存中。
如果字符串常量池中不存在abc,则会创建并放入字符串常量池,并返回引用,此时会有一个对象进行创建。

String.intern()

String对象的实例调用intern方法后,可以让JVM检查常量池,如果没有实例的value属性对应的字符串序列比如”123″(注意是检查字符串序列而不是检查实例本身),就将本实例放入常量池,如果有当前实例的value属性对应的字符串序列”123″在常量池中存在,则返回常量池中”123″对应的实例的引用而不是当前实例的引用,即使当前实例的value也是”123″。

优缺点

字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。
如果硬要说弊端的话,就是牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。

赞(1) 打赏
未经允许不得转载:刘鹏博客 » Java基础学习总结-String字符串常量池-intern()方法
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏