String类的equals方法和==方法的比较 ..

时间:2022-04-03 16:06:09

先看下面的例子:

public class StringExample
{
public static void main (String args[])
{
String s0 = "Programming";
String s1 = new String ("Programming");
String s2 = "Program" + "ming";

System.out.println("s0.equals(s1): " + (s0.equals(s1)));
System.out.println("s0.equals(s2): " + (s0.equals(s2)));
System.out.println("s0 == s1: " + (s0 == s1));
System.out.println("s0 == s2: " + (s0 == s2));
}}

这个例子包含了3 个String 型变量,其中两个被赋值以常量表达式“Programming”;另一个被赋值以一个新建的值为“Programming”的String 类的实例。使用equals(...)方法和“= =”运算符进行比较产生了下列结果:
s0.equals(s1): true
s0.equals(s2): true
s0 == s1: false
s0 == s2: true

      String.equals()方法比较的是字符串的内容,使用equals(...)方法会对字符串中的所有字符一个接一个地进行比较,如果完全相等那么返回true。 在这种情况下全部字符串都是相同的,所以当字符串s0 与s1 或s2 比较时我们得到的返回值均为true 。

     运算符“==”比较的是String 实例的引用。在这种情况下很明显s0 和s1 并不是同一个String 实例,但s0 和s2 是同一个。

      读者也许会问s0 和s2 怎么是同一个对象呢?
      这个问题的答案来自于
Java语言规范中关于字符串常量String Literals 的章节。本例中“Programming” ,“Program”和“ming”都是字符串常量!!它们在编译期就被确定了当一个字符串由多个字符串常量连接而成时,例如s2 ,它同样在编译期就被确定为一个字符串常量。Java 确保一个字符串常量只有一份拷贝,所以当Programming”和“Program”+“ming”被确定为值相等时,Java 会设置两个变量的引用为同一个常量的引用。

常量池constant pool中,Java 会跟踪所有的字符串常量。
       常量池指的是在编译期被确定,并被保存在已编译的.class 文件中的一些数据。它包含了关于方法,类,接口等等,当然还有字符串常量的信息。当JVM 装载了这个.class 文件。变量s0 和s2 被确定,JVM 执行了一项名为常量池解析constant pool resolution 的操作。

       该项操作针对字符串的处理过程包括下列3 个步骤,摘自JVM 规范5。4 节::
       ¹ 如果另一个常量池入口constant pool entry 被标记为CONSTANT_String2 ,并且指出同样的Unicode 字符序列已经被确定,那么这项操作的结果就是为之前的常量池入口创建的String 实例的引用。
       ² 否则,如果intern()方法已经被这个常量池描述的一个包含同样Unicode 字符序列的String 实例调用过了,那么这项操作的结果就是那个相同String 实例的引用。
      ³ 否则,一个新的String 实例会被创建它包含了CONSTANT_String 入口描述的Unicode 字符;序列这个String 实例就是该项操作的结果。
      也就是说,当常量池第一次确定一个字符串,在Java 内存栈中就创建一个String 实例。在常量池中,后来的所有针对同样字符串内容的引用,都会得到之前创建的String 实例。当JVM 处理到第6 行时,它创建了字符串常量Programming 的一份拷贝到另一个String 实例中。所以对s0 和s1 的引用的比较结果是false ,因为它们不是同一个对象。这就是为何s0==s1 的操作在某些情况下与s0.equals(s1)不同。s0==s1 比较的是对象引用的值;而s0.equals(s1)实际上执行的是字符串内容的比较。
      存在于.class 文件中的常量池,在运行期被JVM 装载,并且可以扩充。此前提到的intern()方法针对String 实例的这个意图提供服务。当针对一个String 实例调用了intern()方法,intern()方法遵守前面概括的第3 步以外的常量池解析规则:因为实例已经存在,而不需要另外创建一个新的。所以已存在的实例的引用被加入到该常量池。来看看另一个例子:

 import java.io.*;

