• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

java 中的String.intern()

JAVA web前端中文站 2年前 (2017-04-23) 807次浏览 已收录 0个评论

相信很多人都没有用过String.intern()这个方法。在学习String.intern()之前,我们先看看上一章的《Java JVM 内存(栈、堆、常量池)分配》。

存在于.class 文件中的常量池,在运行期被 JVM 装载,并且可以扩充。String 的 intern()方法就是扩充常量池的 一个方法;当一个 String 实例 str 调用 intern()方法时,Java 查找常量池中 是否有相同 Unicode 的字符串常量,如果有,则返回其的引用,如果没有,则在常 量池中增加一个 Unicode 等于 str 的字符串并返回它的引用;看下面的示例

 String s0= "kvill";    String s1=new String("kvill");    String s2=new String("kvill");    System.out.println( s0==s1 );    System.out.println( "**********" );    s1.intern();    s2=s2.intern(); //把常量池中"kvill"的引用赋给 s2    System.out.println( s0==s1);    System.out.println( s0==s1.intern() );    System.out.println( s0==s2 );

结果为:

 false  false //虽然执行了 s1.intern(),但它的返回值没有赋给 s1  true //说明 s1.intern()返回的是常量池中"kvill"的引用  true

最后我再破除一个错误的理解:有人说,“使用 String.intern() 方法则可以将一个 String 类的保存到一个全局 String 表中 ,如果具有相同值的 Unicode 字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”如果我把他说的这个全局的 String 表理解为常量池的话,他的最后一句话,”如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:

示例:

 String s1=new String("kvill");    String s2=s1.intern();    System.out.println( s1==s1.intern() );    System.out.println( s1+" "+s2 );    System.out.println( s2==s1.intern() );

结果:

 false  kvill kvill  true

在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用 s1.intern()后就在常量池中新添加了一 个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。
s1==s1.intern() 为 false 说明原来的”kvill”仍然存在;s2 现在为常量池中”kvill”的地址,所以有 s2==s1.intern()为 true。

equals()和==

这个对于 String 简单来说就是比较两字符串的 Unicode 序列是否相当,如果相等返回 true;而==是 比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。

String 是不可变的

这一说又要说很多,大家只 要知道 String 的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”; 就是有 4 个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了 str,就是因为 String 的”不可变”产生了很多临时变量,这也就是为什么建议用 StringBuffer 的原 因了,因为 StringBuffer 是可改变的。

下面是一些 String 相关的常见问题:

String 中的 final 用法和理解

 final StringBuffer a = new StringBuffer("111"); final StringBuffer b = new StringBuffer("222"); a=b;//此句编译不通过 final StringBuffer a = new StringBuffer("111"); a.append("222");// 编译通过

可见,final 只对引用的”值”(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象 的变化,final 是不负责的。

String 常量池问题的几个例子
下面是几个常见例子的比较分析和理解:

 String a = "a1";    String b = "a" + 1;    System.out.println((a == b)); //result = true   String a = "atrue";    String b = "a" + "true";    System.out.println((a == b)); //result = true   String a = "a3.4";    String b = "a" + 3.4;    System.out.println((a == b)); //result = true

分析:JVM 对于字符串常量的”+”号连接,将程序编译期,JVM 就将常量字符串的”+”连接优化为连接后的值,拿”a” + 1 来说,经编译器优化后在 class 中就已经是 a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为 true。

 String a = "ab";    String bb = "b";    String b = "a" + bb;    System.out.println((a == b)); //result = false

分析:JVM 对于字符串引用,由于在字符串的”+”连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即”a” + bb 无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给 b。所以上面程序的结果也就为 false。

 String a = "ab";    final String bb = "b";    String b = "a" + bb;    System.out.println((a == b)); //result = true

分析:和[3]中唯一不同的是 bb 字符串加了 final 修饰,对于 final 修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量 池中或嵌入到它的字节码流中。所以此时的”a” + bb 和”a” + “b”效果是一样的。故上面程序的结果为 true。

 String a = "ab";    final String bb = getBB();    String b = "a" + bb;    System.out.println((a == b)); //result = false    private static String getBB() {    return "b";    }

分析:JVM 对于字符串引用 bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和”a”来动态连接并分配地址为 b,故上面 程序的结果为 false。

通过上面 4 个例子可以得出得知:String ?s ?= ?”a” + “b” + “c”;?就等价于 String s = “abc”;

 String  a  =  "a";    String  b  =  "b";    String  c  =  "c";    String  s  =   a  +  b  +  c;

这个就不一样了,最终结果等于:

 StringBuffer temp = new StringBuffer();      temp.append(a).append(b).append(c);      String s = temp.toString();

由上面的分析结果,可就不难推断出 String 采用连接运算符(+)效率低下原因分析,形如这样的代码:

 public class Test {    public static void main(String args[]) {    String s = null;    for(int i = 0; i < 100; i++) {     s += "a";     }    }   }

每做一次 + 就产生个 StringBuilder 对象,然后 append 后就扔掉。下次循环再到达时重新产生个 StringBuilder 对象,然后 append 字符串,如此循环直至结束。如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N – 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用 StringBuffer 或 StringBulider 对象来进行 append 操作。

String 对象的 intern 方法理解和分析:

 public class Test4 {    private static String a = "ab";     public static void main(String[] args){     String s1 = "a";     String s2 = "b";     String s = s1 + s2;     System.out.println(s == a);//false     System.out.println(s.intern() == a);//true  } }

这里用到 Java 里面是一个常量池的问题。对于 s1+s2 操作,其实是在堆里面重新创建了一个新的对象,s 保存的是这个新对象在堆空间的的内容,所 以 s 与 a 的值是不相等的。而当调用 s.intern()方法,却可以返回 s 在常量池中的地址值,因为 a 的值存储在常量池中,故 s.intern 和 a 的值相等。

总结

栈中用来存放一些原始数据类型的局部变量数据和对象的引用(String,数组.对象等等)但不存放对象内容
堆中存放使用 new 关键字创建的对象.
字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常 量池中,而有的是运行时才被创建.使用 new 关键字,存放在堆中。


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:java 中的 String.intern()
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址