【.net超级群:27921837】

Java中equals和==的区别

java中的数据类型,可分为两类:
1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
他们之间的比较,应用双等号(==),比较的是他们的值。
2.复合数据类型(类)
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

按 Ctrl+C 复制代码

public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = "Monday";
if (s1 == s2)
{
System.out.println("s1 == s2");}
else{
System.out.println("s1 != s2");}
}
}
按 Ctrl+C 复制代码

编译并运行程序,输出:s1 == s2说明:s1 与 s2 引用同一个 String 对象 -- "Monday"!
2.再稍微改动一下程序,会有更奇怪的发现:

public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = new String("Monday");
if (s1 == s2)
{System.out.println("s1 == s2");}
else
{System.out.println("s1 != s2");}
if (s1.equals(s2)) {System.out.println("s1 equals s2");}
else{
System.out.println("s1 not equals s2");}
}
}

我们将s2用new操作符创建
程序输出:
s1 != s2
s1 equals s2
说明:s1 s2分别引用了两个"Monday"String对象

3. 字符串缓冲池
原来,程序在运行的时候会创建一个字符串缓冲池当使用 s2 = "Monday" 这样的表达是创建字符串的时候,程序首先会在这个String缓冲池中寻找相同值的对象,在第一个程序中,s1先被放到了池中,所以在s2被创建的时候,程序找到了具有相同值的 s1
将s2引用s1所引用的对象"Monday"
第二段程序中,使用了 new 操作符,他明白的告诉程序:"我要一个新的!不要旧的!"于是一个新的"Monday"Sting对象被创建在内存中。他们的值相同,但是位置不同,一个在池中游泳一个在岸边休息。哎呀,真是资源浪费,明明是一样的非要分开做什么呢?

4.再次更改程序:

public class TestString {
public static void main(String[] args) {
String s1 = "Monday";
String s2 = new String("Monday");
s2 = s2.intern();
if (s1 == s2)
{System.out.println("s1 == s2");}
else
{System.out.println("s1 != s2");}
if (s1.equals(s2)) {System.out.println("s1 equals s2");}
else{
System.out.println("s1 not equals s2");}
}
}

这次加入:s2 = s2.intern();
程序输出:
s1 == s2
s1 equals s2
原 来,(java.lang.String的intern()方法"abc".intern()方法的返回值还是字符串"abc",表面上看起来好像这个方 法没什么用处。但实际上,它做了个小动作:检查字符串池里是否存在"abc"这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会 把"abc"添加到字符串池中,然后再返回它的引用。

 
 
 
 
 
 
  先来看一个例子,代码如下:

  1. public class Test {
  2. public static void main(String[] args) {
  3. String str = "abc";
  4. String str1 = "abc";
  5. String str2 = new String("abc");
  6. System.out.println(str == str1);
  7. System.out.println(str1 == "abc");
  8. System.out.println(str2 == "abc");
  9. System.out.println(str1 == str2);
  10. System.out.println(str1.equals(str2));
  11. System.out.println(str1 == str2.intern());
  12. System.out.println(str2 == str2.intern());
  13. System.out.println(str1.hashCode() == str2.hashCode());
  14. }
  15. }
public class Test {
public static void main(String[] args) {
String str = "abc";
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str == str1);
System.out.println(str1 == "abc");
System.out.println(str2 == "abc");
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
System.out.println(str1 == str2.intern());
System.out.println(str2 == str2.intern());
System.out.println(str1.hashCode() == str2.hashCode());
}
}

如果您能对这8个输出结果直接判断出来,下面的分析就不用看了。但是我想还是有很多人对这个String对象这个问题只是表面的理解,下面就来分析一下Java语言String类和对象及其运行机制的问题。


做个基础的说明,堆(heap)内存和栈(Stack)内存的问题。堆和栈的数据结构这里就不解释了。Java语言使用内存的时候,栈内存主要保存以下内容:基本数据类型和对象的引用,而堆内存存储对象,栈内存的速度要快于堆内存。总结成一句话就是:引用在栈而对象在堆。


Java中的比较有两种,是==和equals()方法,equals()是Object类的方法,定义在Object类中的equals()方法是如下实现的:

  1. public boolean equals(Object obj){
  2. return (this==obj);
  3. }
	public boolean equals(Object obj){
return (this==obj);
}

String类重写了equals()方法,改变了这些类型对象相等的原则,即判断对象是否相等依据的原则为判断二者的内容是否相等。


