【java笔记】==和equals的区别
关于==
该符号对于基本类型和引用类型的作用效果是不同的:
对于基本类型:比较的是值是否相同
byte、short、int、long、float、double、boolean、char
对于引用类型:比较的是引用是否相同
“str” != new String(“str”);
关于equals
equals方法是Object类里面的,每个类都默认继承Object类,所以每个java类都有equals方法。
equals的本质就是==,因为是类里面比较两个引用,所以是引用比较。但是String,Integer等类重写了equals方法,把他变成了值比较。
字符串常量池
在java8中,为了优化字符串的存储和性能,引入了一种称为字符串常量池的机制,该机制在jvm层面提供。
实现前提
在Java中,
String类型是不可变的(immutable),这意味着一旦一个String对象被创建,它的内容就不能被改变。在Java运行时,全局字符串常量池会跟踪所有唯一的字符串字面量,确保相同的字符串字面量只存储一次。当进行垃圾回收时,如果一个字符串对象仍然有至少一个引用指向它(即它仍然被程序中的某个部分使用),那么这个对象就不会被垃圾回收机制回收。
实现原理
在JVM层面为字符串提供字符串常量池,可以理解为是一个缓存区;
创建字符串常量时,JVM会检查字符串常量池中是否存在这个字符串;
若字符串常量池中存在该字符串,则直接返回引用实例;若不存在,先实例化该字符串,并且,将该字符串放入字符串常量池中,以便于下次使用时,直接取用,达到缓存快速使用的效果。
1
2
3
4String str1 = "abc";
String str2 = "abc";
System.out.println("str1 == str2: " + (str1 == str2));
//结果:str1 == str2: true
字符串常量池位置的变化
方法区:和java堆一样,是各个线程共享的区域。当虚拟机需要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
主要包含两个模块:class文件,运行时常量池
在jdk1.7之前,运行时常量池和字符串常量池都在方法区中,而方法区的实现被称为永久代,在jvm内存中。这个区域的GC主要是针对运行时常量池的回收和对类型的卸载。
在jdk1.7中,字符串常量池被从永久代移到了堆区,运行时常量池保留在永久代中。
jdk1.8中,永久代被移除,方法区的实现变成了本地内存中的元空间。此时字符串常量池仍在堆中,运行时常量池在元空间。
为什么要把方法区从jvm内存(永久代)移到本地内存(元空间),原因如下:
- 直接内存属于本地系统的IO操作,具有更高的一个IO操作性能,而JVM的堆内存这种,如果有IO操作,也是先复制到直接内存,然后再去进行本地IO操作。经过了一系列的中间流程,性能就会差一些。非直接内存操作:
本地IO操作——>直接内存操作(本地内存)——>非直接内存操作(jvm)——>直接内存操作——>本地IO操作,而直接内存操作:本地IO操作——>直接内存操作(本地内存)——>本地IO操作。 - 永久代有一个无法调整更改的JVM固定大小上限,回收不完全时,会出现
OutOfMemoryError问题;而直接内存(元空间)是受到本地机器内存的限制,不会有这种问题。
新建字符串
创建一个字面量字符串,会在字符串常量池创建一个对象,一个引用(str)指向该对象。如果有相同字面值的创建,那么会有多个引用指向该对象。字面量字符串在编译时被识别,生成字节码,并在类加载时放入字符串常量池中。
1 | String str1 = "abc"; |
使用new创建一个字符串:
如果在代码中就在new String(“123”)中写了”123”这个字面值,那么该字面值在编译期间会被识别,生成字节码。
在类加载时,JVM会检查字符串常量池中是否已经存在一个值为
"123"的字符串对象。如果不存在,JVM会在字符串常量池中创建一个新的字符串对象,并将其放入池中。无论字符串常量池中是否存在该对象,在运行期间,
new String("123")都会在堆内存中创建一个新的String对象实例。返回堆中String对象的引用。
1 | String str2 = new String("123"); |
即:若常量池里没有”123”字符串,则创建了2个对象和一个堆引用;若有该字符串,则创建了一个对象及对应的引用。
涉及到字符串拼接问题:
String str =”ab” + “cd”;对象个数?
分析:若字符串常量池该字符串对象
- 字符串常量池:(1个对象)”abcd”(发生编译时常量折叠) ;
- 堆:无
- 栈:(1个引用)str->”abcd”
总共:1个对象+1个引用
String str = new String(“abc”);对象个数?
分析:若字符串常量池该字符串对象
- 字符串常量池:(1个对象)”abc”;
- 堆:(1个对象)new String(“abc”)
- 栈:(1个引用)str->堆
总共:2个对象+1个引用
String str = new String(“a” + “b”);对象个数?
分析:若字符串常量池该字符串对象
- 字符串常量池:(1个对象)”ab”(发生编译时常量折叠);
- 堆:(1个对象)new String(“ab”)
- 栈:(1个引用)str->堆
总共:2个对象+1个引用
String str = new String(“ab”) + “ab”;对象个数?
分析:若字符串常量池该字符串对象
字符串常量池:(1个对象)”ab”;(
"abab"是通过拼接生成的,而非直接使用字符串字面量。)堆:
new String("ab")创建了1个新的String对象。拼接时,创建了1个
StringBuilder对象。拼接后的新
String对象"abab"会存放在堆中。
栈:(1个引用)str->新
String对象"abab"
总共:4个对象+1个引用
String str = new String(“ab”) + new String(“ab”);对象个数?
分析:若字符串常量池该字符串对象
字符串常量池:(1个对象)”ab”;
堆:
两个
new String("ab")分别创建了两个新的String对象。拼接操作创建了一个
StringBuilder对象。拼接结果
"abab"会创建一个新的String对象。
栈:(1个引用)str->新
String对象"abab"
总共:5个对象+1个引用
String str = new String(“ab”) + new String(“cd”);对象个数?
分析:若字符串常量池该字符串对象
- 字符串常量池:(2个对象)”ab”,”cd”;
- 堆:(4个对象)new String(“ab”),new String(“cd”), stringbuilder, 拼接后的string对象
- 栈:(1个引用)str
总共:6个对象+1个引用
String str3 = str1 + str2;对象个数?
1 | String str1 = "ab"; |
分析:若字符串常量池该字符串对象
- 字符串常量池:(2个对象)”ab”,”cd”,”abcd”;
- 堆:无
- 栈:(3个引用)str1,str2,str3
总共:2个对象+3个引用
如何指向字符串池中的特定的对象?
通过String的intern()方法。作用是当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。
1 | String s0= “kvill”; |


