关于HashSet、LinkedHashSet和TreeSet以及Comparable和Comparator

一、HashSet和LinkedHashSet基础

1 . Set中的元素在底层存储的位置是无序的;
2 . Set中的元素是不可重复的;

 String str1 = new String("abc");
 String str2 = new String("abc");
 //要求对象各属性值均不同。即使是new的对象,只要属性值相同,也视为重复的。如str1和str2
 //因为String重写了Object的hashcode()方法,根据属性值计算hash值,因此属性值相同,hash值就相同。

3 . 因此,要求存入HashSet中的自定义对象要重写hashCode()和equal()方法,保证Set中元素的不可重复性;
当向HashSet中添加对象时,会首先调用对象的hashcode()方法,计算此对象的hash值,此hash值决定了此对象存在HashSet中的位置。若此位置之前没有对象,则该对象直接存储到此位置。若此位置已有对象,再通过equal()方法比较两个对象是否相同。若相同,后一个对象就不能再添加进去。(要求最好保证hashCode比较的结果要与equal比较的结果一致)

class Person{
    int age;
    String name;
    //常用重写hashCode的方法(可在IDE自动生成)
    public int hashCode(){
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
}

4 . 使用哈希算法,降低了比较的复杂性,若不使用哈希算法,每次向HashSet中存入元素,都要与HashSet中已有的所有元素进行比较,过于复杂。
5 . HashSet按hash算法来存储集合中的元素,因此具有很好的存取和查找性能。但是不能保证元素的排列顺序。

6 . LinkedHashSet是HashSet的子类,在根据hashCode值决定元素存储位置的同时,使用链表为元素建立前向和后向索引,来维护元素的词序,这使得元素是以插入顺序保存的。
7 . 因此,LinkedHashSet的插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。

二、关于HashSet的底层实现

  1. HashSet的底层用的是HashMap,当用户创建HashSet实例时,会为该实例创建一个HashMap类型的成员变量。对HashSet实例的操作,其实都是在对该HashMap成员变量操作。如下源码所示:
private transient HashMap<E,Object> map;//HashMap成员变量
private static final Object PRESENT = new Object();//map的value存放一个静态的Object常量

//构造器中初始化map
public HashSet() {
    map = new HashMap<>();
}

//对HashSet的操作内部封装的都是对map的操作
//map.put()返回的是key先前对应的value,返回null,则说明先前map中没存放该k-v对,set.add成功,返回true。
//否则,返回的value为PRESENT,则set.add失败,返回false,这说明map中先前存在该k-v对。
//(实际上map中依然成功添加了该key-value对,只不过与k-v值未发生变化)
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

//同理,HashSet的迭代器,调用的的是对应map的keySet的迭代器。HashSet中的值存放在对应map的key中。
public Iterator<E> iterator() {
    return map.keySet().iterator();
}

//更多信息可查看HashSet的API

三、关于LinkedHashSet的底层实现

1 . LinkedHashSet作为HashSet的子类,并没有增加任何新的方法。只提供了四个构造方法,且内部都是调用了父类HashSet的带boolean标识符的构造方法。

//LinkedHashSet的构造方法调用父类的带标参的构造方法。
public LinkedHashSet() {
    super(16, .75f, true);
}

//hashSet的带标识符参数的构造方法,该方法为对象实例化一个LinkedHashMap变量。
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

2 . 因此,可知LinkedHashSet中的元素其实是存储在其内部的LinkedHashMap实例属性中。

四、关于TreeSet

1 . TreeSet中存储的元素会自动按大小顺序排列。String、包装类等默认按从小到大的顺序。(字符串升序)
2 . 存入TreeSet中元素会自动比较大小,因此,TreeSet中存放的元素必须是同一类型。且自定义类必须实现comparable接口或者使用TreeSet(Comparator<? super E> comparator)构造方法传入所存放类型的外部比较器,否则向TreeSet中添加该类的对象时,会报ClassCastException(类型转换异常)。

3 . 自定义类重写comparable接口的compareTo( )方法时,可以指定按照自定义类某个属性排序。

public class People implements Comparable<People>{
    private String name;
    private int age;

    //若在重写的compareTo()方法中只比较对象的某个属性,则只要该属性相同,程序就认为两个对象是重复的。
    //因此,在compareTo()方法中一般要求比较对象的所有属性,保证compareTo()的结果与hashCode()和equal()一致。
    @Override
    public int compareTo(People o) {
        int result = this.name.compareTo(o.name);//只比较了name属性
        return result;
    }

五、关于TreeSet的底层实现

1 . TreeSet内部定义了一个NavigableMap类型的接口变量。在调用构造器生成TreeSet实例时,都是实例化了一个TreeMap对象,并回调给NavigableMap接口变量。因此,TreeSet底层使用的是TreeMap来存储数据,与HashSet底层使用的是HashMap类似。

private transient NavigableMap<E,Object> m;//私有NavigableMap变量

//TreeSet的其他构造方法均在内部调用该构造方法,将TreeMap实例回调给NavigableMap接口变量
TreeSet(NavigableMap<E,Object> m) {
    this.m = m;
}

//内部调用带参构造方法
public TreeSet() {
    this(new TreeMap<E,Object>());
}
//内部调用带参构造方法,TreeSet的其他构造方法都类似,具体可查看API
public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

六、Comparable和Comparator

Comparable可以认为是一个内比较器,一个类通过实现Comparable接口,在内部重写compareTo()方法,来定义如何比较该类的两个对象。
Comparator可以认为是是一个外比较器,一个类通过实现泛型接口Comparator< T >,重写compare(T o1, T o2)方法来定义如何比较两个 T 类的对象。实现Comparator< T >接口的类,可以认为是 T 类的一个专门的比较器类,是类 T 的一个外部比较器。

  1. 一个对象不支持自己和自己比较(没有实现Comparable接口),但是又需要对两个对象进行比较时,可以构造一个实现Comparator接口的外部比较器。比如需要将该类的对象存入Set中时。

  2. 一个对象实现了Comparable接口,但是开发者认为compareTo()方法中的比较方式并不是自己想要的那种比较方式,可以构造一个实现Comparator接口的外部比较器。

  3. 实现Comparable接口的方式比实现Comparator接口的耦合性要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。

  4. 在具体场景下选择最合适的那种比较器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值