27-集合--Set及其子类(HashSet+LinkedHashSet+TreeSet)+二叉树+Comparable+Comparator+哈希表+HashSet存储自定义对象+判断元素唯一的方式

一、Set

1、Set:元素不可以重复,是无序的(存入和取出的顺序不一致)

2、Set接口中的方法和Collection中的方法一致

3、Set集合的元素取出方式只有一种:迭代器iterator()

        Set set = new HashSet();
        Iterator it = set.iterator();
        while (it.hasNext()) {
            it.next();
        }

4、Set接口有两个子类

(1)HashSet:内部数据结构是哈希表(哈希表底层还是数组),是不同步的

(2)TreeSet:内部数据结构是二叉树,是不同步的(可以对Set集合中的元素排序。存取顺序不一致,但有指定顺序)

注:LinkedHashSet:元素唯一(哈希表),元素有序(链表)

二、HashSet

1、HashSet由哈希表(实际上是一个HashMap实例)支持,它不保证迭代顺序(无序)。因为底层的存储方式是通过算法来完成的

2、Set进行迭代所需的时间与HashSet实例的大小(元素的数量)和底层HashMap实例(桶的数量)的“容量”的和成比例。因此,如果迭代性能很重要,则不要将初始容量设置的太高(或将加载因子设置的太低)

3、HashSet用来保证元素唯一(重复的元素存不进去)。需要唯一才使用HashSet,不需要唯一应该选择ArrayList

注:需要唯一就用Set,无所谓就用List(List使用较多)。List和Set最大的区别在于元素是否唯一

4、HashSet判断元素唯一,需要覆盖hashCode()和equals()方法,建立自己的判断唯一的依据

5、无论是contains()还是remove(),关键问题是判断元素是否相同,即容器中是否有和该元素相同的元素。而容器判断相同的依据,根据容器的数据结构而定

(1)ArrayList:判断equals()

(2)HashSet:判断hashCode() + equals()    (哈希表中必须首先判断hashCode())

6、构造函数

(1)HashSet():构造一个新的空set,其底层HashMap实例的默认初始容量是16,加载因子是0.75

(2)HashSet(Collection<? extends E> c):构造一个包含指定collection中的元素的新set

(3)HashSet(int initialCapacity):构造一个新的空set,其底层HashMap实例具有指定的初始容量和默认的加载因子(0.75)

(4)HashSet(int initialCapacity, float loadFactory):构造一个新的空set,其底层HashMap实例具有指定的初始容量和指定的加载因子

三、LinkedHashSet

1、LinkedHashSet:具有可预知迭代顺序的Set接口的哈希表和链接列表实现

2、唯一+有序。LinkedHashSet可以让客户免遭未指定的、由HashSet提供的通常杂乱无章的排序工作,而又不致引起与TreeSet关联的成本增加。使用它可以生成一个与原来顺序相同的set副本,并且与原set的实现无关,然后返回由此副本决定了顺序的结果(客户通常期望内容返回的顺序与它们出现的顺序相同)

四、TreeSet

1、TreeSet:基于TreeMap的NavigableSet实现。使用元素的自然顺序对元素进行排序(Comparable),或者根据创建set时提供的Comparator进行排序,具体取决于使用的构造方法

2、TreeSet可以对集合中的元素进行排序,是不同步的

3、TreeSet集合是Set集合的子类,也可以保证元素的唯一性。TreeSet判断元素唯一的方式:根据比较方法(compareTo()或compare())的返回结果是否为0。为0,就是相同元素,不存储

注:TreeSet集合和hashCode()、equals()没有关系

4、TreeSet无序(存入和取出顺序不同),但有自己的排序方式。TreeSet对元素进行排序的方式(两种):

(1)让元素自身具备比较功能。元素需要实现Comparable接口,覆盖CompareTo()方法 -- 元素的自然排序

(2)让集合自身具备比较功能。定义一个类,实现Comparator接口,覆盖compare()方法。将该类对象(接口Comparator的子类对象)作为参数传递给TreeSet集合的构造函数 -- 比较器

注:TreeSet集合中的元素要按照指定方式排序,需要进行比较,必须要让元素自身或者集合自身具备比较功能。在实际开发中,比较器(Comparator)比较常用。因为它能避免一些弊端和不足,eg:Person本身具备比较功能A,但不是想要的,此时就必须使用比较器定义另一个比较功能B

例:字符串本身实现按元素的字典顺序排序(Java写好的,无法修改),若现在需要按字符串长度排序,就只能使用Comparator比较器

public class Test {

    public static void main(String[] args) {

        TreeSet ts = new TreeSet(new ComparatorByLength());
        ts.add("aaaaa");
        ts.add("zz");
        ts.add("nbaq");
        ts.add("cba");
        ts.add("abc");
        System.out.println(ts); //[zz, abc, cba, nbaq, aaaaa]

        for (Iterator it = ts.iterator(); it.hasNext(); ) {
            System.out.println(it.next());
        }

    }

}

