JAVA研发面试题(基础)String实现 intern

JAVA研发面试题(基础)

JAVA基础(目录):
Object类的方法,逐个解释一下(clone,hashCode,equals,wait,finalize,notify)
Java的Exception类型 
Integer和int有啥区别,integer中有哪些特殊的函数?
说一下String实现 intern 
final 关键字 
序列化,远程方法调用

String实现 intern ,intern()方法设计的初衷,就是重用String对象,以节省内存消耗

public native String intern();

public class StringTest {
    public static void main(String[] args){
        Integer integer = new Integer(1);
        String ss = "iii";
        String ssr = new String("iii");
        String si = ss.intern();
        System.out.println(ss==si);//true
        System.out.println(ssr==si);//false
    }
}

以下来自:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
在JAVA语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。
8种基本类型的常量池都是系统协调的,String。类型的常量池比较特殊它的主要使用方法有两种:
使用直接双引号声明出来的String对象会直接存储在常量池中。
如果不是用双引号声明的String对象,可以使用String提供的intern方法.intern方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

我们接下来主要来谈一下String#intern方法。

首先深入看一下它的实现原理。

public native String intern();  

String#intern方法中看到,这个方法是一个native的方法,“如果常量池中存在当前字符串,就会直接返回当前字符串。如果常量池中没有此字符串,会将此字符串放入常量池中后,再返回“。

JAVA使用JNI调用C ++的实现StringTable的intern方法,StringTable的intern方法跟的Java的中HashMap的实现的英文差不多的,只是不能自动扩容,默认大小是1009。

要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了直接后会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

在jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:

-XX:StringTableSize=99991
相信很多JAVA程序员都做做类似String s = new String(“abc”)这个语句创建了几个对象的题目。这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是“abc”字符串存储在常量池中,第二个对象在JAVA Heap中的字符串对象。

来看一段代码:

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);
}

打印结果是

jdk6下false false
jdk7下false true

具体为什么稍后再解释,将然后s3.intern();语句下调一行,放到String s4 = “11”;后面。将s.intern();放到String s2 = “1”;后面。是什么结果呢

public static void main(String[] args) {
    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);
}

打印结果为:

jdk6下false false
jdk7下false false
1,JDK6中的解释

JDK6图

注:图中绿色线条代表string对象的内容指向。黑色线条代表地址指向。

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

2,JDK7中的解释

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

正是因为字符串常量池移动到JAVA Heap区域后,再来解释为什么会有上述的打印结果。

JDK7图1JDK7图1

在第一段代码中,先看s3和s4字符串。,这句String s3 = new String(“1”) + new String(“1”);代码中现在生成了2最终个对象,是字符串常量池中的“1”和JAVA Heap中的s3引用指向的对象。还有2个匿名的new String(“1”)我们不去讨论它们。此时s3引用对象内容是“11”,但此时常量池中是没有“11”对象的。
接下来s3.intern();这一句代码,是将s3中的“11”字符串放入字符串常量池中,因为此时常量池中不存在“11”字符串,因此常规做法是跟jdk6图中表示的那样,在常量池中生成一个“11”的对象,关键点是jdk7中常量池不在Perm区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。也就是说引用地址是相同的。
最后String s4 = “11”;这句代码中“11”是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向s3引用对象的一个​​引用。所以s4引用就指向和s3一样了。因此最后的比较s3 == s4是真的。

再看s和s2对象。String s = new String(“1”);第一句代码,生成了2个对象。常量池中的“1”和JAVA Heap中的字符串对象s.intern();这一句是s对象去常量池中寻找后发现“1”已经在常量池里了

接下来这句String s2 = “1”;代码是生成一个s2的引用指向常量池中的“1”对象。结果就是s和s2的引用地址明显不同。图中画的很清晰。

JDK7图2JDK7图2

来看第二段代码,从上边第二幅图中观察。第一段代码和s3.intern();第二段代码的改变就是的顺序是放在String s4 = “11”;后了。这样,首先执行声明String s4 = “11”;s4的时候常量池中是不存在“11”对象的,执行完毕后,“11”对象是s4声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此s3和s4的引用是不同的。
第二段代码中的s和s2代码中,,s.intern();这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String(“1”);的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的.s和s2的引用地址是不会相等的。

小结

从上述的例子代码可以看出jdk7版本对实习操作和常量池都做了一定的修改。主要包括2点:

  • 将字符串常量池从Perm区移动到了Java堆区

  • String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。

    自己总结:
    当直接使用双引号,会在字符串常量池生成字符串,当使用new String()生成时,是在堆空
    间生成字符串,并且在常量池中保存一个指向堆的引用(intern 方法)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值