Set集合中的元素是无序的,不可重复的。这个接口下有两个常用集合的实现,HashSet和TreeSet。
HashSet
HashSet底层用的是哈希表,它把对象根据其哈希值存放到对应的区域里。由于这种特性,两个在不同区域的对象会被认为不相同的。
所以如果对象要存放到Hash集合里面,则需要重写对象的hashCode方法,让相等的对象的hashCode的值也相等。
TreeSet
TreeSet采用的数据结构是红黑树,我们可以让它按指定规则对其中的元素进行排序。它又是如何判断两个元素是否相同呢?除了用equals方法检查两个元素是否相同外,还要检查compareTo方法是否返回为0。
所以如果对象要存放到Tree集合里,需要在重写compareTo时,把相同的对象的比较值定为0,防止相同的元素被重复添加进集合中。
面试分享:
HashSet在源代码内是如何实现不add相同的元素的?
理论上我们都知道,它的内部是根据equals()返回值和hashCode()的值是否相同两个方法来判断两个对象是否相同的。而源代码上,HashSet内部用了一个HashMap作存储,add()方法内是调用了map的put()方法,map的put()方法会检查这个键是否已存在,若是则返回该键之前的值,并更新成新值,若不是则返回null,但这个键是不会发生改变的。所以,在源代码里,HashSet的add()方法是根据map的put返回值来判断添加元素是否成功的。
public boolean add(E e ) {
return map.put(e, PRESENT)== null;
}
HashSet导致的内存泄漏
延伸一下,hash集合在操作不当的情况下,有可能造成内存泄漏。
考虑这样的情况,把一个对象存储进hashSet集合后,修改这个对象中参与计算hash的变量的值,这时这个对象的hash值也会随之改变,那么这么对象可以正常地被删除吗?下面用代码试一下:
先自定一个MPoint类,其中有两个变量,x和y,其中x参与计算hash值
public class MPoint {
private int x;
private int y;
public MPoint() {
}
public MPoint( int x, int y) {
this. x = x;
this. y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this. x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this. y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x; //x参与计算hash值
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj)
return true;
if ( obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MPoint other = (MPoint) obj;
if ( x != other. x)
return false;
if ( y != other. y)
return false;
return true;
}
}
public class HashSetTest {
public static void main(String[] args) {
HashSet<MPoint> set = new HashSet<MPoint>();
MPoint mp1 = new MPoint(1, 6);
MPoint mp2 = new MPoint(2, 7);
MPoint mp3 = new MPoint(1, 6);
set.add( mp1);
set.add( mp2);
set.add( mp3);
set.add( mp1);
System. out.println( set.size()); // 结果为2
mp1.setX(3);
set.remove( mp1);
System. out.println( set.size()); // 结果还是为2,说明没有删除成功
System. out.println( set.contains( mp1)); // 结果为false,修改了参与计算hash值的变量,其对象不能再被找到
System. out.println( set.remove( mp1)); // 结果为false,修改了参与计算hash值的变量,其对象不能被删除
mp2.setY(2);
System. out.println( set.contains( mp2)); // 结果为true,没有修改关键属性的对象可以被找到
System. out.println( set.remove( mp2)); // 结果为true,没有修改关键属性的对象可以被删除
System. out.println( set.size()); // 结果还是为1
}
}
输出结果如下:
2
2
false
false
true
true
1
可以看出已经发生了内存泄漏了,mp1对象不能被正常删除。
总结:Java中也有内存泄漏发生,这个知识点或许在面试中会很有用,一定要能说出内存泄漏发生的场景这样的代码细节,才算是真正碰到过。。