引言

  什么都先不说,先看下面这个引入的例子:

public static void test4(){
String str1 = new String("SEU") + new String("Calvin");
System.out.println(str1.intern() == str1);
System.out.println(str1 == "SEUCalvin");
}

  再将上面的例子加上一行代码:

public static void test5(){
String str2 = "SEUCalvin";
String str1 = new String("SEU") + new String("Calvin");
System.out.println(str1.intern() == str1);
System.out.println(str1 == "SEUCalvin");
}

  是不是感觉莫名其妙,新定义的str2好像和str1没有半毛钱的关系,怎么会影响到有关str1的输出结果呢?其实这都是intern()方法搞的鬼!看完这篇文章,你就会明白。

  这中间的原因和Hotspot是有很大关系的,在JVM运行时数据区中的方法区有一个常量池,但是发现在JDK1.6以后常量池被放置在了堆空间,因此常量池位置的不同影响到了String的intern()方法的表现。

1.为什么要介绍intern()方法

  intern()方法设计的初衷,就是重(chong)用String对象,以节省内存消耗。这么说可能有点抽象,那么就用例子来证明。

package cn.stringPractise.create;
static final int MAX = 100000;
static final String[] arr = new String[MAX]; public static void main(String[] args) throws Exception {
//为长度为10的Integer数组随机赋值
Integer[] sample = new Integer[10];
Random random = new Random(1000);
for (int i = 0; i < sample.length; i++) {
sample[i] = random.nextInt();
}
//记录程序开始时间
long t = System.currentTimeMillis();
//使用/不使用intern方法为10万个String赋值,值来自于Integer数组的10个数
for (int i = 0; i < MAX; i++) {
arr[i] = new String(String.valueOf(sample[i % sample.length]));
//arr[i] = new String(String.valueOf(sample[i % sample.length])).intern(); 通过intern重复利用已经存在的字符串对象,这样垃圾回收器能够回收更多没有指向的对象
}
System.out.println((System.currentTimeMillis() - t) + "ms");
System.gc();
}

  这个例子也比较简单,就是为了证明使用intern()比不使用intern()消耗的内存更少。

  先定义一个长度为10的Integer数组,并随机为其赋值,在通过for循环为长度为10万的String对象依次赋值,这些值都来自于Integer数组。两种情况分别运行,可通过Window ---> Preferences --> Java --> Installed JREs设置JVM启动参数为-agentlib:hprof=heap=dump,format=b,将程序运行完后的hprof置于工程目录下。再通过MAT插件查看该hprof文件。
两次实验结果如下:

  从运行结果来看,不使用intern()的情况下,程序生成了101924个String对象,而使用了intern()方法时,程序仅生成了1934个String对象(多次测试结果可能不同)。自然也证明了intern()节省内存的结论。细心的同学会发现使用了intern()方法后程序运行时间有所增加。这是因为程序中每次都是用了new String后又进行intern()操作的耗时时间,但是不使用intern()占用内存空间导致GC的时间是要远远大于这点时间的。

2. 深入认识intern()方法

  查看API对于intern()的解释如下:

  String  intern()  返回字符串对象的规范表示。【具体是什么意思,我们将在一系列的例子后总结说明,有助于理解】

  JDK1.7后,常量池被放入到堆空间中,这导致intern()函数的功能不同,具体怎么个不同法,且看看下面代码,这个例子是网上流传较广的一个例子,分析图也是直接粘贴过来的,这里我会用自己的理解去解释这个例子:

String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
  1. JDK1.6以及以下:false false
  2. JDK1.7以及以上:false true

再分别调整上面代码2.3行、7.8行的顺序:

1.String s = new String("1");  
2.String s2 = "1";  
3.s.intern();  
4.System.out.println(s == s2);  
5.  
6.String s3 = new String("1") + new String("1");  
7.String s4 = "11";  
8.s3.intern();  
9.System.out.println(s3 == s4);  

输出结果为:

  1.JDK1.6以及以下:false false

  2.JDK1.7以及以上:false false

下面依据上面代码对intern()方法进行分析:

1.2.1 JDK1.6

  在JDK1.6中所有的输出结果都是 false,因为JDK1.6以及以前版本中,常量池是放在 Perm 区(方法区,也叫永久代)中的,熟悉JVM的话应该知道这是和堆区完全分开的。使用引号声明的字符串都是会直接在字符串常量池中生成的,而 new 出来的 String 对象是放在堆空间中的。所以两者的内存地址肯定是不相同的,即使调用了intern()方法也是不影响的。intern()方法在JDK1.6中的作用是:比如String s = new String("SEU_Calvin"),再调用s.intern(),此时返回值还是字符串"SEU_Calvin",表面上看起来好像这个方法没什么用处。但实际上,在JDK1.6中它做了个小动作:检查字符串池里是否存在"SEU_Calvin"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把"SEU_Calvin"添加到字符串池中,然后再返回它的引用。然而在JDK1.7中却不是这样的,后面会讨论。

