一道题引发的关于String的思考

public static void main(String[] args) {

String str1 = "sdsadf";
System.out.println(str1.intern()==str1);
String str2 = new String("sdsadf").toString();
System.out.println(str2.intern()==str2);
String str3 = new StringBuilder("ja").append("av").toString();
System.out.println(str3.intern()==str3);
String str4 = new StringBuilder("ja").append("va").toString();
System.out.println(str4.intern()==str4);
String a = "a" + "b" + 1;
String b = "ab1";
System.out.println(a == b);
}

先写个例子,一开始我是看迷糊了。jdk1.7运行结果如下:
true
false
true
false
true
如果你知道结果,且明白怎么原理,下文就不用看了,如果不知道,就跟我一起梳理下这块知识点。
背景
String是比较绕的,要说这块还是离不开JVM的内存模型。而且,jdk1.6与1.7还有些细节不同。
虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值(jdk1.6 中长度1009),注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method Area,而不是堆中。常量池中保存着很多String对象; 并且可以被共享使用,因此它提高了效率。
通俗的说,equals是比值的,==是比地址的。equals()方法,首先是在Object类中被定义的。equals()方法之所以存在,是希望子类去重写这个方法,实现对比值的功能,类似的,String就自己实现了equals()方法。== 如果是原始类型byte、boolean、short、char、int、long、float、double,就是直接比较它们的值。如果是引用(Reference),比较的就是引用的值,“引用的值”可以被认为是对象的逻辑地址。如果两个引用发生“==”操作,就是比较相应的两个对象的地址值是否一样。
场景分析:
一道题引发的关于String的思考 - bohu83 - bohu83的博客
一道题引发的关于String的思考 - bohu83 - bohu83的博客
一道题引发的关于String的思考 - bohu83 - bohu83的博客
 说明:
1)对于s2s是个局部变量, 它也指向一个常量,但是其引用上并未“强制约束”是不可以被改变的。所以编译器不会优化。
2)由于final不可变,所以编译器自然认为结果是不可变的。编译器会优化。
对于编译器优化这块,建议可以结合javap命令去分析字节码。还有为什么编译器要优化,主要是为了速度。
 总结:
通过上面的场景,日常大部分string操作可以理解了。下面是string有关特性。

1.String类初始化后是不可变的(immutable)

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

2 new 创建对象

String s = new String(“xyz”); 产生几个对象? 一个或两个。如果常量池中原来没有 ”xyz”, 就是两个。如果原来的常量池中存在“xyz”时,就是一个(首先走常量池的取到一个实例xyz的引用,然后在堆上创建一个新的String实例s)。

3 string ,StringBuilder与StringBuffer

   String 使用数组保存数据,源码如下:private final char value[];因为有“final”修饰符,所以可以知道string对象是不可变的,可以理解为常量,是线程安全的。

StringBuilder与StringBuffer都继承自AbstractStringBuilder类,不同的是StringBuffer 加入了synchronized ,所以是线程安全的,StringBuilder是线程不安全的,相对的单线程情况下StringBuilder效率更高些。

这块也是简单说,其实这块要展开也有很多细节。

好了,基本上跟string有关的梳理基本上要结束啦。。。
不对,还有一块,就是跟一开始的题目有关系的 intern
intern
API解释如下:

返回字符串对象的规范化表示形式。

一个初始时为空的字符串池,它由类 String 私有地维护。

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

所有字面值字符串和字符串赋值常量表达式都是内部的。

返回:

一个字符串,内容与此字符串相同,但它保证来自字符串池中。

通俗理解:

String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常 量池中增加一个Unicode等于str的字符串并返回它的引用;看题目就理解了。

String str1 = "sdsadf";

System.out.println(str1.intern()==str1);

     //true,因为都是指向常量池的“sdsadf”。

String str2 = new  String("sdsadf").toString();

System.out.println(str2.intern()==str2);

       //flase,一个指向常量池,一个是堆内存对象,不相同

String str3 = new StringBuilder("ja").append("av").toString(); System.out.println(str3.intern()==str3);

               //true,都是指向常量池的“jaav”
       String str4 = new StringBuilder("ja").append("va").toString(); 
 System.out.println(str4.intern()==str4);
               //false,因为“java”是关键字
    美团的文章指出了String#intern容易隐藏的问题。网上也有大神说,不建议使用此方法,可以用其它方法代替。 String.intern()最初的用途是去重(消除重复),我作为一个菜鸟,就从了大神的意见吧,的确也没用过,只是遇到题目触发梳理有关知识,加深了解。

参考:
http://www.cnblogs.com/ITtangtang/p/3976820.html
http://tech.meituan.com/in_depth_understanding_string_intern.html#-jdk6-jdk7-intern-
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值