黑马程序员——ArrayList&HashSet&Hashcode的学习总结
2014-04-24 10:53
495 查看
直观上来说,ArrayList不判断集合内的元素的重复性,HashSet会保持集合元素的唯一性,即如果有重复的元素,则后一个重复的元素就不会加入到HashSet集合里面,如下所示:
如果用ArrayList集合来存放元素,最后的输出结果为4,如果用HashSet,输出结果为3。
这是因为在HashSet中,test1已经存在了,如果再用add()方法加入一个test1,则这个test1为重复对象,不会加入到集合中。
那HashSet是通过什么方法判断元素是否重复呢?是通过equals()方法。在没有重写equals()方法前,==和equeals()这两个比较的方法是一样的,都是比较内存的引用地址,如果引用地址都一样,则两个元素相同,对于HashSet集合来说,如果两个元素的equals()为true,则后一个元素不会被加入到集合中。当然,如果是一个一个的和集合中已有的元素进行比较,那效率就太低了,所以这里就用到了Hashcode,如下图所示:
HashSet把集合分成了不同的区域,每个区域内存放的是拥有相似hashcode的元素,如果有新元素需要加入集合,则只要在新元素对应的hashcode区域内找有没有重复元素即可,查找的效率自然就提高了。
如果想把值相同但引用不同的对象也当成是同一对象来看待的话,可以重写equals()方法,在eclipse中通过快捷生成方式覆盖equals()方法:
这样输出的结果就为2了,因为重写了equals()方法,test1和test2的x,y值相同,所以就被当成是同一元素,test2便不会加入到HashSet集合中。
这里还需要注意两个问题:
1:==和equals()的区别
上面已经说了,对应没有重写equals()的类,==和equals()比较的都是指向对象的内存引用地址,两者比较的结果是一样的。而String类默认重写了这个方法,用equals()比较两个字符串时是比较字符串的内容而非引用地址,所以,对于如下代码:
如果用==比较的话结果会是false,用equals()比较的话结果是true。但特别的:
这个无论是==还是equals()结果都是true,因为s1与s2分别指向由字符串常量”123” 创建的对象,在常量池中,只有一个对象,内容为123,有两个引用s1和s2指向这个对象,故这两个引用变量所指向的地址是相同的,所以无论是值比较还是引用比较都是true。
2:JAVA的内存泄漏问题
工作中接触到这个问题的时候会有一个疑问——JAVA有GC自动垃圾回收机制,不用的对象会自动销毁,内存空间也会被释放,那么还会出现内存泄漏的问题么?
答案是会的,而且正因为有了GC,所以JAVA的内存泄漏变得更隐蔽更难发现,连GC都不能回收这些引起泄漏的内存地址,程序员来找不就更麻烦了么?刚好对于HashSet这个集合,如果操作不当就会引起内存泄漏。
public static void main(String[] args) {
HashSet arr = new HashSet();
// ArrayList arr = new ArrayList();
Test33 test1 = new Test33(1, 1);
Test33 test2 = new Test33(2, 2);
Test33 test3 = new Test33(3, 3);
arr.add(test1);
arr.add(test2);
arr.add(test3);
test1.x=2;//改变了test1中的x的值
arr.remove(test1);//集合中删除test1
System.out.println(arr.size());
}
}
class Test33 {
int x;
int y;
public Test33(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Test33 other = (Test33) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
如上所示,重写了equals()方法,让其比较的是两个对象中x和y的数值而非引用地址,然后创建了三个不同的实例对象,x,y的值都不一样,加入到HashSet中,这时集合中有三个元素,size()的结果为3,然后把其中一个对象的x值改变,再在HashSet中把这个对象移除remove()掉,按理说这时size()的值应该为2,但实际运行结果为3,说明test1这个对象没有被删除,为什么呢?
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
根据类中重写的这个方法,hashcode的值用到了x和y的值,而x的值一旦改变了,hashcode的值肯定也改变,根据上面所说的,HashSet集合是按照不同的hashcode值分成不同的区域来存放元素,当一个元素的hashcode值改变后再移除这个元素,HashSet集合就会在这个元素的新的hashcode值所对应的区域中寻找,当然找不到这个元素,结果也就移除不了了。但这个元素还是实实在在的存在在HashSet集合中,存在在内存中,已经用不着了但又删除不掉,这边会造成内存的泄漏,而改变集合中某个元素的值的操作又是何其普遍,如果真的出现问题,GC机制肯定是帮不上忙,程序员要找问题所在也会陷入巨大的麻烦中。
public static void main(String[] args) { HashSet arr = new HashSet(); // ArrayList arr = new ArrayList(); Test33 test1 = new Test33(1, 1); Test33 test2 = new Test33(1, 1); Test33 test3 = new Test33(1, 2); arr.add(test1); arr.add(test2); arr.add(test3); arr.add(test1); System.out.println(arr.size()); } } class Test33 { int x; int y; public Test33(int x, int y) { super(); this.x = x; this.y = y; } }
如果用ArrayList集合来存放元素,最后的输出结果为4,如果用HashSet,输出结果为3。
这是因为在HashSet中,test1已经存在了,如果再用add()方法加入一个test1,则这个test1为重复对象,不会加入到集合中。
那HashSet是通过什么方法判断元素是否重复呢?是通过equals()方法。在没有重写equals()方法前,==和equeals()这两个比较的方法是一样的,都是比较内存的引用地址,如果引用地址都一样,则两个元素相同,对于HashSet集合来说,如果两个元素的equals()为true,则后一个元素不会被加入到集合中。当然,如果是一个一个的和集合中已有的元素进行比较,那效率就太低了,所以这里就用到了Hashcode,如下图所示:
HashSet把集合分成了不同的区域,每个区域内存放的是拥有相似hashcode的元素,如果有新元素需要加入集合,则只要在新元素对应的hashcode区域内找有没有重复元素即可,查找的效率自然就提高了。
如果想把值相同但引用不同的对象也当成是同一对象来看待的话,可以重写equals()方法,在eclipse中通过快捷生成方式覆盖equals()方法:
public static void main(String[] args) { HashSet arr = new HashSet(); // ArrayList arr = new ArrayList(); Test33 test1 = new Test33(1, 1); Test33 test2 = new Test33(1, 1); Test33 test3 = new Test33(1, 2); arr.add(test1); arr.add(test2); arr.add(test3); arr.add(test1); System.out.println(arr.size()); } } class Test33 { int x; int y; public Test33(int x, int y) { super(); this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Test33 other = (Test33) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } }
这样输出的结果就为2了,因为重写了equals()方法,test1和test2的x,y值相同,所以就被当成是同一元素,test2便不会加入到HashSet集合中。
这里还需要注意两个问题:
1:==和equals()的区别
上面已经说了,对应没有重写equals()的类,==和equals()比较的都是指向对象的内存引用地址,两者比较的结果是一样的。而String类默认重写了这个方法,用equals()比较两个字符串时是比较字符串的内容而非引用地址,所以,对于如下代码:
String s1 = new String("123"); String s2 = new String("123");
如果用==比较的话结果会是false,用equals()比较的话结果是true。但特别的:
String s1 = "123"; String s2 = "123";
这个无论是==还是equals()结果都是true,因为s1与s2分别指向由字符串常量”123” 创建的对象,在常量池中,只有一个对象,内容为123,有两个引用s1和s2指向这个对象,故这两个引用变量所指向的地址是相同的,所以无论是值比较还是引用比较都是true。
2:JAVA的内存泄漏问题
工作中接触到这个问题的时候会有一个疑问——JAVA有GC自动垃圾回收机制,不用的对象会自动销毁,内存空间也会被释放,那么还会出现内存泄漏的问题么?
答案是会的,而且正因为有了GC,所以JAVA的内存泄漏变得更隐蔽更难发现,连GC都不能回收这些引起泄漏的内存地址,程序员来找不就更麻烦了么?刚好对于HashSet这个集合,如果操作不当就会引起内存泄漏。
public static void main(String[] args) {
HashSet arr = new HashSet();
// ArrayList arr = new ArrayList();
Test33 test1 = new Test33(1, 1);
Test33 test2 = new Test33(2, 2);
Test33 test3 = new Test33(3, 3);
arr.add(test1);
arr.add(test2);
arr.add(test3);
test1.x=2;//改变了test1中的x的值
arr.remove(test1);//集合中删除test1
System.out.println(arr.size());
}
}
class Test33 {
int x;
int y;
public Test33(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Test33 other = (Test33) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
如上所示,重写了equals()方法,让其比较的是两个对象中x和y的数值而非引用地址,然后创建了三个不同的实例对象,x,y的值都不一样,加入到HashSet中,这时集合中有三个元素,size()的结果为3,然后把其中一个对象的x值改变,再在HashSet中把这个对象移除remove()掉,按理说这时size()的值应该为2,但实际运行结果为3,说明test1这个对象没有被删除,为什么呢?
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
根据类中重写的这个方法,hashcode的值用到了x和y的值,而x的值一旦改变了,hashcode的值肯定也改变,根据上面所说的,HashSet集合是按照不同的hashcode值分成不同的区域来存放元素,当一个元素的hashcode值改变后再移除这个元素,HashSet集合就会在这个元素的新的hashcode值所对应的区域中寻找,当然找不到这个元素,结果也就移除不了了。但这个元素还是实实在在的存在在HashSet集合中,存在在内存中,已经用不着了但又删除不掉,这边会造成内存的泄漏,而改变集合中某个元素的值的操作又是何其普遍,如果真的出现问题,GC机制肯定是帮不上忙,程序员要找问题所在也会陷入巨大的麻烦中。
相关文章推荐
- 黑马程序员--11集合类的学习List&Hash&Array)Set&泛型
- STL学习——STL中的关联式容器总结(RB-tree、set、map、hashtable、hash_set、hash_map)
- JAVA学习.JAVA集合类型Collection.Set.HashSet&TreeSet
- 黑马程序员---看ArrayList_HashSet的比较及Hashcode分析视频查阅的资料总结
- 黑马程序员_JavaSE基础17 之 集合框架 Vector LinkedList ArrayList HashSet LinkedHasSet TreeSet
- EasyDemo*HashSet&&TreeSet特有方法(个人总结)
- Java-Collections Framework学习与总结-HashSet和LinkedHashSet
- 黑马程序员-c语言学习逻辑与&&、逻辑或||、逻辑非!总结
- 字符串HASH 学习总结 && 模板
- ArrayList、List<T>、HashSet<T>、LinkedList<T>各自优点和缺点,Dictionary<K,V>的内部存储数据方式有什么特殊的?
- 字符串HASH 学习总结 && 模板
- 黑马程序员:Java基础总结----子接口 set<E>及其实现类
- 容器Collection的总结一 Vector ArrayList LinkedList HashSet TreeSet
- 黑马程序员_学习笔记:9) 集合框架1:Collection(List、Set)、Iterator、List(ArrayList、LinkedList、Vector)
- 黑马程序员 第29天 List ArrayList HashSet TreeSet
- 黑马程序员_<<Set,HashSet>>
- 【黑马程序员】银行业务调度系统学习总结
- J2SE学习笔记:Set集合中相等性的比较,hashCode()与equals()
- 黑马程序员_学习笔记第14天集合(一)_List、Set
- 黑马程序员_java 集合框架学习总结