1.2.2 JDK1.7

  针对JDK1.7以及以上的版本,我们将上面两段代码分开讨论。先看第一段代码的情况:

再把第一段代码贴一下便于查看:

    String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
    1. JDK1.6以及以下:false false
    2. JDK1.7以及以上:false true
  1. String s = newString("1"),生成了常量池中的“1” 和堆空间中的字符串对象。
  2. s.intern(),这一行的作用是s对象去常量池中寻找后发现"1"已经存在于常量池中了。
  3. String s2 = "1",这行代码是生成一个s2的引用指向常量池中的“1”对象。
  4. 结果就是 s 和 s2 的引用地址明显不同。因此返回了false。
  5. String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1”,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
  6. s3.intern(),这一行代码,是将 s3中的“11”字符串放入 String 常量池中,此时常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一个 "11" 的对象。
  7. 但是在JDK1.7中,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用直接指向 s3 引用的对象,也就是说s3.intern() ==s3会返回true。
  8. String s4 = "11", 这一行代码会直接去常量池中创建,但是发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。因此s3 == s4

再把第二段代码贴一下便于查看:

String s = new String("1");  

String s2 = "1";  

s.intern();  

System.out.println(s == s2);  

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

String s4 = "11";  

s3.intern();  

System.out.println(s3 == s4); 

  1.JDK1.6以及以下:false false

  2.JDK1.7以及以上:false false

  1. String s = newString("1"),生成了常量池中的“1” 和堆空间中的字符串对象。
  2. String s2 = "1",这行代码是生成一个s2的引用指向常量池中的“1”对象,但是发现已经存在了,那么就直接指向了它。
  3. s.intern(),这一行在这里就没什么实际作用了。因为"1"已经存在了。
  4. 结果就是 s 和 s2 的引用地址明显不同。因此返回了false。
  5. String s3 = new String("1") + newString("1"),这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11",实际上s3指向的是一个字符串对象,该对象中是两个个字符串变量连接运算)。注意此时常量池中是没有 “11”对象的。
  6. String s4 = "11", 这一行代码会直接去生成常量池中的"11"。
  7. s3.intern(),这一行在这里就没什么实际作用了。因为"11"已经存在了。
  8. 结果就是 s3 和 s4 的引用地址明显不同。因此返回了false。

  本文转载,原文链接为:SEU_Calvin的博客

1.3 总结

终于要做Ending了。现在再来看一下开篇给的引入例子,是不是就很清晰了呢。

    String str1 = new String("SEU") + new String("Calvin");
System.out.println(str1.intern() == str1); //true
System.out.println(str1 == "SEUCalvin"); //true

  str1.intern() == str1就是上面例子中的情况,str1.intern()发现常量池中不存在“SEUCalvin”,因此指向了str1。 "SEUCalvin"在常量池中创建时,也就直接指向了str1了。两个都返回true就理所当然啦。

那么第二段代码呢:

    String str2 = "SEUCalvin";//新加的一行代码,其余不变
String str1 = new String("SEU")+ new String("Calvin");
System.out.println(str1.intern() == str1); //false
System.out.println(str1 == "SEUCalvin"); //false

  也很简单啦,str2先在常量池中创建了“SEUCalvin”,那么str1.intern()当然就直接指向了str2,你可以去验证它们两个是返回的true。后面的"SEUCalvin"也一样指向str2。所以谁都不搭理在堆空间中的str1了,所以都返回了false。

  小结:

  intern()方法在JDK1.6中的作用是:比如String s = new String("SEU_Calvin"),再调用s.intern(),此时返回值还是字符串"SEU_Calvin",表面上看起来好像这个方法没什么用处。但实际上,在JDK1.6中它做了个小动作:检查字符串池里是否存在"SEU_Calvin"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把"SEU_Calvin"添加到字符串池中,然后再返回它的引用。

  JDK1.7中,string.Intern()会先检查在字符串常量池中有没有这个字符串,如果没有,那么在字符串常量池中村存入一个指针指向堆中的String对象,如果字符串常量池中有这个字符串,那么会返回字符串常量池中这个对象地址.

  1. String str1 = new String("SEU") + new String("Calvin");

  语句在内存中是什么样的呢?因为String是不可改变的,所以Str1相当于创建了5个对象,两个new,两个在字符串常量池,一个是str1的指向,这个指向的内容并不是SEUCalvin,而是一个计算new String("SEU") + new String("Calvin")的计算,也就是会有这两个new对象的地址,还有运算法则,所以System.out.println(str1.intern() == str1);为true,如果是下面情况:

  String str1 = new String("SEUCalvin");

  System.out.println(str1.intern() == str1);

