集合(4)----Set接口

一、Set接口介绍                                                                                                                                                 点击此处返回总目录

二、HashSet

三、哈希表的数据结构

四、对象的哈希值

五、哈希表的存储过程

六、自定义重写hashCode()方法和equals()方法

七、一道关于hashCode()方法和equals()方法的面试题

八、LinkedHashSet

 

 

 

一、Set接口介绍

Set接口中不予许存储重复元素。

Set集合的取值方式,目前只有两种:迭代器和增强for。Set是没有索引的,不能通过索引的方式来获取数据。

Set接口中的方法与Collection接口的方法一模一样。

 

 

二、HashSet

是Set接口的一个实现类。HashSet的底层本质是HashMap。

HashSet集合的自身特点:

      底层数据结构:哈希表。哈希表是数组和链表的结合体。

      存储、取出都比较快。

      线程不安全,运行速度比较快

 

例:

package cn.itcast.demo07;

import java.util.HashSet;
import java.util.Iterator;

public class Test {    
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<String>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
        set.add("bbb");                            //重复的不会存
        
        //迭代器方式遍历
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());     //aaa ccc bbb
        }
        //增强for方式遍历
        for(String s:set){
            System.out.println(s);               //aaa ccc bbb
        }
              
    }

}

三、哈希表的数据结构

自己翻翻数据结构。一个数组,数组的每个元素挂一个链表。

 

 

四、对象的哈希值

Object类的方法:public int hashCode() ,返回对象的哈希值,是一个整数,不知道怎么算的,运行多次可能得到多个结果。【例1】

String对象重写了hashCode()方法,有自己的一个算法,见下面。【例2】

 

 

例1:得到对象的哈希值。

//Person.java

package cn.itcast.demo08;

public class Person {

}

//Test.java

package cn.itcast.demo08;

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        Person p = new Person();
        int i = p.hashCode();
        System.out.println(i);        //26542488、24763620等。

    }
}
 

例2:String类重写了Object类的hashCode()方法。

package cn.itcast.demo08;

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = new String("abc");


        System.out.println(s1.hashCode());    //96354
        System.out.println(s2.hashCode());    //96354。得到的哈希值是一样的。

    }
}

解释:

查看String的源代码可以发现,String类重写了Object类的hashCode()方法,如下:

    public int hashCode() {
        int h = hash;                                            //hash初始为0 
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

计算过程如下:

step1:h = 0

step2:h == 0 && value.length=3>0,为true

step3:val[] = value;

step4:h = 31*0 + 'a' = 0+97 = 97

step5:h = 31*97 + 'b' = 31*97 + 98 = 3105

step6:h = 31*3105 +'c' = 31* 3105 + 99 = 96354

因此s1与s2调用hasCode()方法得到的值相等。

 

 

 

 

五、哈希表的存储过程

对象调用hashcode()方法;

if(集合中没有出现过该哈希值){

       是一个新的对象,存储;

}else if(集合中出现过该哈希值){

       if(新对象.equals(旧对象) == true){

             元素重复,不存储;

       }else{

             不重复,挂在旧对象下面;

       }

}

 

例1:

package cn.itcast.demo08;

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {        
        HashSet<String> set = new HashSet<String>();
        set.add(new String("abc"));
        set.add(new String("abc"));
        set.add(new String("bbc"));
        set.add(new String("bbc"));
        
        System.out.println(set);      //[abc, bbc]。明明是四个对象,为什么set中只存了两个元素呢?

    }
}

上面明明是四个不同的对象,但是只存储了两个。因此需要知道是怎么存储的。

当存储new String("abc")时:

step1:首先调用对象的哈希值。new String("abc").hashCode()方法,得到96354。

step2:在容器中找有没有和96354一样的哈希值,没有,把该对象存在数组的一个索引上。

 

当又存储new String("abc")时:

step1:首先调用对象的哈希值。new String("abc").hashCode()方法,得到96354。

step2:在容器中找有没有和96354一样的哈希值。找到了一个对象也有同样的哈希值。

step3:集合让后来的对象调用equals()方法,与已经有了的对象作比较,得到true。

step4:集合判定元素重复,不再存储。

 

"bbc"是一样的,不再解释。

 

假设又来了一个new String("adc"),调用hashCode(),假设也得到了96354。在调用equals()方法,得到了false。那么集合判断元素没有重复,会采用桶的存储方式,把"adc"存储在"abc"的链表中。

 

六、自定义重写hashCode()方法和equals()方法

通过五,可以知道存储元素的时候会调用hashCode()和equals()方法。如【例1】:

例1:

package cn.itcast.demo09;

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<Person>();
        
        Person p1 = new Person("aaa",23);
        Person p2 = new Person("aaa",23);
        Person p3 = new Person("bbb",22);
        
        set.add(p1);
        set.add(p2);
        set.add(p3);
        
        System.out.println(set);         //[aaa--23, bbb--22, aaa--23]。因为p1和p2是两个不同的对象,hash值不同,所以存了两次。       
    }
}

如果想将Person对象中,姓名和年龄相同的数据看做同一个对象,在set中只存储一份,就需要改写hashCode()方法和equals()方法。

例2:

//Person.java

package cn.itcast.demo09;

public class Person {
    private String name;
    private int age;
    
    public Person(){}

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    public String toString(){
        return name + "--"+age;
    }
    
    public int hashCode(){                                                  //重写了hashCode()方法。
        String s = name +age;
        return s.hashCode();
    }
    
    public boolean equals(Object b){                                  //重写了equals()方法。
        if(this == b){
            return true;
        }
        if(b == null){
            return false;
        }
        if(b instanceof Person){
            Person p = (Person)b;
            return name.equals(p.name) && age == p.age;
        }
        return false;
    }
    
}

//Test.java

package cn.itcast.demo09;

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<Person>();
        
        Person p1 = new Person("aaa",23);
        Person p2 = new Person("aaa",23);
        Person p3 = new Person("bbb",22);
        
        set.add(p1);
        set.add(p2);
        set.add(p3);
        
        System.out.println(p1.equals(p2));     //true
        System.out.println(p1.hashCode());   //92566082
        System.out.println(p2.hashCode());   //92566082
        
        System.out.println(set);                     //[bbb--22, aaa--23]。不再输出3个了,因为哈希值相等,equals()结果也相等。
        
    }
}

七、一道HasCode()和equals()相关的面试题

1.如果两个对象的哈希值相等:p1.hashCode() == p2.hashCode();

   那么两个对象的equals一定返回true么?即,p1.equals(p2) 一定是true么?

   答:不一定。

2.如果两个对象的equals返回true,即p1.equals(p2) 是true,

   那么两个对象的哈希值一定相等么?

   答:一定。

 

解释:参考API文档,对Object类的hashCode()方法的描述如下:

hashCode 的常规协定是:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

而,对Object类的equals()方法的描述如下:

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

综上,一定要维护hashCode方法的常规协定。当equals时,一定要hashCode()相等。

 

 

 

八、LinkedHashSet

基于链表的哈希表实现,继承HashSet。

具有顺序,存储和取出的顺序相同。有序的Set集合。【例1】

 

例1:

package cn.itcast.demo10;

import java.util.LinkedHashSet;

public class Test {
    public static void main(String[] args) {
        LinkedHashSet<Integer> link = new LinkedHashSet<Integer>();
        link.add(3);
        link.add(2);
        link.add(2);
        link.add(1);
        
        System.out.println(link);    //[3, 2, 1]。不存储重复元素,而且有序。
    }
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值