大家知道我们在java中定义一个字符串是这样定义的 String str=”abc”;
然而还可以这样来定义 String str=new String(“abc”);
那么这两者的区别是什么呢?
我们先来看看代码:
String str="abc";
String str1=new String("abc");
System.out.println(str);
System.out.println(str1);
那么运行结果毋庸置疑:
abc
abc
那么在这里我们要牵涉到==和equal()方法
那么我们在写一段程序
String str="abc";
String str1=new String("abc");
System.out.println((str==str1));
System.out.println(str.equals(str));
那么结果是:
false
true
通过这个结果我们知道==和equals输出的结果是不一样的,那么他们到底有什么不同呢?
那么java早起版本中“==”运算符只能用来测试两个字符串是否是相同的实例,而在新的jdk版本中“==”运算符和equals的效果是一样的 注意这里的效果一样是只针对字符串(String str1=”123” String str2=”wsd” 因为一般都是这样定义的!),而不是对象。
那么具体有什么不同我们等会再分析
那么我们来分析上面的代码在jvm编译器中 编译器为我们做了哪些我们不知道的工作。
那么在解释这些问题之前 你必须弄明白 两个概念:堆(heap)内存和栈(Stack)内存的问题,Java语言使用内存的时候,栈内存主要保存以下内容:基本数据类型和对象的引用,而堆内存存储对象,栈内存的速度要快于堆内存。总结成一句话就是:引用在栈而对象在堆。
那么我们还要弄明白这个函数(equals())这个函数的来源和意义
1, equals的来源 你可以查询String的api文档 你会发现在String中式存在这个方法的 那么它给出的解释是:equals(
Object anObject)
将此字符串与指定的对象比较。
单看这个我们还是很迷糊 到底这个函数比较内容还是对象 下面我们可以具体在String类中是怎么去定义的:
那么我怎么去看String这个类呢?我们只需要在eclipse中对准String 按“ctrl”单击进入String中就可以了!那么我们找到equals这个方法:看到的代码如下
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
那么我们仔细看这个方法 我们可以知道 在这个方法一开始是这样写的
if (this == anObject) {
return true;
} 那么它在一开始就使用了==的运算符来判断 这两个对象是否相等 我们可想而知 如果闯入的这个object跟自己String的对象是同一个对象的话 那么他们的内容当然是一样的。那么直接返回true 而下面的代码就无需再执行了。
当时往往要比较的是不同的两个对象 那么我们在看它下面做的代码 我们可以知道 这个方法传进来的是一个object类型 而我本身是String.equals()的调用的方法 那么就需要把这个object转化为String类型:
那么instanceof 这个关键字是什么意义呢!在这里我们就简单的说一下他的意思:
它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。可以用在继承中的子类的实例是否为父类的实现
我们且不去管他的意思,
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
那么我们从这段代码就可以看出它是在比较内容
所以说归根结底这个String的equals的这个方法是比较内容 。
那么我们追根溯源 我们在api文档中去看看String的父类是否有这个方法
结果我们发现我们在String的父类 Object中也找到这个方法
而他是这样说明的:equals(
Object obj)
指示其他某个对象是否与此对象“相等”。
而我们在这里我们就知道了 原来在String类中的equals方法实际上就是重写了父类object中的equals方法 去实现了不一样的比较 我们不难看出 在object中equals的方法是用来比较两个对象是否相等,而在String中的equals这个方法是比较内容是否相等 而==也是比较两个对象是否相等 那么我们就解释了为什么:
tring str="abc";
String str1=new String("abc");
System.out.println((str==str1));
System.out.println(str.equals(str));
那么结果是:
false
true
的输出结果是不相等的了!
下面我们来深入这个问题 看下面的代码:
String str="abc";
String str1=new String("abc");
String str2="abc";
System.out.println((str==str1));
System.out.println((str==str2));
那么它的结果是:
false
true
那么我们会疑问 String str="abc"; String str1=new String("abc");
这样定义的区别在于什么地方?
String类的本质是字符数组char[],其次String类是final的,是不可被继承的,这点可能被大多数人忽略,再次String是特殊的封装类型,使用String时可以直接赋值,也可以用new来创建对象,但是这二者的实现机制是不同的。
栈内存 |
String pool |
-
Str的引用
Str2的引用
String str = "abc";abc对象
- String str2= "abc";
对于以上的两句代码 编译器做了什么:
第一句的真正含义是在String池中创建一个对象”abc”,然后引用时str指向池中的对象”abc”。第二句执行时,因为”abc”已经存在于String池了,所以不再创建,则str==str2返回true就明白了。Str2==”abc”肯定正确了,在String池中只有一个”abc”,而str和str2都指向池中的”abc”,就是这个道理。
堆内存 |
栈内存 |
String pool |
abc对象 |
Str1的引用
|
对象(abc) |
其实这句话可以分解
String str1=new String();
Str1=”abc”;
单独这句话创建了2个String对象,一个是String池中创建了一个abc对象 在堆内存中创建了一个abc对象。
而基于上面两句,只在栈内存创建str1引用,在堆内存上创建一个String对象,内容是”abc”,而str2指向堆内存对象的首地址。
String str="abc";
String str2="abc";
String str1=new String("abc");
对象(abc) |
堆内存 |
abc对象 |
String pool |
栈内存 |
Str的引用 Str2的引用 Str1 |
因为编译器在创建String常量字符串的时候 会判断在池中是否存在这个常量字符串 如果存在的话 也就不在创建了!
下面就是str1==”abc”的问题了,显然不对,”abc”是位于String池中的对象,而str2指向的是堆内存的String对象,==判断的是地址,肯定不等了。
str1.equals(str2),这个是对的,前面说过,String类的equals重写了Object类的equals()方法,实际就是判断内容是否相同了。
既然说到这里 我们就往下在说说 String的intern()方法大家可以查询api文档 它是这么说的 intern()
返回字符串对象的规范化表示形式。那么我们怎么来理解这句话呢?百度,google 实际上过程是这样进行的:该方法现在String池中查找是否存在一个对象,存在了就返回String池中对象的引用。如果不存在,该方法会把"abc"添加到字符串池中,然后再返回它的引用。
那么我看以下的代码
- String str1 = "a";
- String str2 = "bc";
- String str3 = "a"+"bc";
- String str4 = str1+str2;
- System.out.println(str3==str4);
- str4 = (str1+str2).intern();
- System.out.println(str3==str4);
一个初始时为空的字符串池,它由类 String 私有地维护当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
所有字面值字符串和字符串赋值常量表达式都是内部的。字符串字面值在《Java Language Specification》的 §3.10.5 中已定义。
他么它返回的是什么呢!从哪里来呢?它返回一个字符串,内容与此字符串相同,但它保证来自字符串池中的。
字符串对象的创建方式有两种
String s1 = new String(""); //第一种
String s2 = ""; //第二种
第一种始终不会入池的.
第二种要看情况而定(等号右边如果是常量则入池,非常量则不入池)
String s3 = "a" + "b"; //"a"是常量,"b"是常量,常量+常量=常量,所以会入池.
String s4 = s1 + "b"; //s1是变量,"b"是常量,变量+常量!=常量,所以不会入池.
一旦入池的话,就会先查找池中有无此对象.如果有此对象,则让对象引用指向此对象;如果无此对象,则先创建此对象,再让对象引用指向此对象.
例如:
String s5 = "abc"; //先在池中查找有无"abc"对象,如果有,则让s5指向此对象;如果池中无"abc"对象,则在池中创建一个"abc"对象,然后让s5指向该对象.
String str ="a"+"b"完全等同于String str="ab";
下面有个网上的例子:
public class Mud {
public static String hello(String[] strs, String s2) {
strs[0] = "<" + strs[0] + ">";
s2.toUpperCase();
return s2;
}
/**
* @param args
*/
public static void main(String... args) {
String a = new String("t");
String[] b = new String[] { "t" };
String c = a.intern();
if (a.equals(b[0])) {
System.out.print("1");
}
if (b[0] == c) {
System.out.print("2");
}
if (a == c) {
System.out.print("3");
}
a = hello(b, c);
System.out.print(a);
System.out.print(b[0]);
System.out.print(c);
}
}
那么它的结果是:12t<t>t
大家可以分析分析!来夯实基础吧!
下面我们来讲一讲hashcode的这个东西!我们翻看了api文档 他是这么说的:
hashCode()
返回该对象的哈希码值。那么我们要知道什么事哈希吗呢?
我们首先看了object的文档中hashcode:
hashCode
public int hashCode()
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常规协定是:
· 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
· 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
· 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
返回:
此对象的一个哈希码值。
这么一大堆文字到底告诉了我们什么呢?那么我们主要看红色字的意思 也就是说 两个对象是相等的,那么两个对象的hashcode返回的值一定是相等的,如果两个对象不是相同的对象 那么它说不要求hashcode生成不同的结果 那么就是说存在允许两个不同对象生成相同的hashcode 那么为什么呢!为什么是这样定义的呢!如果你知道hash是根据一个算法来的 散列表 是存在相同的可能性的!这里就不在细细的为大家讲解了!
由上述的结论我们来看下面的代码以及执行的结果:
StringTest a=new StringTest();
StringTest b=new StringTest();
System.out.println(a.hashCode());
System.out.println(b.hashCode());
System.out.println((a.hashCode()==b.hashCode()));
结果:
12677476
33263331
False
那么这里的运行结果是符合上述所说的原理的!两个不同对象的hashcode是不同的 输出了false
下面我们在来看以下的代码:
String str="abc";
String str2="abc";
String str1=new String("abc");
System.out.println(str.hashCode());
System.out.println(str2.hashCode());
System.out.println(str1.hashCode());
System.out.println((str.hashCode()==str2.hashCode()));
System.out.println((str.hashCode()==str1.hashCode()));
System.out.println((str1.hashCode()==str2.hashCode()));
结果:
96354
96354
96354
true
true
true
大家看到这个结果我们应该怎么解释呢?为什么都是一样的 在前面我们已经知道了一些知识,所以str和str2指向的是常量池里的一个对象 所以他们的hashcode相等 我们可以理解 但是 str和str1 以及str2和str1分明指向的不是一个对象啊!为什么会出现相w同的hashcode呢?难道是api文档中所学的可能性么?那么你们可以在程序中多次尝试 如果你尝试了 你就会发现 事实上当我们尝试了很多次以后我们发现他们的hashcode还是相等的 为什么呢?
那么我们再次来查询api文档,我们知道了object中存在了这种方法 那么我们的祖先都存在这种方法 那么我们会发问为它的后代是不是也会继承这种方法或是会重写了这种方法呢?
我们上面的程序是针对String来调用hashcode来操作的 我们就自爱api中查询String这个类吧!
查询之后我们会惊喜的发现 String中定义了一个hashcode方法 这说明什么呢?这说String这个类没有继承祖先的hashcode方法 而是在祖先的基础上“青出于蓝而胜于蓝”自己定义了(重写)了这个方法 他们它就不是和祖先的方法的思想一样了 而是有自己的定义和自己的思想!
它是这样定义的: hashCode()
返回此字符串的哈希码。
那么我们进入深度定义:
public int hashCode()
返回此字符串的哈希码。String
对象的哈希码根据以下公式计算:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
使用 int
算法,这里 s[i]
是字符串的第 i 个字符,n
是字符串的长度,^
表示求幂。(空字符串的哈希值为 0。)
覆盖:
返回:
此对象的哈希码值。
另请参见:
Object.equals(java.lang.Object)
, Hashtable
???我们会发现Stiing中的hashcode的方法的实现和object中的完全不一样 他是根据一个算法来计算出来的数值。它是根据字符串来算出的 注意这里的字符串!
那么我们就不能理解了看一下代码:
String str="abc";
String str2="abc";
String str1=new String("abc");
System.out.println(str);
System.out.println(str2);
System.out.println(str1);
System.out.println(str.hashCode());
System.out.println(str2.hashCode());
System.out.println(str1.hashCode());
StringTest a=new StringTest();
StringTest b=new StringTest();
System.out.println(a.toString());
System.out.println(b.toString());
System.out.println(a.hashCode());
System.out.println(b.hashCode());
他们结果是:
abc
abc
abc
96354
96354
96354
test.StringTest@c17164
test.StringTest@1fb8ee3
12677476
33263331
对于字符串类型来说 他们的String的字符串值都是“abc”所以他们调用的是String.hashCode()的根据字符串算出的哈希值
所以他们是一样的 而大家要注意
System.out.println(a.toString());
System.out.println(b.toString());
System.out.println(a.hashCode());
System.out.println(b.hashCode());
红色的部分其实在这里是没有说明作用的 因为绿色的的部分的hashcode()方法不是String的方法 而是object的 所以其实他们不是根据String的实现思想来得到哈希值的 所以在一度程度上a的哈希值是有机率等于b的哈希值的!
所以哈希值相等 对象就是同一个对象 这句话是有歧义的 我们应该说object的hashcode()得到的哈希值相等,基本上可以说是同一个对象(也存在几率) 这里这样说是因为String的hashcode的计算方式和object的计算方式是不一样的!
其实到底有什么不一样我们只能看到表面而java中是怎样具体规定的 我们不得其知!
深入api文档中的继承、接口 重写 重构、多态的定义和意义!又便于我们分析问题!快速得到答案!
在实际应用中,经常要对字符串做某些操作,比如字符串的拼接。下面代码演示了关于字符串拼接的过程。
String s4 = "hello";
String s5 = "world";
String s6 = s4 + s5;
为了得到s6,对s4和s5做了拼接,结果不仅在strings pool里生成了内容分别为hello和world的两个对象,又在堆里生成了一个新的对象,它的内容为helloworld,这个新对象不会在constant table中保存引用,但在我们的代码里,用s6来引用它。拼接后的两个对象(s4和s5),如果不重用,就没有存在的必要了,但是Java仍然要在 constant table 中维护它们的引用,它们在堆里也要占用空间,系统开销就出现了,而当strings pool里的此类对象过多时,必然要影响程序的性能。这还有一个问题,执行下面的代码
s4=null;
s5=null;
这样gc是否会回收s4和s5引用的对象呢?当然不能,因为在constant table中还保留一个我们看不到的对这两个对象的引用,所以gc无能为力了。
为了解决"垃圾"对象泛滥的问题,我们需要一个可变的字符串对象,操作该对象而不导致新的对象产生,只是让该对象做些适当的改变而已。StringBuffer类就这样应运而生了。
至此!谢谢 !