java string.intern,java字符串intern

  java string.intern,java字符串intern

  00-1010常量池简介实习方法简介(JDK7)原理(JDK6和JDK7)常规测试常规分析jdk1.6jdk1.7应用实例

  

目录

本文介绍了Java字符串的intern方法的原理。

 

  

简介

JAVA语言中有八种基本类型和一种特殊类型字符串。这些类型提供了一个常量池的概念(在方法区域),以使它们运行得更快并节省更多的内存。常量池类似于JAVA系统级提供的缓存。八种基本类型的常量池由系统协调,字符串型常量池比较特殊。

 

  使用字符串的常量池有两种主要方式:

  用双引号直接声明的字符串对象将直接存储在常量池中。

  如果不是用双引号声明的String对象,可以使用String提供的intern方法将其放入常量池。

  00-1010原型:public native String intern();

  描述:

  从字符串常量池中查询当前字符串是否存在(由equals判断)。

  如果存在,它将返回常量池中的字符串引用。如果不存在,就把这个String对象的引用存储在常量池中,然后返回这个String对象的引用。返回值:都是对字符串变量对应的字符串常量池的引用。

  例子

  包com.examplepublic class Demo { public static void main(String argv[]){ String s= test ;system . out . println(s==s . intern());}}JDK6及之前版本:输出false

  JDK7和更高版本:输出true

  

常量池简介

常量池里的字符串的由来

 

  JDK6和以前对String.intern()的调用

  如果常量池中有,则返回该字符串在常量池中的引用;如果常量池中没有人,则对象的副本会放入常量池中(永久生成);是对返回值常量池(永久生成)中相应字符串实例的引用。JDK7和更高版本调用String.intern()

  如果常量池中有,则返回该字符串在常量池中的引用;如果常量池中没有人,则制作引用的副本,放入常量池(堆)中;(JDK1.7将字符串常量池从Perm区移到Java堆区)

  00-1010例行程序1:

  包org . example . a;public class Demo { public static void main(String argv[]){ String S1=new String( 1 );S1 . intern();字符串S2=“1”;system . out . println(S1==S2);String s3=新字符串( 1 )新字符串( 1 );S3 . intern();字符串s4= 11system . out . println(S3==S4);}}个结果

  jdk6:假假jdk7:假真jdk8:假真

  套路二:

  包org . example . a;public class Demo { public static void main(String argv[]){ String S1=new String( 1 );S1 . intern();字符串S2=“1”;system . out . println(S1==S2);String s3=新字符串( 1 )新字符串( 1 );字符串s4= 11S3 . intern();system . out . println(S3==S4);}}上面代码的第二部分有一个开关。

  结果

  jdk6:假假jdk7:假假jdk8:假假

  ss="maodian">

  

例程分析

下边图中:绿色线条代表 String 对象的内容指向。 红色线条代表地址指向。

 

  

 

  

jdk1.6

例程1与例程2的分析

 

  

 

  如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。

  

 

  

jdk1.7

在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 会产生java.lang.OutOfMemoryError:PermGen space错误的。在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称jdk8已经直接取消了Perm区域,而新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在 JAVA的发展了。字符串常量池移动到JAVA Heap区域,现在解释为什么会有上述的打印结果。

 

  例程1的分析

  

 

  1.String s1 = new String("1");

  分析:这行代码生成了2个对象(常量池中的1 和 JavaHeap 中的字符串对象)。s.intern(); 这一句是 s1 对象去常量池中寻找后发现 1 已经在常量池里了。

  此时s1指向的是Java Heap中的字符串对象。

  2.String s2 = "1";

  分析:这行代码生成一个 s2的引用指向常量池中的1对象。 结果就是 s1 和 s2 的引用地址不同。

  3.String s3 = new String("1") + new String("1");

  分析:这行代码生成了2个对象(字符串常量池中的1 和 Java Heap中的 s3 引用指向的对象11(中间还有2个匿名的new String("1")我们不讨论它)。此时s3 是Java Heap中的字符串对象的引用,对象内容是11″,此时常量池中是没有 11对象的。

  4.s3.intern();

  分析:这行代码将 s3中的"11"字符串放入String 常量池中,因为此时常量池中不存在"11"字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个"11"的对象,关键点是 jdk7 中常量池不在Perm区域,而是在堆中了。常量池中不需再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。 也就是说引用地址是相同的。

  此时,s3是Java Heap中的字符串对象的引用,对象内容是11″,此时常量池中是有 11对象,它保存的就是s3引用地址。

  5.String s4 = "11";

  这行代码11″是显式声明的,因此会直接去常量池中创建,创建时发现已经有这个对象了。

  此时:s4 == 常量池的11对象引用 == s3引用对象的引用

  例程2的分析

  

 

  String s1 = new String("1");

  s1.intern();

  String s2 = "1";

  分析:s1.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成1对象了。下边的s2声明都是直接从常量池中取地址引用的。 s1 和 s2 的引用地址是不会相等的。

  String s3 = new String("1") + new String("1");

  分析:这行代码生成了2个对象(字符串常量池中的1 和 Java Heap中的 s3 引用指向的对象11(中间还有2个匿名的new String("1")我们不讨论它)。

  此时s3 是Java Heap中的字符串对象的引用,对象内容是11″,此时常量池中是没有 11对象的。

  String s4 = "11";

  分析:声明 s4 的时候常量池中是不存在11对象的,执行完后,s4是常量池里11对象的引用。

  s3.intern();

  分析:此时常量池中11对象已经存在了,不会有任何操作,s3仍然是堆中String对象的引用。因此 s3 != s4

  

 

  

应用实例

package org.example.a; import java.util.Random; public class Demo { static final int MAX = 1000 * 10000; static final String[] arr = new String[MAX]; public static void main(String argv[]) { Integer[] DB_DATA = new Integer[10]; Random random = new Random(10 * 10000); for(int i = 0; i < DB_DATA.length; i++){ DB_DATA[i] = random.nextInt(); } long t = System.currentTimeMillis(); for(int i = 0; i < MAX; i++){ //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])); arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern(); } System.out.println((System.currentTimeMillis() -t) + "ms"); System.gc(); }}

上述代码是一个演示代码,其中有两条语句不一样,一条是使用 intern,一条是未使用 intern。

 

  运行的参数是:-Xmx2g -Xms2g -Xmn1500M

  不用intern

  2160ms

  

 

  使用intern

  826ms

  

 

  通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。其实通过观察程序中只是用到了10个字符串,所以准确计算后应该是正好相差100w 倍。虽然例子有些极端,但确实能准确反应出 intern 使用后产生的巨大空间节省。

  细心的同学会发现使用了 intern 方法后时间上有了一些增长。这是因为程序中每次都是用了 new String 后, 然后又进行 intern 操作的耗时时间,这一点如果在内存空间充足的情况下确实是无法避免的,但我们平时使用时,内存空间肯定不是无限大的,不使用 intern占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。 毕竟这里使用了1000w次intern 才多出来1秒钟多的时间。

  到此这篇关于详解Java String中intern方法的原理与使用的文章就介绍到这了,更多相关Java String intern方法内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: