JAVA--Set与Map

Set接口

Set拥有如下特点:

 

 

这里有一条提到了不存在重复元素,但是List是可以存在重复元素的,那么Set是如何判定元素是否重复的呢?

Set是利用了hash算法配合equals方法来实现的.

 

  1. 无序排列
  2. 不能有重复元素
  3. 可以有null,一样只能存在一个.
  4. 由于无序,所以无法使用经典for循环遍历,可以使用迭代器和foreach循环.

在设计元素类型时,提供哈希算法,用于返回对象的的一个int值,在内存中开辟很多小的区域用于存储一定范围的返回值的对象.当我们想添加或查看元素时,先计算此元素的返回值(int hashCode()),之后去相应的区域进行遍历.如果没有找到值相同的,说明可以存储这个对象.如果有相同的值,查看两个对象equals方法返回值,如果为true则不能存储,反之则可以,添加至对应的链表结构中,但实际使用时应尽可能避免这种情况.

原则上,应该让所有的元素参与运算,还要尽量避免出现相同的返回值.

首先设计一个Person类,提供如下变量,并使用IDE自动生成hashCode和equals方法,以及遵循JavaBean规范生成的各种方法.

public class Person {
    private String name;
    private int age;
    private char gender;
    private String number;
    /*遵从JavaBean规范,同时提供无参有参构造器*/
    public Person(){}

    public Person(String name, int age, char gender, String number) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.number = number;
    }
/*get/set方法*/
    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 char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

/*重写equals方法和hashCode方法*/
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (getAge() != person.getAge()) return false;
        if (getGender() != person.getGender()) return false;
        if (!getName().equals(person.getName())) return false;
        return getNumber() != null ? getNumber().equals(person.getNumber()) : person.getNumber() == null;
    }

    public int hashCode() {
        int result = getName().hashCode();
        result = 31 * result + getAge();
        result = 31 * result + (int) getGender();
        result = 31 * result + (getNumber() != null ? getNumber().hashCode() : 0);
        return result;
    }
/*重写toString方法*/
    public String toString() {
        return "("+name+","+age+","+gender+","+number+")";
    }
}

来看一下自动生成的hashCode方法. 

一个result变量存储了要返回的数据.

首先第一步使用了getName()的的hashCode方法,因为String类已经重写了hashCode方法,所以直接调用即可.

第二步用一个质数(任意质数都可以)与第一步的result相乘,再加上getAge()的返回值.

第三步把char强制转换为int数据,再与上一步的已经与质数相乘的result相加.

最后依旧是把上一步的result与质数相乘,再看getNumber()是否为空,不为空就调用String的hashCode方法,为空就加0.

方法末尾返回经过这些计算的result值.

可以看到经过这些计算很难再有重复的值了.

来看看重写hashCode方法的原则与注意事项.

  1. 如果重写了equals(),有必要重写hashCode()
  2. 如果equals()返回true,hashCode()的返回值要相同.
  3. 如果equals()返回false,hashCode()的返回值不一定不一样,如果返回值不同,可以提高检索的效率.
  4. hashCode值相同,equals方法可能不同.
  5. hashCode值不同,equals方法一定不同.

Set接口的子类

HashSet:通过实现hash算法实现的一种数据结构,无序,不重复,线程不安全.增删操作时比LinkedHashSet效率高.

LinkedHashSet:通过实现hash算法的一种数据结构,通过链表来维持顺序,顺序与添加顺序一致.查看或检索时比HashSet效率高.

TreeSet:SortedSet子接口的实现类.使用了二叉树的一种数据结构,顺序与自然排序有关,支持自定义排序.

测试类:

import java.util.HashSet;
import java.util.Set;

public class SetTest{
    public static void main(String[] args) {
        Set<Person> set=new HashSet<Person>();
        Person p1=new Person("A",15,'F',"1234567890");
        Person p2=new Person("B",19,'M',"1234567891");
        Person p3=new Person("C",20,'M',"1234567892");
        Person p4=new Person("D",17,'F',"1234567893");
        Person p5=new Person("E",18,'F',"1234567894");
        /*设置一个与p1重复的p6*/
        Person p6=new Person("A",15,'F',"1234567890");
        /*添加进set*/
        set.add(p1);
        set.add(p2);
        set.add(p3);
        set.add(p4);
        set.add(p5);
        set.add(p6);
        /*输出set*/
        System.out.println(set);
..........
    }
}
[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (B,19,M,1234567891), (C,20,M,1234567892)]

 

可以看到并不是按照添加顺序输出的,而且重复的p6也没有被添加进去. 

现在删除一个元素,再输出.

..........
/*删除元素p2*/
        set.remove(p2);
        /*再次输出set*/
        System.out.println(set);
..........
[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (C,20,M,1234567892)]

此时修改其中一个元素,利用set方法,输出,删除p2后再输出.

  ...........
/*修改元素p2*/
        p2.setName("FF");
        /*输出修改后的set*/
        System.out.println(set);
        /*再尝试删除p2*/
        set.remove(p2);
        /*再次输出set*/
        System.out.println(set);
..........
修改p2后的set[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (FF,19,M,1234567891), (C,20,M,1234567892)]
删除p2后的set:[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (FF,19,M,1234567891), (C,20,M,1234567892)]

 可以看到并没有被删除,因为修改之后,元素还在原来的地址上,但是本身的hashCode值发生了改变,删除时找不到该元素.若输出删除元素的方法,可以看到输出-1,表示没找到元素.

如何删除呢,其实很简单,把内容改为原来的值就好了~

..........
/*将p2改为原来的状态*/
        p2.setName("B");
        /*再次删除*/
        set.remove(p2);
        System.out.println("修改为原本的p2之后删除p2后的set:"+set);
修改为原本的p2之后删除p2后的set:[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (C,20,M,1234567892)]

 修改之后由于hashCode值变成了原来的,所以就可以删除了.

Map接口

集合框架中的另一个父接口

用于存储一一对应的元素数据.第一个对象可以作为索引,第二个对象作为值,所以称之为key-value键值对.

存储特点

  1. 以key-value形式进行存储.
  2. key与value必须都是引用类型.
  3. key可以为null.
  4. key-value是单向一对一映射.
  5. key不能重复,value可以重复.

存储机制

Map是基于数组和链表的数据结构.

作为key的元素采用了散列算法计算存储的数组的位置上,即散列数组或散列桶.如果数组计算出来的位置,数组此位置上没有元素,就可以添加到散列桶内.如果有元素,使用key的equals,若返回值为false,就会存储在散列桶元素对应的单向链表中,若返回值为true,就进行替换.使用Map集合时,作为key的数据类型应重写equals()hashCode().

注意向Map中添加元素需要使用put方法.

import java.util.HashMap;
import java.util.Map;

public class MapTest{
    public static void main(String[] args) {
        /*设置key为String,value为Person.*/
        Map<String,Person> map=new HashMap<String, Person>();
        Person p1=new Person("A",15,'F',"1234567890");
        Person p2=new Person("B",19,'M',"1234567891");
        Person p3=new Person("C",20,'M',"1234567892");
        Person p4=new Person("D",17,'F',"1234567893");
        Person p5=new Person("E",18,'F',"1234567894");
        /*向map中添加元素,注意应该使用put方法.*/
        Person p6=new Person("C",19,'M',"1234");
        map.put("A",p1);
        map.put("B",p2);
        map.put("C",p3);
        map.put("D",p4);
        map.put("E",p5);
        System.out.println(map);
        /*将key值与p3相同的p6添加至map*/
        Person p=map.put("C",p6);
        System.out.println(p);
..........
    }
}

运行结果: 

{A=(A,15,F,1234567890), B=(B,19,M,1234567891), C=(C,20,M,1234567892), D=(D,17,F,1234567893), E=(E,18,F,1234567894)}

常用方法

V put(K k,V v):存储一对键值对,返回被替换的value,若未发生替换则返回null.

..........
/*将key值与p3相同的p6添加至map*/
        Person p=map.put("C",p6);
         System.out.println("被替换的value:"+p);
..........

运行结果:

被替换的value:(C,20,M,1234567892)

再次输出map:

发生替换的map:{A=(A,15,F,1234567890), B=(B,19,M,1234567891), C=(C,19,M,1234), D=(D,17,F,1234567893), E=(E,18,F,1234567894)}

 可以看到以C为key的value被替换为p6了.

 V get(K k):通过key对象获取对应的value对象,若集合中没有这个key,返回null.

..........
 /*取出存在的key"B"*/
        p=map.get("B");
        System.out.println(p);
        /*取出不存在的key"G"*/
        p=map.get("G");
        System.out.println(p);

运行结果:

(B,19,M,1234567891)
null

可以发现输出不存在的key:G的值时,输出为null,证明这个key没有对应的value.

Map集合的遍历

Set<K> keySet():用于获取Map中的所有key对象,返回Set集合.

 /*使用Set集合接收元素*/
        Set<String> set=map.keySet();
        /*输出set*/
        System.out.println(set);

 运行结果

[A, B, C, D, E]

Set<Entry<K,V>> entrySet():将key-value封装成内部类的对象,返回Entry对象的Set集合.

  /*添加至Entry中*/
       Set<Map.Entry<String,Person>> set=map.entrySet();
       /*输出*/
       System.out.println(set);

运行结果 

[A=(A,15,F,1234567890), B=(B,19,M,1234567891), C=(C,20,M,1234567892), D=(D,17,F,1234567893), E=(E,18,F,1234567894)]

Collection<V> values():将Map集合中的所有value封装成一个Collection中

  /*添加至Collection中*/
        Collection<Person> c=map.values();
       /*输出*/
        System.out.println(c);

运行结果

[(A,15,F,1234567890), (B,19,M,1234567891), (C,20,M,1234567892), (D,17,F,1234567893), (E,18,F,1234567894)]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值