探秘inter()方法
最近在阅读《深入理解Jav虚拟机》的运行时常量池章节,看到“java语言并不要求常量池一定只有编译器才能产生...运行期间也可以将新的常量放入常量池,这种特性被开发人员利用得比较多的时String类的intern()方法。"于是我便去深入了解了一下。
1 public static void main(String[] args) {
2 String a="王者";
3 String b="荣耀";
4 String c=a+b;
5 }
上述代码中,我通过javap命令查看编译期的常量池,发现只有字符串"王者"和"荣耀",并无”王者荣耀“,通过反编译得到的String c=a+b变成了,原来+号会被转为StringBuilder,并且拼接,注意当String c="王者"+"荣耀"时,编译器则会优化,常量池里只有王者荣耀,并无“王者”和“”
1 String c= (new StringBuilder()).append(a).append(b).toString()
这就很有意思了,也就是说编译时常量池中并无"王者荣耀"了,因为编译期不会运算,只有在运行期才能执行方法得到结果。

由上图可知,"王者荣耀"直接指向堆,并且运行时常量池无"王者荣耀"
1 public static void main(String[] args) {
2 String a="王者";
3 String b="荣耀";
4 String c=a+b;
5 String d=c.intern();
6 System.out.println(d==c);
7 }
上面为什么是true呢,接下来讲述inter()

