三、JavaSE_集合(Set)

2018-06-18 02:24:28来源:未知 阅读 ()

新老客户大回馈,云服务器低至5折

Set(HashSet、TreeSet)

一、Set简单介绍

首先,我们来看看Set的API:

 之前我们看到Collection的体系结构时,可以看出Set是无序的,并且存储的是不重复的。通过API我们也可以看出,Set最多保存一个null元素。

二、HashSet

1.API介绍

 

简单点就是,HashSet用哈希算法实现,无序,不重复,可以使用null。

 2.案例

  •  存储字符串并遍历
     1 public class Test_Set {
     2     public static void main(String[] args){
     3         //存储字符串并遍历
     4         HashSet<String> hashSet = new HashSet<>();
     5         boolean flag = hashSet.add("唐豆豆");
     6         boolean flag2 = hashSet.add("唐豆豆");
     7         System.out.println(flag);
     8         System.out.println(flag2);
     9         //增强for循环遍历
    10         for (String s : hashSet){
    11             System.out.println(s);
    12         }
    13     }
    14 }
    View Code

    结果:


    第一个输出结果为:true,证明已经存放到HashSet当中,第二个为false,证明没有存放到集合当中,输出结果也是只有一个。所以,HashSet存放的是不重复的。那么存储对象又是什么样的呢?


  • 存储自定义对象保证元素唯一性

     1 public class Test_Set {
     2     public static void main(String[] args){
     3         // addStringByHashSet();
     4         HashSet<Person> hashSet = new HashSet<>();
     5         hashSet.add(new Person("张三",23));
     6         hashSet.add(new Person("张三",23));
     7         hashSet.add(new Person("张三",23));
     8 
     9         System.out.println(hashSet.size());
    10     }
    11 }
    12 
    13 
    14 class Person {
    15     private String name;
    16     private Integer age;
    17 
    18     public Person() {
    19     }
    20 
    21     public Person(String name, Integer age) {
    22         this.name = name;
    23         this.age = age;
    24     }
    25 
    26     public String getName() {
    27         return name;
    28     }
    29 
    30     public void setName(String name) {
    31         this.name = name;
    32     }
    33 
    34     public Integer getAge() {
    35         return age;
    36     }
    37 
    38     public void setAge(Integer age) {
    39         this.age = age;
    40     }
    41 
    42     @Override
    43     public String toString() {
    44         return "Person{" +
    45                 "name='" + name + '\'' +
    46                 ", age=" + age +
    47                 '}';
    48     }
    49     //重写equals方法
    50     @Override
    51     public boolean equals(Object o) {
    52         if (this == o) return true;
    53         if (o == null || getClass() != o.getClass()) return false;
    54 
    55         Person person = (Person) o;
    56 
    57         if (name != null ? !name.equals(person.name) : person.name != null) return false;
    58         return age != null ? age.equals(person.age) : person.age == null;
    59     }
    60     //重写HashCode方法
    61     @Override
    62     public int hashCode() {
    63         int result = name != null ? name.hashCode() : 0;
    64         result = 31 * result + (age != null ? age.hashCode() : 0);
    65         return result;
    66     }
    67 
    68 }
    View Code

    结果:

   

  保证了元素的唯一性。

 既然是HashSet,那么如果不重写hashCode方法,那么会怎么样呢?大家可以注释掉hashCode方法执行一下看看结果。

答案是:

1 Console.info(3)//答案是3
View Code

没有重写hashCode方法,也就没有执行equals方法。(也有可能执行,但概率很低)

首先,我们来看看他没有重写HashCode方法时,是怎么存储的。

1 HashSet<Person> hashSet = new HashSet<>();
2         hashSet.add(new Person("张三",23));
3         hashSet.add(new Person("张三",23));
4         hashSet.add(new Person("张三",23));

由于HashSet底层用Hash算法实现,那么第二行代码 hashSet.add(new Person("张三",23));在执行时,会生成一个hash码,第三行代码执行时有生成一个hash码,以此类推,每次都不一样,那么就认为他是三个不同的值

那么他就会都存下来。因此最后的结果就是3。

