原文网址:Java之String系列--intern方法的作用及原理_IT利刃出鞘的博客-CSDN博客

简介

本文介绍Java的String的intern方法的原理。

常量池简介

在 JAVA 语言中有8种基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池(在方法区)的概念。常量池就类似一个JAVA系统级别提供的缓存。

8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。String的常量池的主要使用方法有两种:

  1. 直接使用双引号声明出来的String对象会直接存储在常量池中。
  2. 如果不是用双引号声明的String对象,可以使用String提供的intern方法将其放到常量池。

intern方法简介(JDK7之后)

原型

public native String intern();

说明:

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

    • 如果存在,返回常量池中的字符串引用。
    • 如果不存在,把这个String对象引用存到常量池,然后返回这个String对象的引用。

返回值:都是返回String变量对应的常量池中字符串的引用。

以上内容可以从intern的注释中得到:

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {// 其他代码/*** Returns a canonical representation for the string object.* <p>* A pool of strings, initially empty, is maintained privately by the* class {@code String}.* <p>* When the intern method is invoked, if the pool already contains a* string equal to this {@code String} object as determined by* the {@link #equals(Object)} method, then the string from the pool is* returned. Otherwise, this {@code String} object is added to the* pool and a reference to this {@code String} object is returned.* <p>* It follows that for any two strings {@code s} and {@code t},* {@code s.intern() == t.intern()} is {@code true}* if and only if {@code s.equals(t)} is {@code true}.* <p>* All literal strings and string-valued constant expressions are* interned. String literals are defined in section 3.10.5 of the* <cite>The Java&trade; Language Specification</cite>.** @return  a string that has the same contents as this string, but is*          guaranteed to be from a pool of unique strings.*/public native String intern();
}

原理(JDK6与JDK7之后)

常量池里的字符串的由来

  • JDK6及以前调用String.intern()

    • 若常量池中有,则返回常量池中这个字符串的引用
    • 若常量池中没有,则拷贝一份对象,放到常量池(永久代)中;返回值是常量池(永久代)中对应字符串实例的引用。
  • JDK7及以后调用String.intern()
    • 若常量池中有,则返回常量池中这个字符串的引用
    • 若常量池中没有,则拷贝一份引用,放到常量池(堆)中;(JDK1.7将String常量池从Perm区移动到了Java Heap区)

实例分析

代码测试

例程1: 

package com.example.a;public class Demo {public static void main(String argv[]) {String s1 = new String("1");s1.intern();String s2 = "1";System.out.println(s1 == s2);String s3 = new String("1") + new String("1");s3.intern();String s4 = "11";System.out.println(s3 == s4);}
}

结果

jdk6:false false
jdk7:false true
jdk8:false true

例程2

package com.example.a;public class Demo {public static void main(String argv[]) {String s1 = new String("1");s1.intern();String s2 = "1";System.out.println(s1 == s2);String s3 = new String("1") + new String("1");String s4 = "11";s3.intern();System.out.println(s3 == s4);}
}

上述代码第二部分有个对调。

结果

jdk6:false false
jdk7:false false
jdk8:false false

例程分析

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

JDK1.6(例程1与例程2)

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

JDK1.7(例程1的分析)

在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 会产生java.lang.OutOfMemoryError:PermGen space错误的。

在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称jdk8已经直接取消了Perm区域,新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在 JAVA的发展了。字符串常量池移动到JAVA Heap区域,现在解释为什么会有上述的打印结果。

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

    • 此时s1指向的是Java Heap中的字符串对象。
  • String s2 = "1"; 
    分析:这行代码生成一个 s2的引用指向常量池中的“1”对象。 结果就是 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”对象的。
  • s3.intern();
    分析:这行代码将 s3中的"11"字符串放入String 常量池中,因为此时常量池中不存在"11"字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个"11"的对象,关键点是 jdk7 中常量池不在Perm区域,而是在堆中了。常量池中不需再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。 也就是说引用地址是相同的。

    • 此时,s3是Java Heap中的字符串对象的引用,对象内容是”11″,此时常量池中是有 “11”对象,它保存的就是s3引用地址。
  • String s4 = "11"; 
    这行代码”11″是显式声明的,因此会直接去常量池中创建,创建时发现已经有这个对象了。

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