那么结果就是false,因为第一句会在字符串常量池中创建SEUCalvin字符串对象,str1.intern()后地址就变成了字符串常量池中的地址,两个地址这时变不一样了.

  使用String.intern()能够重复使用在内存中已经存在的相同的字符串对象,这样避免了相同对象分配内存造成的内存的浪费,又因为String是不可变的,所以在两个索引指向同一个String对象时,这时如果另一个对象改变String,实际上是重新创建了一个String对象,也不会因为一个索引的操作修改而影响另一个索引指向的String发生改变;

Knowledge Point 20180309 字符串常量池与String,intern()的更多相关文章

  1. 字符串常量池和String.intern()方法在jdk1.6、1.7、1.8中的变化

    字符串常量池也是运行时常量池 jdk1.6中,它是在方法区中,属于“永久代” jdk1.7中,它被移除方法区,放在java堆中 jdk1.8中,取消了“永久代”,将常量池放在元空间,与堆独立了 pub ...

  2. Java String类相关知识梳理(含字符串常量池(String Pool)知识)

    目录 1. String类是什么 1.1 定义 1.2 类结构 1.3 所在的包 2. String类的底层数据结构 3. 关于 intern() 方法(重点) 3.1 作用 3.2 字符串常量池(S ...

  3. 常量池之String.intern()方法

    JDK7将String常量池从Perm区移动到了Java Heap区.在JDK1.6中,intern方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中的实例.但是在JDK1.7以后,Str ...

  4. 关于jvm中的常量池和String.intern()理解

    1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性. 2. ne ...

  5. String:字符串常量池

    String:字符串常量池 作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 字 ...

  6. 对于JVM中方法区,永久代,元空间以及字符串常量池的迁移和string.intern方法

    在Java虚拟机(以下简称JVM)中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表. 在过去(当自定义类加载器使用不普 ...

  7. [JAVA]字符串常量池String pool

    字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定.不仅如此,还可以使用 String 的 intern() 方法在运行过程中将 ...

  8. Java String:字符串常量池(转)

    作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么? 字符串常量池的设计思想是什么? 字符串常量池在哪里? 如何操作字符串常量 ...

  9. Java中的字符串常量池

    ava中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new ...

随机推荐

  1. python全栈开发之路

    一.Python基础 python简介 python数据类型(数字\字符串\列表) python数据类型(元组\字典) python数据类型(集合) python占位符%s,%d,%r,%f prin ...

  2. Cookie实现留言板

    Cookie实现留言板 直接代码: <!DOCTYPE html> <html> <head> <meta http-equiv="Content- ...

  3. 浅谈JavaScript之function用括号包起来

    (function a(){}) (命名函数表达式)会返回这个函数(不会执行),但是在括号外面无法调用该函数,需要一个变量接收这个函数,var fun = (function a(){}),一般这个用 ...

  4. WINDOWS API ——CREATETOOLHELP32SNAPSHOT——查找进程

    原文:http://www.cnblogs.com/wind-net/archive/2012/10/26/2741458.html //根据进程名获取进程ID DWORD GetPidByProce ...

  5. 使用SDL2出现 “error LNK2019: 无法解析的外部符号 _SDL_main,该符号在函数 _main 中被引用” 时的可能错误记录

    这几天在使用SDL2,之前一直都没有错,直到上午把项目搬了个地方.结果一直出现 “error LNK2019: 无法解析的外部符号 _SDL_main,该符号在函数 _main 中被引用” . 看了网 ...

  6. SQL计算上下两行某列的差

    SELECT * FROM #TempHuDong SELECT * FROM #TempHuDong SELECT TOP 1 ABS(a.num -b.num) '差' FROM (select ...

  7. June 25th 2017 Week 26th Sunday

    There is always that one song that brings back old memories. 总有那么一首歌,让你想起那些旧的回忆. There are seveal so ...

  8. Android webview 点击超链接打开新的webview

    webview.setWebViewClient(new webViewClient() { HitTestResult hit = view.getHitTestResult(); if (hit ...

  9. mongoDB 固定集合(capped collection)

    固定集合(Capped Collection)是一种尺寸固定的“循环”集合,可提供高效的创建.读取.删除等操作.这里所指的“循环”的意思是,当分配给集合的文件尺寸耗尽时,就会自动开始删除最初的文档,不 ...

  10. AngularJs学习笔记--Scope

    原版地址:http://code.angularjs.org/1.0.2/docs/guide/scope 一.什么是Scope? scope(http://code.angularjs.org/1. ...