上一篇你真的会用String吗(3)-关于字符串拼接中我们提到了String.intern()方法,本篇我们就来详细的看下这个方法是干嘛的。首先来看下jdk8中这个方法的注释:

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.
All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of theThe Java™ Language Specification.

当调用intern()这个方法的时候,如果String常量池中没有这个String对象就把它放到常量池中,然后再返回它的引用,如果池中已经有这个String对象了,就直接把它返回。当且仅当两个String是equals的时候,他们的intern才会返回同一个引用。所有的String字面常量和String constant variable都是interned,也就是说都是在String常量池中的。

关于intern有个非常有意思的现象,对于同一段代码,在jdk6和jdk7以后输出结果却完全不一样,这个打破了java高版本兼容低版本的传统,看代码:

public static void main(String[] args) {
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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这段代码在jdk6下运行结果如下:

在jdk8下:

这个是由于jdk内存结构变化导致的,具体地说:
jdk6中,字符串常量池是存在于Perm区的,但是从jdk7开始,字符串常量池从Perm区移到了堆区,jdk8中则完全移除了Perm区 。对于String.intern来说,无论是jdk6还是jdk7,都是先去查看字符串常量池是否有该字符串,如果有,则返回字符串常量池中的引用。不同点在于如果是 jdk7,当字符串常量池中找不到对应的字符串时,不会将字符串拷贝到字符串常量池,而只是生成一个对该字符串的引用在字符串常量池,而 jdk6会拷贝字符串至字符串常量池。因此,从jdk7开始,常量池中的字符串分为两类,一类是本身就存在于池中的字符串,一类是本身存在于堆中但是引用存在于池中的字符串,而在jdk6和以前常量池和堆是完全分开的两个东西。

对于jdk6:

public static void main(String[] args) {
//s指向堆中的一个字符串,"1"是字面常量,是存在于Perm区的常量池中
String s = new String("1");
//s2指向的是常量池中的"1"
String s2 = "1";
//因为常量池中已经有"1",因此这个什么也不做
s.intern();
//s指向堆区,s2指向Perm区的常量池,所以是false
System.out.println(s == s2);
//"1"已经存在于常量池
//s3指向堆中的一个串,内容是“11”,但是,此时常量池中并没有“11”
String s3 = new String("1") + new String("1");
//此时把s3代表额字符串“11”加入到了Perm区的常量池中
s3.intern();
//s4指向Perm区常量池中的“11”
String s4 = "11";
//s3指向堆区的一个字符串,s4指向Perm区常量池中字符串,所以false
System.out.println(s3 == s4);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

对于jdk7来说:


public static void main(String[] args) {
//s指向堆区的一个串,“1”位于堆区的常量池中
String s = new String("1");
//s2引用常量池中的“1”
String s2 = "1";
//常量池重已经有“1”,什么也不做
s.intern();
//s指向堆,s2引用常量池,false
System.out.println(s == s2); //s3指向堆中的一个字符串
String s3 = new String("1") + new String("1");
//把s3这个字符串的引用加入到了常量池,此时常量池中有了“11”这个串的引用
s3.intern();
//s4引用的常量池中的“11”,也就是s3
String s4 = "11";
//s3和s4都是引用常量池中字符串,true
System.out.println(s3 == s4);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

如果把代码稍微改一下,jdk7中:

public static void main(String[] args) {
//s3指向堆中的字符串
String s3 = new String("1") + new String("1");
//s4引用常量池中的字符串
String s4 = "11";
//池中已经有“11”,什么也不做
s3.intern();
//显然是false
System.out.println(s3 == s4);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

因为String.intern会引用常量池中已经存在的字符串,可以减少内存中相同字符串的数量,节省一些内存空间,为了提高效率,是不是我们应该把所有的字符串都调用intern()加入到池中呢?显然是不是的。
String常量池底层的数据结构类似于HashMap,jdk6中池的大小是固定的1009,如果池中的字符串太多就会造成hash冲突严重,会严重影响字符串查找的效率。从jdk7开始,池的大小可以手动设置StringTableSize这个参数来指定。jdk8中,池的大小默认是60013.

C:\Users\xujs>java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
C:\Users\xujs>java -XX:+PrintFlagsInitial | findstr StringTableSize
uintx StringTableSize = 60013
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

当然,如果能确定系统中对某些串的读取非常频繁,而且这些串的数量也不会很多,那也是可以把他们加入到池中的。参考:
https://tech.meituan.com/in_depth_understanding_string_intern.html

此外,jdk8的G1收集器还添加了一个很有用的选项 -XX:+UseStringDeduplication可以对String进行去重,也可以节省一部分内存空间,参考:
http://openjdk.java.net/jeps/192

String中的intern方法的更多相关文章

  1. 详解String类中的intern()方法

    我们用一个经典的例子来理解 package com.jvm.heap; public class MyTest { public static void main(String[] args) { S ...

  2. JVM体系结构之七:持久代、元空间(Metaspace) 常量池==了解String类的intern()方法、常量池介绍、常量池从Perm-->Heap

    一.intern()定义及使用 相信绝大多数的人不会去用String类的intern方法,打开String类的源码发现这是一个本地方法,定义如下: public native String inter ...

  3. String类的intern()方法,随常量池发生的变化

    JVM的知识这里总结的很详细:https://github.com/doocs/jvm/blob/master/README.md,因此在本博客也不会再对其中的东西重复总结了. intern的作用 简 ...

  4. String中的“equal方法”和“==”

    二话不说,先来说下重写的事情: 在Java中,String .Math.还有Integer.Double....等这些封装类重写了Object中的equals()方法,让它不再比较其对象在内存中的地址 ...

  5. Java String类中的intern()方法

    今天在看一本书的时候注意到一个String的intern()方法,平常没用过,只是见过这个方法,也没去仔细看过这个方法.所以今天看了一下.个人觉得给String类中加入这个方法可能是为了提升一点点性能 ...

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

    一般我们变成很少使用到 intern这个方法,今天我就来解释一下这个方法是干什么的,做什么用的 首先请大家看一个例子: public static void main(String[] args) t ...

  7. Java String类的intern()方法

    该方法的作用是把字符串加载到常量池中(jdk1.6常量池位于方法区,jdk1.7以后常量池位于堆) 在jdk1.6中,该方法把字符串的值复制到常量区,然后返回常量区里这个字符串的值: 在jdk1.7里 ...

  8. Java技术——你真的了解String类的intern()方法吗

    0.引言 什么都先不说,先看下面这个引入的例子:   String str1 = new String("SEU")+ new String("Calvin") ...

  9. String类的intern()方法

    0.引言 什么都先不说,先看下面这个引入的例子: String str1 = new String("SEU")+ new String("Calvin"); ...

随机推荐

  1. python定位隐藏元素

    定位隐藏要素的原理: 页面主要通过“display:none”来控制元素不可见.所以我们需要通过javaScript修改display的值得值为display="block,来实现元素定位的 ...

  2. OpenStack组件——Nova计算资源管理

    1.nova介绍 Nova 是 OpenStack 最核心的服务,负责维护和管理云环境的计算资源.OpenStack 作为 IaaS 的云操作系统,虚拟机生命周期管理也就是通过 Nova 来实现的. ...

  3. 【JulyEdu-Python基础】第 6 课:高级面向对象

    使用@property添加属性和自定义属性 __slots__和property 方法和属性的动态绑定 使用__slots__限定class实例能添加的属性 __slots__仅对当前类实例起作用,对 ...

  4. 更改默认浏览器(Windows7)

    更改默认浏览器 第一个方法(最好用): 第一步,先点击左下角WIN 然后点默认程序(画框框的图上) 第二步骤,来到控制面板主页,点击设置程序 第三步骤,左键单击选择要设置为默认浏览器的程序(我用360 ...

  5. VirtualBox下Centos6.8网络配置

    win10环境下,VirtualBox和Centos6.8已经按照完毕,下面配置Centos6.8网络. 1.设置VirtualBox为桥接模式,具体的有三种联网方法,我们参考http://www.c ...

  6. the property “***” on could not be set to a null value

    在建立EF框架的时候,创建实体时由于部分数据库类型和.net类型不同,比如int类型,在数据库中是可空类型,而.net中是不允许的,所以创建实体的时候,数据库的int类型对应的实体类型应该为int?类 ...

  7. PHP Smarty模板的安装

    最近开发中用到了PHP中smarty模板..作为一个长久以来的前端,开始学习PHP模板..下面将安装教程分享给大家.. 1. 下载Smarty最新版: http://www.smarty.NET/do ...

  8. mysql事件(event)

    [小结]简单案例 SET GLOBAL event_scheduler=1delimiter $$ create definer = current_user event `test`.`event_ ...

  9. CF387B 【George and Round】

    暴力还真的出奇迹了这题窝将读入的两个数组都先排个序,然后再枚举一遍就过了: 目前题解最短的代码QwQ.这里是代码 #include<bits/stdc++.h>using namespac ...

  10. NOIP 2017 逛公园 题解

    题面 这道题是一道不错的计数类DP: 首先我们一定要跑一遍dijkstra来求得每个点到1号点的最短路: 注意题干,题中并没有说所有点都可以到达n好点,只说了存在一条1号点到n号点的路径:所以我们在反 ...