/**
 * 自定义类,实现Comparator接口,覆盖compare()方法
 */
class ComparatorByLength implements Comparator {
    @Override
    public int compare(Object obj1, Object obj2) {
        //......此处省略强转前的健壮性判断
        String s1 = (String) obj1;
        String s2 = (String) obj2;

        int temp = s1.length() - s2.length();
        //如果长度相同,再按元素的字典顺序排序
        return temp == 0 ? s1.compareTo(s2) : temp;
        //如果只按照字符串长度比较,则长度相同的后面元素将会被舍弃
//        return temp;  //ts的值为:[zz, cba, nbaq, aaaaa]
    }
}

5、构造函数

(1)TreeSet():构造一个新的空set,该set根据其元素的自然顺序进行排序。插入该set的所有元素都必须实现Comparable接口。另外,所有这些元素都必须是可互相比较的:对于set中的任意两个元素e1和e2,执行e1.compareTo(e2);都不得抛出ClassCastException

(2)TreeSet(Collection<? extends E> c):构造一个包含指定collection元素的新TreeSet,它按照其元素的自然顺序进行排序。插入该set的所有元素都必须实现Comparable接口。另外,所有这些元素都必须是可互相比较的:对于set中的任意两个元素e1和e2,执行e1.compareTo(e2);都不得抛出ClassCastException

(3)TreeSet(Comparator<? super E> comparator):构造一个新的空TreeSet,它根据指定比较器进行排序。插入到该set的所有元素都必须能够由指定比较器进行相互比较:对于set中的任意两个元素e1和e2,执行comparator.compare(e1, e2);都不得抛出ClassCastException

(4)TreeSet(SortedSet<E> s):构造一个与指定有序set具有相同映射关系和相同排序的新TreeSet

五、二叉树(红黑树)

1、二叉树是一种数据结构,持有左、右、父三个索引

2、用二叉树可以完成排序,并能确定元素的位置。查找顺序:左 - 中 - 右,左边小,右边大,相同的不存储

3、在存储后一个元素时,已有的元素是有序的。对已有的有序元素进行折半(折半查找/二分法查找),再确定新元素的位置,效率较高

4、用二叉树实现TreeSet 正序/倒序 存取(怎么存入就怎么取出,或者怎么存入就倒序取出)

class ComparatorByName implements Comparator {
    /**
     * 用二叉树实现TreeSet 正序/倒序 存取
     * @param obj1
     * @param obj2
     * @return
     */
    @Override
    public int compare(Object obj1, Object obj2) {
        //......此处省略强转前的健壮性判断
        Person p1 = (Person) obj1;
        Person p2 = (Person) obj2;

        //二叉树只看正数、负数和零
        //正序:存入和取出顺序一致
        return 1;
        //倒序:存入和取出顺序相反
//        return -1;
        //若 return 0; ,说明后面添加的元素都和第一个元素相同,全部舍弃
    }

}

六、java.lang.Comparable

1、interface Comparable<T>:此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo()方法被称为它的自然比较方法

2、实现此接口的对象列表(和数组)可以通过Collections.sort()(和Arrays.sort())进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器

3、建议:最好使自然排序和equals()一致。即 (x.compareTo(y) == 0) == (x.equals(y))    ???

4、方法

(1)int compareTo(T o):比较此对象与指定对象的顺序。如果该对象小于、等于、大于指定对象,则分别返回负整数、零、正整数

5、TreeSet集合用Comparable(自然排序)判断元素唯一的方式:根据比较方法compareTo()返回结果是否为0。为0,就是相同元素,不存储

/**
 * 实现了Comparable接口的Person类
 */
class Person implements Comparable {

    private String name;

    private int age;

    //...... 省略 构造方法 和 get、set方法

    /**
     * 覆盖 Comparable 接口的比较方法 compareTo()
     * 下面方法的排序方式:按年龄从小到大排序,如果年龄相同,按名称的字典顺序从前到后
     * 
     * 问题:如果要从大到小排序,如何修改?
     * 逆序。即 改变 this 和 p 两个对象的位置(互换)
     * 
     * @param obj
     * @return
     */
    @Override
    public int compareTo(Object obj) {
        //凡是引用数据类型强转之前,都要进行健壮性判断,否则会发生ClassCastException
        if (!(obj instanceof Person)) {
            throw new ClassCastException();
        }

        Person p = (Person) obj;
        //age是int类型,可以相减
        int temp = this.age - p.age;
        //如果age相同,再比较name
        //String类的默认比较方法compareTo(),按照元素的字典顺序排序 -- 字符串本身就实现了Comparable接口
        return temp == 0 ? this.name.compareTo(p.name) : temp;
    }

}

