关于HashSet、LinkedHashSet和TreeSet以及Comparable和Comparator
2017-03-31 23:04
471 查看
一、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的底层实现
HashSet的底层用的是HashMap,当用户创建HashSet实例时,会为该实例创建一个HashMap类型的成员变量。对HashSet实例的操作,4000
其实都是在对该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 的一个外部比较器。
一个对象不支持自己和自己比较(没有实现Comparable接口),但是又需要对两个对象进行比较时,可以构造一个实现Comparator接口的外部比较器。比如需要将该类的对象存入Set中时。
一个对象实现了Comparable接口,但是开发者认为
compareTo()方法中的比较方式并不是自己想要的那种比较方式,可以构造一个实现Comparator接口的外部比较器。
实现Comparable接口的方式比实现Comparator接口的耦合性要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修改。
在具体场景下选择最合适的那种比较器。
相关文章推荐
- 黑马程序员------TreeSet集合框架存储自定义元素之排序Comparable与Comparator
- HashSet、LinkedHashSet、TreeSet+JAVA学习笔记-DAY17
- HashSet 与TreeSet和LinkedHashSet的区别
- 关于comparator接口和comparable接口以及它们各自的方法compare()和compareTo()
- 关于comparator接口和comparable接口以及它们各自的方法compare()和compareTo()
- 黑马程序员-java之TreeSet,Comparable,Comparator
- 关于comparator以及comparable的区别及应用
- List.HashMap.TreeMap.HashSet,TreeSet以及集合排序
- HashSet、LinkedHashSet和TreeSet三者区别与联系
- Java集合框架:Set(HashSet,LinkedHashSet,TreeSet)
- TreeSet(有序集合)对Comparable元素的排序(或使用Comparator)与元素equals方法的关系
- JAVA菜鸟入门(17) 有排序的TreeSet和无排序的HashSet, LinkedHashSet
- HashSet、LinkedHashSet和TreeSet三者区别与联系
- set集合 HashSet: TreeSet实现Comparable接口,覆盖compareTo方法。 类实现Comparator接口,覆盖compare方法。
- Java之集合框架 Set集合的特性以及LinkedHashSet的特性
- ArraList和linkedList的区别及优缺点以及Comparable与Comparator的区别
- Java集合框架:Set(HashSet,LinkedHashSet,TreeSet)
- 黑马程序员-Set-HashSet-TreeSet-Comparator-Comparable
- HashSet的hashCode方法和equals方法的重写,TreeSet中compareTo方法的重写,Comparator在treeSet中的应用。
- HashSet 与TreeSet和LinkedHashSet的区别