今天和同学一起讨论==和equlas区别到底是啥,一本正经给他分析了一下,结果呢idea一运行发现自己是错的····。当然本篇文章不是主要讲它们区别。我们还是用code代码带我们了解底层String在jvm中生成过程。
今天我们就从下方这一段代码来引出我们要探究的问题
public void test3(){
String s1 = "11";
String s2 = "11";
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
起因就是这串代码,问输出结果hashCode相等不,下面两个是真是假呢。那小编问问大家你们知道吗?
结果: 1568 1568 true true,是不是觉得很简单,和你想的一样呢?
首先我先分析hashCode()为啥相同吧!这个其实很简单,大家要是了解过哈希表的话,应该知道哈希表的key其实就是通过hash函数计算出了,至于这个计算公式我们可以自己覆盖hashCode()这个函数进行自己定义。
要是不了解大概的哈希表的话可以看一下我之前总结的博客:程序员必备:8种通用数据结构(总结)
关于hashCode函数我们也可以到String类中直接看源码,一目了之。
//String 类中的属性
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
从代码中我们可以看出String的字符串其实存放在char数组中,然后只要字符串值相同,那计算出来的h值肯定是相同的。(为什么讲这个,后面会牵扯到字符串常量池在jvm中生成形式)
分析了hashCode值,我们接着分析,为什么s1 == s2,和s1.equlas(s2)为啥都是true呢?
关于equlas方法比较我觉的还是直接看源码吧,清晰明了:
public boolean equals(Object anObject) {
if (this == anObject) {//比较是不是同一个对象
return true;
}
if (anObject instanceof String) {//判断是不是String类
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {//先比较字符串长度是否相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {//循环比较,这应该都能看懂吧
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
是吧,看完源码之后equals比较的不是对象而是字符串值而已,所以true就理所当然。
关于第一个s1==s2;举个其他的例子吧。
Object a = new Object();
Object b = new Object();
System.out.println(a == a);
System.out.println(a == b);
这个答案就没有异议了吧,true和false.
那你有没有疑问s1和s2不是两个的对象吗?怎么也是true。
有这个问题证明你已经上道哈,嘿嘿这就要看我们jvm底层实现你就会明白了。
准备一下知识,了解一下什么是常量池。
其实常量池分为三种:
再讲字符串常量池在jvm中的运行,还需要了解java对象怎么在jvm中存储的,我们才能通过HSDB去跟踪。
之前在上一篇文章讲过java类在jvm存储形式,今天就在讲一下 java对象在jvm存储形式。
class是java的类(java代码)
InstanceKlass就是jave类在jvm中的存在形式(c++代码)
java中new一个新对象(java代码)
InstanceOop就是jave对象在jvm中的存在形式(c++代码)
不知道 什么是对象或者是类的话自行百度吧!
还有有人如果疑问为什么存在jvm中就换个形式,直接class保存进去不行吗?
非常好的问题,但是你先百度找找原因吧,这里就不阐述了。
言归正传继续介绍字符串常量池,我们称呼字符常量池在JVM中对应的类是StringTable,底层实现是一个hashtable。
哈希表的key实现是通过String的value计算出来的,而哈希表的value则是将Java的String类的实例instanceOopDesc封装成HashtableEntry。
言归正传继续介绍字符串常量池,在java中如果你创建一个String,那么大多情况都会保存在字符串常量池中,接下来我就用代码和图片的形式给大家讲解吧。
String s1 = "1";
这样一串代码到底在JVM中创建几个String对象(InstanceOop)呢?
另外附加一个idea查看memory的图:
发现内存中其实就是只有一个String至于那个char[]细心地同学应该知道,它就是String中自带的属性。(之后就不分析那么详细了)
从图上可以看出过程:
String s1 = "1";
String s2 = "1";
这样一串代码到底在JVM中创建几个String对象(InstanceOop)呢?
字符串s2在解析保存时候在字符串常量池直接找到了,就会把里面的HashTableEntry在变成String返回给s2指向,所以s1和s2在底层的JVM中其实就是一个String,那么s1==s2也就理所当然了。
用idea再证明一下吧
可以看到在没创建s1时候内存中char[]只有2605,String只有2243
运行了s1之后
所有运行结束了,还是String都只是+1,这也能证明s1和s2在jvm中指向的是一个对象。
当然前面的都只是开胃菜,接下来,来点难的。
String s1 =new String("1");
这样一串代码到底在JVM中创建几个String对象(InstanceOop)呢?
这个与之Test1相比,它不仅仅需要把字符串"1"存进字符串常量池,还多new了一个String对象。所以它在jvm中生成2个String对象。
当然前面的都只是开胃菜,接下来,来点难的。
String s1 =new String("1");
String s2 =new String("1");
System.out.println(s1 == s2);
这样一串代码到底在JVM中创建几个String对象(InstanceOop)呢?输出的结果又是什么呢?
这样我先说答案大家可以对比一下你们的思考是否正确。在jvm中创建了3个String对象(InstanceOop),输出结果是false。大吃一惊吗???
来看看一怎么创建的吧
首先创建s1没什么疑问了,接着就是s2它们两个都需要从常量池直接去找,如果有就不在将字符串存到常量池中,很明显s1创建结束,s2就不会将字符串再次存入常量池中了,但是s2仍然会new一个String,这也是它们为啥最后不相同原因所在,s1和s2在jvm中指向的不是一个String对象。
再给大家多举几个例子,但是不在画图和演示了,太累了!希望大家体谅
String s1 =1;
String s2 =new String("1");
System.out.println(s1 == s2);
结果:2个String对象,false
String s1 =1;
String s2 =new String("1");
s2.intern();
System.out.println(s1 == s2);
结果:true
intern
1、去常量池中找字符串,有,直接返回
2、没有,就会String对应的InstanceOopDesc封装成HashtableEntry,存储
其实最后感觉还有好多东西,好多情况没讲,那就留到之后我再更新吧。其实这次主要给大家分析String在jvm中的如何形成的。大家如果掌握这个知识,你就是随便String,随便new都影响不了你判断是否相等。好了这次分享就到这里咯。
感谢大家的观看,有什么错误之处欢迎大家指出来。