JDK1.7后,intern方法先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,当字符串常量池中找不到对应的字符串时,而只是生成一个对该字符串的引用在字符串常量池。所以”王者荣耀”本身不会在字符串常量池,而常量池里面保存了在堆中王者荣耀的索引,所以为true。JDK1.7之前这里就不说了。
前面说了intern可以在程序运行时将新的常量放入运行时常量池,接下来就开始演示intern的强大用处
public static void main(String[] args) throws Exception {
long start=System.currentTimeMillis(); //获取开始时间
String[] arr = new String[10000000];
Integer[] a= new Integer[10000000];
for (int i = 0; i < 10000000; i++) {
a[i] = (int)(1+Math.random()*(3));
}
for (int i = 0; i < 10000000; i++) {
arr[i] = new String(String.valueOf(a[i]));
}
long end=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(end-start)+"ms");
}
//程序运行时间: 14461ms
public static void main(String[] args) throws Exception {
long start=System.currentTimeMillis(); //获取开始时间
String[] arr = new String[10000000];
Integer[] a= new Integer[10000000];
for (int i = 0; i < 10000000; i++) {
a[i] = (int)(1+Math.random()*(3));
}
for (int i = 0; i < 10000000; i++) {
arr[i] = new String(String.valueOf(a[i])).intern();
}
long end=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(end-start)+"ms");
}
//程序运行时间: 1688ms
很简单的小程序,就是有无调用intern方法,但程序运行时间差了10倍!
我们明确的知道,会有很多重复的相同的字符串产生,但是这些字符串的值都是只有在运行期才能确定的。所以,我们通过intern显示的将其加入常量池,这样可以减少很多字符串的重复创建。
new String("王者荣耀")和new String("王者")+new String("荣耀")不管是在编译期就确定放入常量池还是在运行期,都还是要在堆里面创建对象的。不同就是new String("王者荣耀")还要指向常量池。new String("王者")+new String("荣耀")不需要指向常量池
new String.intern中。这个过程是不会在Java堆中再创建一个String对象的。
注意,是因为随机数是运行期才知道的,intern把最先出现的,”1“,“2”,”3“,对象索引放入了常量池,下次在看见”1“,“2”,”3“,返回第一次在堆中String的对象地址,不用创建新对象,所以intern高效,使用intern()在堆中只会创建3个对象,而不使用intern则创建100000000个对象,并且常量池里并没有”1“,“2”,”3“
小问题,arri[]=new String("王者荣耀").intern()没什么意义的,虽然这样写比arri[]=new String("王者荣耀")效率高,可用代码验证,arri[]=new String("王者荣耀").intern()编译时常量池就出现“王者荣耀”,因为intern不会产生对象所以快,这里是直接引用了常量池中的字符串“王者荣耀”。但intern()并不是这样的初衷,而是为了解决运行时才出现的常量,不是解决编译时在字符串常量池的问题,这里比较抽象,多理解一下
笔者技术有限,如有错误,请指正,谢谢
探秘inter()方法的更多相关文章
- .NET中那些所谓的新语法之二:匿名类、匿名方法与扩展方法
开篇:在上一篇中,我们了解了自动属性.隐式类型.自动初始化器等所谓的新语法,这一篇我们继续征程,看看匿名类.匿名方法以及常用的扩展方法.虽然,都是很常见的东西,但是未必我们都明白其中蕴含的奥妙.所以, ...
- 【python学习笔记】9.魔法方法、属性和迭代器
[python学习笔记]9.魔法方法.属性和迭代器 魔法方法:xx, 收尾各有两个下划线的方法 __init__(self): 构造方法,创建对象时候自动执行,可以为其增加参数, 父类构造方法不会被自 ...
- 《深入理解Java虚拟机》Java内存区域与内存溢出异常
注:“蓝色加粗字体”为书本原语 先来一张JVM运行时数据区域图,再接下来一一分析各区域功能: 程序计数器 程序计数器(program Counter Register)是一块较小的内存空间,它可以 ...
- Java简明教程
Java与C++比较概况 C++ Java class Foo { // 声明 Foo 类 public: int x; // 成员变量 Foo(): x() { // Foo 的构造函数Constr ...
- 1.Java内存区域
Java虚拟机在执行java程序的过程中会把他管理的内存划分为若干个不同的数据区域各自用途.创建以及销毁时间各不相同.有的随着虚拟机进行的启动而存在,有的区域依赖于线程的启动和结束而建立以及销毁.如图 ...
- Java和C#中String直接赋值与使用new创建(==与equals进行比较)的区别
在Java中,字符串可以直接赋值或者使用new来新建,直接赋值的话是编译阶段(.class文件)中就将该字符串值放到常量池中,以后如果有其他变量直接赋予同样的值的话就不再分配内存空间,而是直接给它个引 ...
- javascript扩充基本类型的功能
可以通过给Function.prototype增加方法来使得该方法对所有函数可用. 通过给Function.prototype增加一个method方法,下次给对象增加方法的时候就不必键入prototy ...
- JAVA中==与equals的区别
equals如果没有被重写的话,和==的作用是一样的,都是判断两个对象引用是否指向同一个地址.一般重写了equals()方法就表示比较它们“实际意义上相等”,比较的是内容,而不是引用地址.Java中S ...
- [CLR via C#]异常和状态管理
当CLR检测到某个正在运行的.NET应用程序处于一种特殊的正常执行顺序被打断的状态时,会生成一个异常对象来表示这个错误,并将此对象在方法调用堆栈中向上传送.如果一个程序引发了一个异常却没有处理,CLR ...
随机推荐
- gopher协议在SSRF漏洞中的作用
1.什么是gopher协议?2.如何使用gopher协议反弹shell?3.在SSRF中如何使用gopher协议反弹shell? 一.什么是gopher协议?定义:Gopher是Internet上一个 ...
- 【Java常用类】Math
Math 说明 java.lang.Math提供了一系列静态方法用于科学计算.其方法的参数和返回 值类型一般为double型. 方法 abs 绝对值 acos,asin,atan,cos,sin,ta ...
- 【Java】comparable、comparator
comparable.comparator接口 说明 Java中的对象,正常情况下,只能进行比较:== 或 != .不能使用 > 或 < 的,但是在开发场景中,我们需要对多个对象进行排序, ...
- Java基础-JNI入门示例
1.JNI是什么? JNI(Java Native Interface) Java本地接口,又叫Java原生接口.它允许Java调用C/C++的代码,同时也允许在C/C++中调用Java的代码. 可以 ...
- spring 事务失效的几种场景
以下场景是基于mysql数据库,InnoDB的存储引擎. 一.没有添加@Transactional注解 二.方法声明是private或者static 三.没有抛出异常而是try catch了异常 下面 ...
- C# 类的继承问题 正方形周长面积计算问题
1. 设计编写一个控制台应用程序,练习类的继承. (1) 编写一个抽象类 People,具有"姓名","年龄"字段,"姓名"属性,Work ...
- macos下命令行通过ndk编译android下可以执行的ELF程序(并验证opencl的调用)
源码如下,实现把一个JPG保存成灰度图格式的BMP 1 //jpg2bmp.cpp 2 #include <stdio.h> 3 #include <inttypes.h> 4 ...
- Redis作缓存
缓存策略三要素:缓存命中率 缓存更新策略 最大缓存容量.衡量一个缓存方案的好坏标准是:缓存命中率.缓存命中率越高,缓存方法设计的越好. 三者之间的关系为:当缓存到达最大的缓存容量时,会触发缓存更 ...
- 中招了,重写TreeMap的比较器引发的问题…
需求背景 给一个无序的map,按照value的值进行排序,value值越小,排在越前面. key和value都不为null value可能相同 返回结果为一个相同的有序map 代码如下所示: 1 // ...
- 为什么ConcurrentHashMap是线程安全的?
ConcurrentHashMap 是 HashMap 的多线程版本,HashMap 在并发操作时会有各种问题,比如死循环问题.数据覆盖等问题.而这些问题,只要使用 ConcurrentHashMap ...