重写了hashCode方法之后呢?我们来看看那段代码~

 

1  @Override
2     public int hashCode() {
3         int result = name != null ? name.hashCode() : 0;
4         result = 31 * result + (age != null ? age.hashCode() : 0);
5         return result;
6     }

 

第三行代码,一个int类型的result,如果name不为null,那么就result就等于name的hash值,如果为null,result等于0。

第四行代码,result = 31 * 第三行的result值,加上age的判断(age不为空那么就等于age的hash值,否则等于0)

最后的结果就是result的值,这是为了保证这个对象的属性的不重复,确保hash码不重复。相同的hash码采取执行equals方法,这样降低了equals执行的次数。

那么为什么会是31乘以后面的数呢?

1,31是一个质数,质数是能被1和自己本身整除的数
2,31这个数既不大也不小
3,31这个数好算,2的五次方-1,2向左移动5位

3.HashSet的原理:

  •  我们使用Set集合都是需要去掉重复元素的,如果在存储的时候逐个equals()比较,,效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数。

  • 当HashSet调用add()方法存储对象的时候,先调用对象的hashCode()方法得到一个哈希值,,然后在集合中查找是否有哈希值相同的对象
            * 如果没有哈希值相同的对象就直接存入集合
            * 如果有哈希值相同的对象,就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存

回到那个存储自定义对象的问题当中:

      * 类中必须重写hashCode()和equals()方法
      * hashCode():属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)
           * equals():属性相同返回true, 属性不同返回false,返回false的时候存储

三、LinkedHashSet

官方说法:此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。

也就是:保证了怎么存怎么取。类似于List怎么存怎么取。上代码:

 1 public class Test_Set {
 2     public static void main(String[] args) {
 3         LinkedHashSet<Integer> lhs = new LinkedHashSet<>();
 4         lhs.add(2);
 5         lhs.add(1);
 6         lhs.add(3);
 7         lhs.add(1);
 8         lhs.add(4);
 9         lhs.add(5);
10 
11         for (Integer num : lhs) {
12             System.out.println(num);
13         }
14 
15     }
16 }
View Code

结果:(保证了怎么存怎么取,不重复)

 

四、TreeSet

 1.API

好晦涩。

我们直接用例子来说明:

 1 public class Test_Set {
 2     public static void main(String[] args) {
 3         TreeSet<Integer> treeSet = new TreeSet<>();
 4         treeSet.add(1);
 5         treeSet.add(2);
 6         treeSet.add(5);
 7         treeSet.add(3);
 8         treeSet.add(4);
 9         treeSet.add(5);
10         treeSet.add(6);
11         treeSet.add(7);
12         for (Integer num : treeSet) {
13             System.out.println(num);
14         }
15     }
16 }
View Code

TreeSet是实现Set接口的,自然不重复,无序,那么运行结果是什么呢?

无论你怎么改变treeSet.add()里面的顺序,他的输出结果都是1234567。

也就是说,TreeSet提供了排序的功能,也就是用来给对象排序的。

 

2.TreeSet存储自定义对象

 1)那么,如果我们往里面放入自定义的对象呢?

 1 public class Test_Set {
 2     public static void main(String[] args) {
 3         TreeSet<Person> treeSet = new TreeSet<>();
 4         treeSet.add(new Person("唐豆豆",24));
 5         treeSet.add(new Person("王豆豆",23));
 6         treeSet.add(new Person("李豆豆",24));
 7 
 8         for (Person person : treeSet) {
 9             System.out.println(person);
10         }
11     }
12 }

结果是:

Person类不能够比较,后面有个Comparable,不能够比较的。要想比较必须在Person类当中实现这一接口。那么我们来实现一下。

class Person implements Comparable<Person> {

    //实现comparable接口实现的方法
    @Override
    public int compareTo(Person o) {
        return 0;
    }

    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    //此处省略set/get方法       

}

再次运行:(只打印了一个对象信息)

将compareTo方法里的return 0改成1(正数),再次运行:(全部打印)

改成-1(负数)

总结:

TreeSet集合是用来对象元素进行排序的,同样他也可以保证元素的唯一
     * 当compareTo方法返回0的时候集合中只有一个元素
     * 当compareTo方法返回正数的时候集合会怎么存就怎么取
     * 当compareTo方法返回负数的时候集合会倒序存储

 2)存储对象按照年龄去排序

这个时候就该在compareTo方法里下手了。

改写CompareTo方法:

 1     @Override
 2     public int compareTo(Person o) {
 3         return this.age - o.age;//this.age是当前对象的年龄,o.age是要放入集合的对象的年龄 
4
} 5 //main方法 6 public static void main(String[] args) { 7 TreeSet<Person> treeSet = new TreeSet<>(); 8 treeSet.add(new Person("唐豆豆", 24)); 9 treeSet.add(new Person("王豆豆", 23)); 10 treeSet.add(new Person("李豆豆", 28)); 11 12 for (Person person : treeSet) { 13 System.out.println(person); 14 } 15 }

结果:(按照年龄进行排序)

我们来画图看看这个存储的原理:

 2)存储对象按照年龄去排序此时有对象年龄是相同的。

如:

正如上个原理图一样,如果相等那么就不存入。可是我们不想这样,不能因为年龄相同就不存储,因此还得改写CompareTo方法。

1 @Override
2     public int compareTo(Person o) {
3         int num = this.age - o.age;//年龄为主要条件
4         //年龄相同时,按照姓名的字典表顺序排序
5         return num == 0 ? this.name.compareTo(o.name) : num;
6     }   

结果:(年龄为主要条件,年龄相同时,按照姓名的字典表顺序排序

 以上都是通过自然顺序去排序的,接下来我们看看通过比较器进行排序。

 

3.比较器排序

 我们来看TreeSet的API

 

通过他的构造方法我们得知,可以IC画一个Comparator<? super E> comparator 来进行比较器排序。

具体的例子就是:(根据字符串长度排序)

 1 public class ComparatorSet {
 2     public static void main(String[] args){
 3         //创建TreeSet集合
 4         TreeSet<String> treeSet = new TreeSet<>(new CompareByLen());//这里传入的就是一个比较器
 5         //存入元素
 6         treeSet.add("China");
 7         treeSet.add("USA");
 8         treeSet.add("Japan");
 9         treeSet.add("England");
10         treeSet.add("Canada");
11 
12         System.out.println(treeSet);
13 
14     }
15 }
16 
17 //这就是一个实现Comparator接口的类(比较器)
18 class CompareByLen implements Comparator<String>{
19 
20     @Override
21     public int compare(String o1, String o2) {
22         int num = o1.length() - o2.length();        //主要判断条件是字符串长度
23         return num == 0 ? o1.compareTo(o2) : num;   //次要条件是根据名称
24     }
25 }
View Code

结果:

 

小结一下:

 1.特点
    * TreeSet是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列
  2.使用方式
    * a.自然顺序(Comparable)
        * TreeSet类的add()方法中会把存入的对象提升为Comparable类型
        * 调用对象的compareTo()方法和集合中的对象比较
        * 根据compareTo()方法返回的结果进行存储
    * b.比较器顺序(Comparator)
        * 创建TreeSet的时候可以制定 一个Comparator
        * 如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
        * add()方法内部会自动调用Comparator接口中compare()方法排序
        * 调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
    * c.两种方式的区别
        * TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
        * TreeSet如果传入Comparator, 就优先按照Comparator

List & Set
1.List
    * a.普通for循环, 使用get()逐个获取
    * b.调用iterator()方法得到Iterator, 使用hasNext()和next()方法
    * c.增强for循环, 只要可以使用Iterator的类都可以用
    * d.Vector集合可以使用Enumeration的hasMoreElements()和nextElement()方法
  2.Set
    * a.调用iterator()方法得到Iterator, 使用hasNext()和next()方法
    * b.增强for循环, 只要可以使用Iterator的类都可以用

 

如有错误之处,欢迎指正。

邮箱:it_chang@126.com

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:day7 面向对象(二)

下一篇:maven的使用(基础1)