了解以上内容后我们来说说String,String类的本质是字符数组char[],其次String类是final的,是不可被继承的,这点可能被大多数人忽略,再次String是特殊的封装类型,使用String时可以直接赋值,也可以用new来创建对象,但是这二者的实现机制是不同的。还有一个String池的概念,Java运行时维护一个String池,池中的String对象不可重复,没有创建,有则作罢。String池不属于堆和栈,而是属于常量池。下面分析上方代码的真正含义

  1. String str = "abc";
  2. String str1= "abc";
	String str = "abc";
String str1= "abc";

第一句的真正含义是在String池中创建一个对象”abc”,然后引用时str指向池中的对象”abc”。第二句执行时,因为”abc”已经存在于String池了,所以不再创建,则str==str1返回true就明白了。str1==”abc”肯定正确了,在String池中只有一个”abc”,而str和str1都指向池中的”abc”,就是这个道理。

  1. String str2 = new String("abc");
String str2 = new String("abc");

这个是Java
SE的热点问题,众所周知,单独这句话创建了2个String对象,而基于上面两句,只在栈内存创建str2引用,在堆内存上创建一个String对象,内容是”abc”,而str2指向堆内存对象的首地址。


下面就是str2==”abc”的问题了,显然不对,”abc”是位于String池中的对象,而str2指向的是堆内存的String对象,==判断的是地址,肯定不等了。


str1.equals(str2),这个是对的,前面说过,String类的equals重写了Object类的equals()方法,实际就是判断内容是否相同了。


下面说下intern()方法,在JavaDoc文档中,这样描述了intern()方法:返回字符串对象的规范化表示形式。怎么理解这句话?实际上过程是这样进行的:该方法现在String池中查找是否存在一个对象,存在了就返回String池中对象的引用。


那么本例中String池存在”abc”,则调用intern()方法时返回的是池中”abc”对象引用,那么和str/str1都是等同的,和str2就不同了,因为str2指向的是堆内存。

hashCode()方法是返回字符串内容的哈希码,既然内容相同,哈希码必然相同,那他们就相等了,这个容易理解。
再看下面的例子:

  1. public class Test {
  2. private static String str = "abc";
  3. public static void main(String[] args) {
  4. String str1 = "a";
  5. String str2 = "bc";
  6. String combo = str1 + str2;
  7. System.out.println(str == combo);
  8. System.out.println(str == combo.intern());
  9. }
  10. }
public class Test {
private static String str = "abc";
public static void main(String[] args) {
String str1 = "a";
String str2 = "bc";
String combo = str1 + str2;
System.out.println(str == combo);
System.out.println(str == combo.intern());
}
}

这个例子用来说明用+连接字符串时,实际上是在堆内容创建对象,那么combo指向的是堆内存存储”abc”字符串的空间首地址,显然str==combo是错误的,而str==combo.intern()是正确的,在String池中也存在”abc”,那就直接返回了,而str也是指向String池中的”abc”对象的。此例说明任何重新修改String都是重新分配内存空间,这就使得String对象之间互不干扰。也就是String中的内容一旦生成不可改变,直至生成新的对象。


同时问题也来了,使用+连接字符串每次都生成新的对象,而且是在堆内存上进行,而堆内存速度比较慢(相对而言),那么再大量连接字符串时直接+是不可取的,当然需要一种效率高的方法。Java提供的StringBuffer和StringBuilder就是解决这个问题的。区别是前者是线程安全的而后者是非线程安全的,StringBuilder在JDK1.5之后才有。不保证安全的StringBuilder有比StringBuffer更高的效率。

自JDK1.5之后,Java虚拟机执行字符串的+操作时,内部实现也是StringBuilder,之前采用StringBuffer实现。

欢迎交流,希望对使用者有用。

 
 
 

但是有一种情况需要引起我们的注意。请看下面的代码:

public class StringStaticTest {

public static final String A = "ab"; // 常量A

public static final String B = "cd"; // 常量B

public static void main(String[] args) {

String s = A + B; // 将两个常量用+连接对s进行初始化

String t = "abcd";

if (s == t) {

System.out.println("s等于t,它们是同一个对象");

} else {

System.out.println("s不等于t,它们不是同一个对象");

}

}

}

这段代码的运行结果如下:

s等于t,它们是同一个对象

我对上面的例子稍加改变看看会出现什么情况:

public class StringStaticTest {

public static final String A; // 常量A

public static final String B; // 常量B

static {

A = "ab";

B = "cd";

}

public static void main(String[] args) {

// 将两个常量用+连接对s进行初始化

String s = A + B;

String t = "abcd";

if (s == t) {

System.out.println("s等于t,它们是同一个对象");

} else {

System.out.println("s不等于t,它们不是同一个对象");

}

}

}

它的运行结果是这样:

s不等于t,它们不是同一个对象