JDK1.7(例程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 方法时间上也稍长。

Java之String系列--intern方法的作用及原理相关推荐

  1. String中intern方法的作用

    前言 读完这篇文章你可以了解,String对象在虚拟机内存中的存放,intern的作用,这么多String对象的创建到底有什么区别,String 创建的对象有几个!! 进入正题 先科普几个知识点 1. ...

  2. java intern_java String的intern方法

    我们知道再jvm的运行时内存可以分为堆.方法区.程序计数器.虚拟机栈和本地方法栈.而在方法区中有一个字符串常量池,用来保存字符串这个不可变量.如果我们使用String str=new String(& ...

  3. Java String的intern方法

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

  4. [转]String 之 new String()和 intern()方法深入分析

    引入 String,是 Java 中除了基本数据类型以外,最为重要的一个类型了.很多人会认为他比较简单.但是和 String 有关的面试题有很多,下面我随便找两道面试题,看看你能不能都答对: Q1:S ...

  5. String中intern()方法

    本文参考 https://blog.csdn.net/believesoul/article/details/79588305 ,我对这篇文章进行了一些小小的整理和修改.这篇文章很好的对intern( ...

  6. 对String中intern()方法的认识

    最近在看字符串相关知识的时候,又看到了关于字符串创建方式和是否相等的相关问题,回忆起之前碰到的一个String中的intern()方法,它是一个本地方法(由Java语言外的语言编写),因此在jdk1. ...

  7. String的intern方法演示及各种字符串的拼接对比

    演示String的intern方法,以及各种拼接字符串的区别 package string;/*** @Author gzx* @create 2022-1-14 jdk8*/ public clas ...

  8. JAVA中String的split方法

    我的个人网站: http://riun.xyz 以下源码版本:JDK1.8 简介 Java 中 String 的 split 方法可以将字符串根据指定的间隔进行切割,例如字符串 str = " ...

  9. JAVA中String类的intern()方法的作用

    2019独角兽企业重金招聘Python工程师标准>>> 一般我们变成很少使用到 intern这个方法,今天我就来解释一下这个方法是干什么的,做什么用的 首先请大家看一个例子: [ja ...

最新文章

  1. 多任务学习(Multi-Task Learning, MTL)、其他分类形式、与迁移学习的关系
  2. 修改项目名称后,部署到tomcat问题
  3. Linux 内核抓包功能实现基础(四) 手动查找邻居缓存填充MAC地址
  4. spring aop 如何切面到mvc 的controller--转载
  5. SQL增删改查,基础
  6. 使用XML管理模版資源
  7. MySQL数据库的权限表
  8. 【C】printf按8进制、10进制、16进制输出以及高位补0
  9. springboot项目接入配置中心,实现@ConfigurationProperties的bean属性刷新方案
  10. lisp读取天正轴号_第2天:Python 基础语法
  11. 关于MySQL分表操作的研究
  12. 人事档案管理系统介绍(二)
  13. 3Done第七课——马克杯设计
  14. 常用原型图设计工具 Axure RP 等等
  15. 拼多多的正品险是个假保险?
  16. 深圳绿色建筑人才需求持续增长
  17. 【QT】linux下alsa库的移植和QT中音视频的处理笔记
  18. CDH集群中HDFS单点故障解决方案:HA模式(High Availability)
  19. 按文件名批量分类文件到文件夹
  20. Matlab学习3-图像处理之镜像、错切、透视

热门文章

  1. wallpaper怎么导入视频_怎样制作Wallpaper Engine视频壁纸 制作视频壁纸方法图文教程...
  2. 视频教程-【跟一夫学设计】从0基础到精通学全套coreldraw x7轻松掌握CDR基础加案例学习视频教程-CorelDraw
  3. 参赛邀请 | 第二届古汉语自动分析国际评测EvaHan(古汉语机器翻译)开始报名...
  4. pd调节规律_一文看懂pd控制器的参数整定
  5. python获取word页数_用程序获取word页码方法汇总
  6. 7-6,输入厘米,输出英尺英寸
  7. Android 读取短信内容(模拟器)
  8. 在线安装rancher2.4管理K8S集群并部署服务
  9. 国家专精特新小巨人申报条件及培育措施
  10. 测试分析——熟悉被测软件