Set作为Collection的子接口,没有添加额外的方法,它存储无序的不可重复的数据,在开发中使用比较少,后面会看Map的源码,会了Map,Set就会了。所以Set的重点是理解它的无序性和不可重复性。
为了保证无序性和不可重复性,我们要求添加到Set中的类要重写equals()和hashCode().重写的equals()和hashCode()要保证一致性:相等的对象(equals)必须具有相同的hash值(equals), 这样就可以使我们规定的相等的元素不会重复插入到Set中。
hashSet: 作为Set接口的主要实现类;是一个线程不安全的;它可以存储 null。底层使用数组+链表实现的。
LinkedHashSet: 是hashSet的子类;遍历内部数据时,可以按照添加的顺序去遍历。
TreeSet: 可以按照添加元素指定的属性排序,所以要求放入同一个treeSet的对象必须是同一个类new出来的。
以hashSet为例。当我们向hashSet中添加元素a时,首先先调用a元素所在类的hashCode()方法,计算出元素a的hash值,根据这个hash值,通过某种算法来确定该元素在hashSet底层数组中的一个存放位置。不同的hashCode通过这种算法可能会被放在同一个位置;如果这个位置上没有其他元素,则直接添加进去,若这个位置上有元素b(或以链表形式的多个元素),则比较a与该链表上所有元素的hash值;如果没有与a相等的hash值的元素,则添加进去,若有相同的hash值的元素,则调用a所在类的equals()方法;如果equals()返回true,则添加失败,返回False,则添加成功。
在jdk8中,添加到链表采用的是尾插法,而jdk7采用的是头插法。
var set = new HashSet(); set.add(new Person("tom",12)); set.add(new Person("tom",12)); Iterator it = set.iterator(); while(it.hasNext()){ System.out.println(it.next()); }在没有重写Person类的hashCode()方法时,它调用的时Object的hashCode方法,这个方法是native,底层是C语言,我们不可见,它返回的是对象的存储地址。
是hashSet的子类, 它也是无序,不可重复的。只不过它在添加数据时,还维护了两个变量,记录这个数据的前一个和后一个数据,使得它在遍历的时候能够按添加的顺序来遍历。
优点:对于频繁的遍历操作,LinkedHashSet效率高于hashSet。
TreeSet的底层是红黑二叉树。TreeSet创造出来就是为了”按照添加元素指定的属性排序“这个功能,所以放入同一个treeSet的对象必须是同一个类new出来的。有两种排序的方式,自然排序(实现Comparable接口)和定制排序(Comparator)。
如果我们使用自然排序的话,我们就要实现Comparable接口,也就是要实现compareTo()。 在自然排序中,比较两个对象相等的标准为compareTo()返回0,而不是equals()
下面是一个例子:
public class Person implements Comparable { private String name; private int age; ............ //按照name从大到小排序,age作为二级比较,从小到大排 @Override public int compareTo(Object o) { if (o instanceof Person){ Person person = (Person)o; int compare = -this.name.compareTo(person.name); if(compare != 0){ return compare; } else { return Integer.compare(this.age, person.age); } } else{ throw new RuntimeException("输入类型不匹配"); } } }我们创建一个Comparator对象,在这个对象里重写compare()方法,之后在声明TreeSet的时候,把这个对象作为参数传入。
在定制排序中,比较两个对象相等的标准为compare返回0,而不是equals()。
Comparator com = new Comparator() { //只考虑年龄,按小到大排序 @Override public int compare(Object o1, Object o2) { if(o1 instanceof Person && o2 instanceof Person){ Person p1 = (Person) o1; Person p2 = (Person) o2; return Integer.compare(p1.getAge(),p2.getAge()); }else { throw new RuntimeException("输入的数据类型匹配"); } } }; TreeSet set = new TreeSet(com); set.add(new Person("luca",20)); set.add(new Person("lucb",20));