只是做了一点改动,结果就和刚刚的例子恰好相反。我们再来分析一下。A和B虽然被定义为常量(只能被赋值一次),但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。

 
 
 
 
 
 
 
 
前言:
字符串 (String) 是 java 编程语言中的核心类之一,在我们平常时候使用也比较很普遍,应用广泛。
但你是否知道什么是字符串直接量,知不知道有个字符串驻留池,字符串的驻留池可以用来缓存字符串直接量。

什么是直接量
直接量是指:在程序中,通过源代码直接指定的值。
eg:
int personId = 8080 ;
String name = "fancy" ;

对于 java 中的字符串直接量,JVM 会使用一个字符串驻留池来缓存它们。一般情况下,字符串驻留池中的字符串对象不会被GC (Garbage Collection,垃圾回收) 所回收,
当再次使用字符串驻留池中已有的字符串对象时候,无需再次创建它,而直接使它的引用变量指向字符串驻留池中已有的字符串对象。

String 基础

String 类代表字符串。字符串是常量,它们的值在创建之后是不能再被更改的。在 java 中除了 synchronized之外,不可变的类也是线程安全的,
因此,String 类本身也是线程安全的。String 类的实例对象其实是可以被共享的。

示例代码:

1
2        String name = "fancy";                  // @1
3        String nick    = "fancydeepin";  // @2
4        name = nick;
5        System.out.println(name);    // export:fancydeepin
6

结果输出 :fancydeepin

这是怎么回事?不是说 String 是不可变的字符串吗?怎么这里又变了?
是这样的,在这里 name 只是一个引用类型变量,并不是一个 String 对象,@1中创建了一个 "fancy" 的字符串对象,
@2中创建了一个 "fancydeepin" 的字符串对象,name 引用 (就像一个指针) 刚开始是指向 "fancy" 对象,而后,name 又重新指向 "fancydeepin" 对象,
在示例代码中,整个过程只创建了两个 String 对象 (不知道我这样说你能不能理解,为什么是只创建了两个 String 对象?而不是 1个、3个...  @3),
一个是 "fancy" 对象,另外一个是 "fancydeepin" 对象。而这两个对象被创建出来后并没有被改变过,之所以程序会输出 fancydeepin,完全只是因为
name 引用所指向的对象发生了改变。

如果你是本着认真的态度看着我的贴子,细心的你,是否会留意到:
当 name 引用重新指向另外一个对象的时候,那 name 之前引用的对象 ( "fancy" 对象 ) JVM 在底层会怎么处理它呢?是会立即来回收它来释放系统资源吗?
答案是否定的。虽然这时候程序再也不访问 "fancy" 这个对象,但 JVM 还是不会来回收它,它将在程序运行期间久驻内存,为什么会这样呢?
再往下说就扯到 java 的内存管理机制了,这里点到即止。在这里你可以简单的将它理解成 "fancy" 对象被缓存了起来 ( 实际上也是因为被缓存了 )。

字符串驻留池
当比较两个 String 对象时候,是应该用 "==" 呢?还是应该选择 equals 呢?相信绝大部分人绝大多时候使用的都是选择用 equals 方法。
"==" 和 equals 的用法相信大家都很熟悉了,"==" 比较的是两个对象的哈希码值是否相等,而 equals 比较的是对象的内容是否一样。
而绝大部分时候我们比较两个 String 对象的时候只是想比较它们的内容是否相等,这样看来,只能选 equals 了,但真的是这样吗?
答案是否定的。你一样也可以用 "==" 来完成这样的一件事情,而且 "==" 的效率无论如何都是要比使用 equals 的效率要高的,但前提是,
需要使用字符串的驻留池,才能使用 "==" 来替代 equals 作比较。
String 里面有个方法叫 intern(),执行效率很高,但也许你还不曾用过,下面是摘自API中 intern() 方法的描述:

“当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。
否则,将此 String 对象添加到池中,并返回此 String 对象的引用。 
它遵循以下规则:
对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 
才为 true。 ”

示例代码:

 1
 2             String name = new String("fancy");
 3            
 4        if(name == "fancy") {    // false
 5            System.out.println("equals 1");
 6        }else {
 7            System.out.println("not equals 1");    // Be printed
 8        }
 9        
10        name = name.intern();    // 将字符串添加到驻留池
11        
12        if(name == "fancy") {    // true
13            System.out.println("equals 2");        // Be printed
14        }else {
15            System.out.println("not equals 2");
16        }
17

输出结果:

1
2not equals 1
3equals 2
4