七、java.util.Comparator

1、interface Comparator<T>:强行对某个对象collection进行整体排序的比较函数。可以将Comparator传递给sort()方法(如Collections.sort()或Arrays.sort()),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序

2、当且仅当对于一组元素S中的每个e1和e2而言,c.compare(e1, e2) == 0与e1.equals(e2)具有相等的布尔值时,Comparator c 强行对S进行的排序才叫做与equals一致的排序

3、通常,让Comparator实现java.io.Serializable。因为它们在可序列化的数据结构(TreeSet、TreeMap)中可用作排序方法。为了成功地序列化数据结构,Comparator(如果已提供)必须实现Serializable

4、方法

(1)int compare(T o1, T o2):比较用来排序的两个参数。根据第一个参数小于、等于、大于第二个参数分别返回负整数、零、正整数

(2)boolean equals(Object obj):指示某个其他对象是否“等于”此Comparator。此方法必须遵守Object.equals(Object)的常规协定。此外,仅当指定的对象也是一个Comparator,并且强行实施与此Comparator相同的排序时,此方法才返回true(判断比较器是否相同)

注:此方法是抽象的,但不重写Object.equals(Object)方法总是安全的(默认继承Object,用覆写的equals()方法即可)。然而,在某些情况下,重写此方法可以允许程序确定两个不同的Comparator是否强行实施了相同的排序,从而提高性能

5、创建集合对象时就应该具备的功能,使用构造函数(对象是在添加的时候进行比较的,所以在添加元素之前容器就要具备比较性)

/**
 * 定义一个类,实现Comparator
 */
class ComparatorByName implements Comparator {
    /**
     * 覆盖compare()方法。按Person的 name + age 排序
     * @param obj1
     * @param obj2
     * @return
     */
    @Override
    public int compare(Object obj1, Object obj2) {
        //......此处省略强转前的健壮性判断
        Person p1 = (Person) obj1;
        Person p2 = (Person) obj2;

        //此处不能直接用 对象.属性 调用。因为属性私有,需要使用get()方法获取属性值
        int temp = p1.getName().compareTo(p2.getName());
        return temp == 0 ? p1.getAge() - p2.getAge() : temp;
    }

}

public class TreeSetDemo{
    public static void main(String[] args) {
        //将Comparator接口的子类对象(ComparatorByName)作为参数传递给TreeSet的构造函数
        TreeSet ts = new TreeSet(new ComparatorByName());
        ts.add("xxx");
        //......
    }
}

八、哈希表

1、哈希是一种算法,这种算法对数组进行了优化。哈希算法算出的值存储起来形成哈希表,哈希表中全是数组,该表的特点是:有对应关系

2、哈希算法:根据元素自身的特点,对元素进行运算,获取其在数组中的位置。哈希算法对查找进行了优化,性能稳定且高效(查找一个元素,先根据哈希算法算出该元素在数组中应该存储的位置,然后判断数组中该位置的元素是否是要查找的元素 -- 因为存入时也是按照哈希算法计算出来的位置存的,如果查找的元素存在,一定在计算出来的位置上)

3、每个对象都有自己的哈希值(即每个对象在内存中的位置),都是通过hashCode()计算出来的。hashCode()是用来计算对象哈希值的方法

4、哈希算法的存储过程

(1)根据哈希算法计算元素abc在数组中的位置a

(2)判断数组中位置a处是否有元素。如果没有,直接存储;如果有,进行步骤(3)

(3)希判断位置a处的元素是否是abc。如果是,不存储;如果不是,用哈冲突的解决方式

5、哈希表如何确定元素是重复的

(1)判断哈希值(hashCode())。哈希值相同时,才会有第二次判断。如果哈希值不同,不需要判断equals()

(2)判断内容(equals())

6、哈希冲突:两个对象不同,但哈希值相同。解决方式

(1)顺延。即将数组延长

(2)串联。基于此位置继续计算,算出一个位置

7、哈希算法的好处和弊端:

(1)好处:提高查询效率

(2)弊端:不能重复

九、HashSet存储自定义对象

1、HashSet中存储的自定义对象,如果要保证存储的自定义对象唯一,或者要进行contains()、remove()等操作,最关键的是判断元素是否相同,要覆盖自定义对象的hashCode()和equals()方法,建立自己的判断相同的依据(最好同时覆盖自定义对象的toString()方法)

注:ArrayList要覆盖自定义对象的equals()方法,HashSet要覆盖自定义对象的hashCode()和equals()方法

十、判断元素唯一的方式

1、ArrayList:equals()

2、HashSet:hashCode() + equals()

3、TreeSet:compareTo() 或 compare()

猜你喜欢

转载自blog.csdn.net/ruyu00/article/details/82150692