1.运行时数据区

粗略的讲下Java的内存结构,要想深入的讲还需要把JVM的知识拿来,但我们现在的核心是比较==和equals的区别以及常见数据类型在取等和调用equals时产生的结果和原因。

如上图,很重要的就是栈,堆,方法区,下面简单介绍他们的作用:

栈(stack):位于通用RAM中,但通过它的“栈指针”可以从处理器哪里获得支持。栈指针若向下移动,则分配新的内存;若向上移动,则释放那些 内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在栈内所有数据的确切大小和生命周期,因为它必须生成 相应的代码,以便上下移动栈指针。这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在栈中——特别是对象引用,但是JAVA对象不存储其中。

– 存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)。

堆(heap):一种通用性的内存池(也存在于RAM中),用于存放所以的JAVA对象。堆不同于栈的好处是:编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用栈进行存储存储需要更多的时间。

— 存放所有new出来的对象,GC会调用垃圾回收算法回收过期的对象。

方法区:里面存储运行时常量池,已被虚拟机加载的类信息、常量、静态变量,及时编译器编译后的代码等数据,常量以HashSet的策略存储。

以上三者,速度最快的是方法区,其次是堆,最后的栈,

2.== 和 equals比较的到底是什么?

很多朋友可能和我一样,大一才学Java,老师就只说== 比较的是数据储存的地址,equals比较的是内容,举个例子:

1
2
3
4
String s1 = "123";
String s2 = "123";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

按照老师的说法,这两个应该都是正确的,确实我们运行代码与我们预期相符,那么现在又来了个兄弟

1
2
3
4
5
String s1 = "123";
String s2 = "123";
String s3 = new String("123");
System.out.println(s1 == s3);
System.out.println(s1.equals(s3));

可以思考下结果,我们即将进入今天的正题!

运行程序结果发现是

false

true

s3不也是字符串“123”吗为什么是false?

为了理清楚里面的奥妙,我们需要记住下面关于==和equals的规则:

1.==
比较基本数据类型时,比较的是数值 byte,short,char,int,float,double,long,boolean
他们作为常量在方法区中的常量池以HashSet的策略存储,栈中的数据可以共享,所以基本数据类型和String常量是可以之间通过==来比较的
比较引用类型:比较引用指向的地址
2.equals
默认比较地址,该方法最初定义在Object上,默认的实现就是比较地址
自定义的类,如果需要比较的是内容,就需要重写String的equals方法

==对于基本数据类型比较的就是值,因为他们存储在方法区的常量池,对于应用类型(如String)就是比较的地址,equals默认是比较的地址,也就是说默认和==是没区别的,但String重写了equals方法,比较字符串的内容!而对于基本数据就只能通过==来比较,用equals连编译都无法通过!

要想知道s3不是字符串“123”吗?为什么是false,我们要一步一步的来!

3.关于String不得不清楚的八大情况

1. String str = “abc”; //引用常量池的对象

String str = “abc” 的创建过程:
1 首先在常量池中查找是否存在内容为”abc”字符串对象
2 如果不存在则在常量池中创建”abc”,并让str引用该对象
3 如果存在则直接让str引用该对象

2.String str1 = “abc”;

String str2 = new String(“abc”).intern();

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

String str = new String(“abc”)创建实例的过程:
1.首先在堆中(不是常量池)创建一个指定的对象”abc”,并让str引用指向该对象
在字符串常量池中查看,是否存在内容为”abc”字符串对象
若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来
若不存在,则在字符串常量池中创建一个内容为”abc”的字符串对象,并将堆中的对象与之联系起来
intern 方法可以返回该字符串在常量池中的对象的引用,可以通过下面代码简单的测试
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对
它遵循以下规则:对于任意两个字符串 s 和 t ,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true 。

到这里我们就可以介绍上面的问题s3是字符串“123”为什么是false,因为s3是new出来的,首先就在堆中创建了“123”,即使后面再常量池中查找“123”是存在的,但==始终是比较的地址,所以即使取等内容相同的s1和s3也是返回false;

3.String str3 = “ab” + “c”;

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

str2会找常量池中是否存在”abc”,如果存在就把str2指向str1,显然str1已经在常量区创建了”abc”,== 是对于引用类型是比较地址,所以str1 和str2 都指向”abc”,结果为true。

4.String str4 = “ab”;

String str5 = str4 + “c”;

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

是因为String str5 = str4 + “c”涉及到变量(不全是常量)的相加,所以会生成新的对象,其内部实现是先new一个StringBuilder,然后 append(str4),append(“c”);然后让str5引用toString()返回的对象。

简单的说就是new了一个StringBuilder,产生了新对象。

5.System.out.println(str1 == str2);

我们知道用equals比较str1和str2是毫无疑问返回true的,因为equal默认比较的是引用地址
==对于引用类型也是比较地址,str1 str2 常量存放在常量区,上文也已经提到str1和str2的创建过程,先看常量区是否存在”abc”,若有就不创建了,之间让str2应用该地址,若无则创建,可以看到str1和str2都是指向的该地址,故==返回

6.str1 = “bcd”;

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

赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为”bcd”时,

JVM发现在 常量池中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

7.String str6 = str1;

System.out.println(str6);

String str7 = “bcd”;

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

常量池中不仅仅是对于基本数据类型是以HashSer策略存储的,对于String也是一样,一个常量只会对于一个地址

8.String str8 = new String(“abc”);

String str9 = “abc”;

System.out.println(str8 == str9);

new会之间在堆中创建对象,所以str8的引用也是指向的堆中的对象
str8是常量区的,即使数据相同,也不会返回true

4.Integer的缓存机制、自带拆箱

1.Integer i1 = new Integer(12);

Integer i2 = new Integer(12);

System.out.println(i1 == i2); //false

这里很简单,new了新的对象,在堆中开辟新的空间,==对于引用类型比较的是地址,很显然两者地址不一样(数值相同)

2.Integer i3 = 14;

Integer i4 = 14;

int i5 = 14;

System.out.println(i3 == i4); //true

System.out.println(i3 == i5); //true

对于i3 == i4是返回true,很多人可能觉得是存放在常量区的原因,其实不然,这是Integer的缓存机制,我们将在下一个示例中得到验证

i3 == i4 返回true是自动拆箱机制,Integer转int ==对于基本数据类型比较的是值

3.Integer i6 = 128;

Integer i7 = 128;

int i8 = 128;

System.out.println(i6 == i7); // false 超出Integer缓存范围,

**System.out.println(i6 == i8); **//true

可以看到即使i6 和 i7 数值相同,但取==并不返回true,这时我们可以翻阅Integer的源码:

1
2
3
4
5
6
7
8
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//若数值在-127 - 128之间则会从缓存中拿,不会new 也就不会
//存在的值而已
//若不在,则会new 开辟新的堆空间

其实,Integer是个大坑,在阿里开发手册就明确写道:

整型包装类对象之间值的比较,全部使用 equals 方法比较!