由上面的示例代码可以看到,字符串驻留池的使用是非常简单的,池中的对象可以被共享,只要你将字符串添加到池中,就能够直接使用
"==" 来比较两个对象,而不是只能使用 equals 来作比较。将字符串添加到驻留池来使用 "==" 作比较的方式要比直接使用 equals 效率要高些。

再论 String、StringBuffer  StringBuilder

由 synchronized 修饰的方法可以保证方法的线程安全,但是会降低该方法的执行效率;

翻开 API,很容易就能知道:StringBuffer 是线程安全的可变字符序列,StringBuilder 是一个可变的字符序列,是线程不安全的;
网上说的所谓的使用 StringBuffer 的效率更高更好,这已经不合时宜,这是 java 1.5 之前的版本的说法,早过时了现在!!
现在是反过来了,由于 StringBuilder 不是线程安全的,StringBuilder 效率会比 StringBuffer 效率更高一些。

你可以不相信我说的,但你总该相信程序跑出来的结果吧,下面是示例代码:

 1
 2        StringBuffer  buffer  = new StringBuffer();
 3        StringBuilder builder = new StringBuilder();
 4        int COUNT = 10;       // 测试 COUNT 趟
 5        final int N = 100000; // 每趟操作 N 次
 6        double beginTime, costTime; // 每趟开始时间和耗费时间
 7        double bufferTotalTime = 0.0D, buliderTotalTime = 0.0D; // StringBuffer 和 StringBuilder 测试 COUNT 趟的总耗时
 8        while(COUNT -- > -1) {
 9            // 也可以测试每趟都创建一个新的对象,这样 StringBuilder 效率比 StringBuffer 的效率变得更明显了
10            /**
11            StringBuffer  buffer  = new StringBuffer();
12            StringBuilder builder = new StringBuilder();
13            */
14            System.out.println("----------------------------------<" + (COUNT + 1) + ">");
15            beginTime = System.currentTimeMillis();
16            for(int i = 0; i < N; i++) {
17                buffer.append(i);
18                buffer.length();
19            }
20            costTime = System.currentTimeMillis() - beginTime;
21            bufferTotalTime  += costTime;
22            System.out.println("StringBuffer  费时: --->> " + costTime);
23            beginTime = System.currentTimeMillis();
24            for(int i = 0; i < N; i++) {
25                builder.append(i);
26                builder.length();
27            }
28            costTime = System.currentTimeMillis() - beginTime;
29            buliderTotalTime += costTime;
30            System.out.println("StringBuilder 费时: --->> " + costTime);
31            System.out.println("----------------------------------<" + (COUNT + 1) + ">");
32        }
33        System.out.println("bufferTotalTime / buliderTotalTime = " + (bufferTotalTime / buliderTotalTime));
34

后台输出结果:

 1
 2----------------------------------<10>
 3StringBuffer  费时: --->> 32.0
 4StringBuilder 费时: --->> 16.0
 5----------------------------------<10>
 6----------------------------------<9>
 7StringBuffer  费时: --->> 21.0
 8StringBuilder 费时: --->> 15.0
 9----------------------------------<9>
10----------------------------------<8>
11StringBuffer  费时: --->> 25.0
12StringBuilder 费时: --->> 35.0
13----------------------------------<8>
14----------------------------------<7>
15StringBuffer  费时: --->> 24.0
16StringBuilder 费时: --->> 8.0
17----------------------------------<7>
18----------------------------------<6>
19StringBuffer  费时: --->> 48.0
20StringBuilder 费时: --->> 38.0
21----------------------------------<6>
22----------------------------------<5>
23StringBuffer  费时: --->> 22.0
24StringBuilder 费时: --->> 8.0
25----------------------------------<5>
26----------------------------------<4>
27StringBuffer  费时: --->> 23.0
28StringBuilder 费时: --->> 9.0
29----------------------------------<4>
30----------------------------------<3>
31StringBuffer  费时: --->> 25.0
32StringBuilder 费时: --->> 7.0
33----------------------------------<3>
34----------------------------------<2>
35StringBuffer  费时: --->> 23.0
36StringBuilder 费时: --->> 7.0
37----------------------------------<2>
38----------------------------------<1>
39StringBuffer  费时: --->> 78.0
40StringBuilder 费时: --->> 59.0
41----------------------------------<1>
42----------------------------------<0>
43StringBuffer  费时: --->> 21.0
44StringBuilder 费时: --->> 11.0
45----------------------------------<0>
46bufferTotalTime / buliderTotalTime = 1.6056338028169015
47

StringBuffer 在测试中平均耗时是 StringBuilder 的 1.6 倍以上,再测多次,都是 1.6 倍以上。StringBuffer 和 StringBuilder 的性能孰更加优,一眼明了。

 
 
 
 
 

