欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

三、JavaSE_集合(Set)

程序员文章站 2022-09-16 10:06:38
Set(HashSet、TreeSet) 一、Set简单介绍 首先,我们来看看Set的API: 之前我们看到Collection的体系结构时,可以看出Set是无序的,并且存储的是不重复的。通过API我们也可以看出,Set最多保安一个null元素。 二、HashSet 1.API介绍 简单点就是,Ha ......

Set(HashSet、TreeSet)

一、Set简单介绍

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

三、JavaSE_集合(Set)

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

三、JavaSE_集合(Set)

二、HashSet

1.API介绍

 三、JavaSE_集合(Set)

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

 2.案例

  •  存储字符串并遍历
    三、JavaSE_集合(Set)
     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

    结果:
    三、JavaSE_集合(Set)

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


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

    三、JavaSE_集合(Set)
     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

    结果:

   三、JavaSE_集合(Set)

  保证了元素的唯一性。

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

答案是:

三、JavaSE_集合(Set)
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怎么存怎么取。上代码:

三、JavaSE_集合(Set)
 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

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

三、JavaSE_集合(Set)

 

四、TreeSet

 1.API

三、JavaSE_集合(Set)

好晦涩。

我们直接用例子来说明:

三、JavaSE_集合(Set)
 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接口的,自然不重复,无序,那么运行结果是什么呢?
三、JavaSE_集合(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 }

结果是:

三、JavaSE_集合(Set)

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方法       

}

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

三、JavaSE_集合(Set)

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

三、JavaSE_集合(Set)

改成-1(负数)

三、JavaSE_集合(Set)

总结:

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 }

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

三、JavaSE_集合(Set)

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

三、JavaSE_集合(Set)

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

如:

三、JavaSE_集合(Set)

正如上个原理图一样,如果相等那么就不存入。可是我们不想这样,不能因为年龄相同就不存储,因此还得改写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     }   

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

三、JavaSE_集合(Set)

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

 

3.比较器排序

 

 

小结一下:

 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