public class StringExample2
{
public static void main (String args[])
{
String sFileName = "test.txt";
String s0 = readStringFromFile(sFileName);
String s1 = readStringFromFile(sFileName);

System.out.println("s0 == s1: " + (s0 == s1));
System.out.println("s0.equals(s1): " + (s0.equals(s1)));

s0.intern();
s1.intern();

System.out.println("s0 == s1: " + (s0 == s1));
System.out.println("s0 == s1.intern(): " +
(s0 == s1.intern()));
}

private static String readStringFromFile (String sFileName)
{
//…read string from file…
}
}


     这个例子没有设置s0 和s1 的值为字符串常量,取而代之的是在运行期它从一个文件中读取字符串,并把值分配给readStringFromFile(...)方法创建的String 实例。从第9 行开始,程序对两个被新创建为具有同样字符值的String 实例进行处理。当你看到从第11 行到12 行的输出结果时,你会再次注意到这两个对象并不是同一个,但它们的内容是相同的。输出结果如下:

s0 == s1: false
s0.equals(s1): true
s0 == s1: false
s0 == s1.intern(): true



第14 行所做的是将String 实例的引用s0 存入常量池。当第15 行被处理时,对s1.intern()方法的调用,会简单地返回引用s0。 这样一来第17 行和18 行的输出结果正是我们所期望的,s0 与s1 仍旧是截然不同的两个String 。实例因此s0==s1 的结果是false。 而s1.intern()返回的是常量池中的引用值即s0 所,以表达式s0==s1.intern()的结果是true。 假如我们希望将实例s1 存入常量池中,我们必须首先设置s0 为null, 然后请求垃圾回收器garbagecollector 回收被指向s0 的String 实例。在s0 被回收后s1.intern()方法的调用,将会把s1存入常量池。
       总的来说在执行等式比较时,应该始终使用String.equals(...)方法,而不是==运算符。如果你还是习惯性地使用==运算符,那么intern()方法可以帮助你得到正确的答案。
因为当n 和m 均为String 实例的引用时,语句n.equals(m)与n.intern() ==m.intern()得到的结果是一致的。假如你打算充分利用常量池的优势那么你就应该选择String.intern()方法。




-----

String实体化对象的特殊性:

java代码: 

String str ; 
这样声明str它只支是一个对象的reference,不会产生实际的对象。如果没有初始化str,编译时便会发生错误。 
java代码: 
String str1=new String("test"); 
String str2 = "test";
str1是一个新的对象。new关键字的意思就是创建某个新的对象。而str2是一个对象的引用。 它们的内容相同,但内存地址是不一样的。 java中对象的引用存在Stack(栈)中,而对象由Heap(堆)分配空间。



intern()方法就是在常量池中试图完成这样的一个寻找过程
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.


Answer 
First off, let's make sure that we all understand what java.lang.String.intern() actually does... Basically, it internalizes strings such that for any two strings, s1.intern() == s2.intern() if and only if s1.equals(s2). In other words, it makes sure that the returned string reference points to the single, canonical version of that string of characters. It does this by managing a pool of unique string values.

Now, as noted in What are the differences between the == operator and the equals() method?, we know that, typically, we can't use the == operator to compare string values because the == operator is just comparing the values of references to those strings rather than the strings' values. But, for internalized strings, since we know that the value of the reference will always be to the unique, canonical version of the string, we can use the == operator.

Therefore, the primary benefit in this case is that using the == operator for internalized strings is a lot faster than use the equals() method. So, use the intern() method if you're going to be comparing strings more than a time or three.

The primary disadvantage is that you have to remember to make sure that you actually do intern() all of the strings that you're going to compare. [Note that you don't have to intern() string literals or string-valued constants.] It's easy to forget to intern() all strings and then you can get confusingly incorrect results. Also, for everyone's sake, please be sure to very clearly document that you're relying on the strings being internalized.

The second disadvantage if you decide to internalize strings is that the intern() method is relatively expensive. It has to manage the pool of unique strings so it does a fair bit of work (even if the string has already been internalized). So, be careful in your code design so that you e.g., intern() all appropriate strings on input so you don't have to worry about it anymore.

最直观的意义还是在于 intern()后字符串可以直接 == 进行比较,速度提高了3倍。
难怪Lucene中的Term里面也对field字段串进行了 intern() 处理
明显是为了速度啊

/** Compares two terms, returning a negative integer if this
term belongs before the argument, zero if this term is equal to the
argument, and a positive integer if this term belongs after the argument.
The ordering of terms is first by field, then by text.*/
public final int compareTo(Term other) {
if (field == other.field) // fields are interned
return text.compareTo(other.text);
else
return field.compareTo(other.field);
}