Java String == && equal的更多相关文章

  1. Java中equal和==区别及String创建过程

    Java中equal和==区别 1.起因 在一段Java代码中,使用了两种实现方式. //第一种命令行输入 int main (String[] args) { if(args[0] == " ...

  2. java String final + equal == 区别

    java String 是不可改变的类型. String a = "hello2"; String d = "hello"; final String b = ...

  3. Java String 的equals, == , hascode的区别

    1.equals 和 == ==在java中是比较引用的,即在内存中的地址.而String的equals()是比较字符串的内容 http://blog.csdn.net/barryhappy/arti ...

  4. 深入理解Java String类(综合)

    在Java语言了中,所有类似“ABC”的字面值,都是String类的实例:String类位于java.lang包下,是Java语言的核心类,提供了字符串的比较.查找.截取.大小写转换等操作:Java语 ...

  5. Java String源码解析

    public final class String implements java.io.Serializable, Comparable<String>, CharSequence { ...

  6. 深入Java—String源代码

    /* * @(#)String.java 1.204 06/06/09 * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * ...

  7. 关于Java中equal函数和==的一些区别

    equal函数比较的到底是什么,很明显是比较的值,但是什么值?与==比较的堆中的内存地址不同,其比较的是对象的值,包括各个属性的值.我们在重新overriding此方法时首先要判断是否为同一对象,如果 ...

  8. Java String 对象,你真的了解了吗?

    String 对象的实现 String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能,看下面这张图,一起了解一 ...

  9. Java String 演进全解析

    前言 String 是我们使用最频繁的对象,使用不当会对内存.程序的性能造成影响,本篇文章全面介绍一下 Java 的 String 是如何演进的,以及使用 String 的注意事项. 下面的输出结果是 ...

随机推荐

  1. OOP三大核心封装继承多态

    OOP支柱 3 个核心:封装 继承 多态 封装就是将实现细节隐藏起来,也起到了数据保护的作用. 继承就是基于已有类来创建新类可以继承基类的核心功能. 在继承中 另外一种代码重用是:包含/委托,这种重用 ...

  2. InnoDB数据库 ibdata1 被删除后 的恢复方法

    前提条件:1  ibdata1 被删除 2  数据库文件还存在  特别是 ibd文件 3  原来数据库表结构及索引还在 恢复步骤: 1. 将原来的数据文件COPY到其它目录下. 2. 创建同名表,表结 ...

  3. centos安装gitlab成果

    centos安装gitlab成果 开始之前 在开始之前请先查看官方的刚需文档: https://github.com/gitlabhq/gitlabhq/blob/master/doc/install ...

  4. Linux内核编程-0:来自内核的 HelloWorld

    Linux内核编程一直是我很想掌握的一个技能.如果问我为什么,我也说不上来. 也许是希望有一天自己的ID也出现在内核开发组的邮件列表里?或是内核发行文件的CREDITS文件上? 也许是吧.其实更多的, ...

  5. BZOJ 1901 洛谷 P2617 ZOJ 2112 Dynamic Rankings

    以下时空限制来自zoj Time limit 10000 ms Memory limit 32768 kB OS Linux Source Online Contest of Christopher' ...

  6. Ansible跳板机自动部署

    首先,安装ansible,略过此步骤. 一.控制机(jenkens.ansible所在机器与跳板机之间互信,跳板机与目的机之间互信) 1.在客户端生成公钥私钥对 命令:ssh-keygen -t rs ...

  7. [CSP-S模拟测试]:优化(贪心+DP)

    题目描述 $visit\text{_}world$发现有下优化问题可以用很平凡的技巧解决,所以他给你分享了这样一道题:现在有长度为$N$的整数序列$\{ a_i\}$,你需要从中选出$K$个不想叫的连 ...

  8. 浏览器HTML5录音功能

    一.浏览器HTML5录音功能 二.业务代码 <!DOCTYPE html> <html> <head> <meta http-equiv="Cont ...

  9. webpack前置知识2(JavaScript项目初始化)

    所有的JavaScript项目都是在终端输入npm init -y进行项目初始化,如果要自定义项目规则,去掉 -y 参数. vscode终端快捷键ctrl+` 初始化 运行上述命令后,项目内会新建一个 ...

  10. 树状数组的理解(前缀和 and 差分)

    二更—— 有神仙反映数星星那个题外链炸了,我决定把图给你们粘一下,汉语翻译的话在一本通提高篇的树状数组那一章里有,同时也修改了一些汉语语法的错误 这段时间学了线段树组,当神仙们都在学kmp和hash的 ...