先写个例子,一开始我是看迷糊了。jdk1.7运行结果如下: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);
}
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),比较的就是引用的值,“引用的值”可以被认为是对象的逻辑地址。如果两个引用发生“==”操作,就是比较相应的两个对象的地址值是否一样。
场景分析:
总结:
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 方法时,如果池已经包含一个等于此 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);