• 5.2类集(java学习笔记)Map,Set接口


    一、Map接口

    Map接口中存储数据是通过key->value的方式成对存储的,可以通过key找到value。

    二、Map接口常用子类

      1.HashMap

       HashMap是无序存放的,key不允许重复,但值可以重复。如果key重复,后来的value会覆盖之前的value。

       

    import java.util.HashMap;
    import java.util.Map;
    
    public class TestHashMpa {
        public static void main(String[] args) {
            Map<Integer,String> m = new HashMap<Integer,String>();
            m.put(3,"C");
            m.put(1,"A");
            m.put(2,"B");
            m.put(3,"D");
            m.put(4,"A");
            System.out.println("1="+ m.get(1)+" 2="+m.get(2)+" 3="+m.get(3)+" 4="+m.get(4));
        }
    }
    运行结果:

    1=A 2=B 3=D 4=A

    这里面有两个方法一个是public V put(K key,V value),此处的K,V泛型。put方法向map中放入k—>v.并返回value。

    还有一个是public V get(Object K),获得key对应的value。

    上面代码中键值3重复了,所以先添加进去C被后添加进去的D覆盖,此时get(3)得到的是D。

    value有两个A,但是它们的键没有重复所以不影响。

    三、常用方法简介

    1.判断内容是否存在

      1.1 public boolean containsKey(Object key)判断key是否存在。

      1.2 public boolean containsValue(Object key)判断value是否存在。

    import java.util.HashMap;
    import java.util.Map;
    
    public class TestHashMpa {
        public static void main(String[] args) {
            Map<Integer,String> m = new HashMap<Integer,String>();
            m.put(3,"C");
            m.put(1,"A");
            m.put(2,"B");
            m.put(3,"D");
            m.put(4,"A");
            System.out.println(m.containsKey(1));//判断key为1是否存在,此处为true
            System.out.println(m.containsKey(5));
            System.out.println(m.containsValue("A"));//判断Value"A"是否存在,此处为true
            System.out.println(m.containsValue("E"));
        }
    }
    运行结果:
    true
    false
    true
    false

    2.根据key删除value

    import java.util.HashMap;
    import java.util.Map;
    
    public class TestHashMpa {
        public static void main(String[] args) {
            Map<Integer,String> m = new HashMap<Integer,String>();
            m.put(3,"C");
            m.put(1,"A");
            m.put(2,"B");
            m.put(3,"D");
            m.put(4,"A");
            System.out.println("1="+ m.get(1)+" 2="+m.get(2)+" 3="+m.get(3)+" 4="+m.get(4));
            m.remove(4);
            System.out.println("1="+ m.get(1)+" 2="+m.get(2)+" 3="+m.get(3)+" 4="+m.get(4));
        }
    }
    1=A 2=B 3=D 4=A
    1=A 2=B 3=D 4=null

    根据键值删除对应的value。

    3.public void putAll(Map<? extends K,? extends V> t)

    将一个Map集合中的内容加入到另一个Map

    import java.util.HashMap;
    import java.util.Map;
    
    public class TestHashMpa {
        public static void main(String[] args) {
            Map<Integer,String> m = new HashMap<Integer,String>();
            Map<Integer,String> ma = new HashMap<Integer,String>();
            m.put(3,"C");
            m.put(1,"A");
            m.put(2,"B");
            m.put(3,"D");
            m.put(4,"A");
            ma.put(5, "D");
            ma.put(6, "E");
            ma.putAll(m);
            System.out.println("1="+ ma.get(1)+" 2="+ma.get(2)+" 3="+ma.get(3)+" 4="+ma.get(4)+
                    " 5="+ ma.get(5)+" 6="+ ma.get(6));
        }
    }
    运行结果:
    1=A 2=B 3=D 4=A 5=D 6=E

     m本身有A、B、D,ma有A、D、E,将m添加到ma后,ma就有A、B、D、A、D、E。

    四、HashMap底层实现

    我们不妨想一想HashMap底层如何实现,

    假如我们用数组,这样好像也可以,但是我们用数组的话。检查Key是否重复,包括根据Key查找Value每次都要从头开始遍历,这样效率太低浪费资源。

    我们能否有一种更好的方式,可以减少查询时间?

    当然是有的,同时也是HashMap的实现方式,数组+链表。

    数组中每一个元素存放一个链表。

    这里还需要用到hashCode(),计算hash码的函数,我们暂且先不管这些。

    我们先来假设下:

    假设有一个无限大的数组arr,向这个数组放入东西必须成对放入(key,value)。

    假设有一个方法h,可以根据key计算出一个值,且不同key计算出来的值不一样,相同key计算出来的值一样。

    那么我们放数据的时候,先通过key.h()计算一个值,然后判断arr[key.h()]是否为空,如果为空则直接放入,

    如果不为空则将原有的value替换成新来的value.

    要通过key获得value时,直接返回arr[key.h()].value.

    假设上列说明是HashMap的底层实现,是不是感觉很方便,取出数据基本只需要一次操作,读取非常快。

    上面的思路之所以快取决于两点的相互配合,一是数组无穷大的,二、每一个不同的key都有一个不同值。

    这两点在实际中很难做到,近乎可以说不可能做到。

    但这种思路确实很方便。

    那么我们能否根据这个思路想一个折中的方法?

    首先,数组不可能无穷大,那我们只能定义一有限长度的数组。

    其次,每一个不同的key都有一个不同值,这个也不好实现,我们就允许不同的key可以出现相同值.

    基本思路确定了,我们就要解决这个思路中有问题的部分,既然不同的key可以出现相同值,那我们存放

    数据时怎么存放呢?答案是用链表。首先通过计算key.h()确定数组下标,然后遍历链表比较key,

    如果有相同的key则替换其中的value,如果没有则将数据添加到链表中。

    这样效率虽然没有无穷数组的快,但也提升了不少。

    假设这里的数组长度为1000,每一个数组元素存放一个链表。

    我们有10000个数据,假设均匀分布,那么最坏的情况也只需要遍历10次,

    如果不是均匀分布的也比遍历10000次会好很多。

    数组+链表

    上面的h()方法就是hashCode(),hashCode()方法就是根据当前对象采用一定的算法计算出一个值。

    通过这个值来减少查找等操作的次数。

    要注意一点:

    比较对象是否相等一般是用过equals方法。

    如果hashCode不相等,则equals为false。

    如果hashCode相等,则equals不定,可能false也可能为true.

    hashCode()可参考:http://www.cnblogs.com/dolphin0520/p/3681042.html

    下面我们来实现一个简易的HashMap(只写了put,get方法),该实现只为便于理解HashMap,

    与JDK中具体实现HashMap有差异,由于是简易版的有很多不足,只为理解数组加链表的思想。

    为了便于理解程序,链表中只提供了实现HashMap(put,get)所需要的方法。

    MyHashMap及MyLinkedList中的其他方法有兴趣可以自行补充.

      1 public class TestMyHashMap {
      2     public static void main(String[] args) {
      3        MyHashMap<Integer, String> m = new MyHashMap<>();
      4        m.put(1, "A");
      5        m.put(1, "C");
      6        m.put(2, "B");
      7        System.out.println(m.get(1));
      8     }
      9 }
     10 
     11 
     12 class MyHashMap<K,V>{
     13     int size = 0;
     14     @SuppressWarnings("unchecked")
     15     MyLinkedList <K,V>[] arr = new MyLinkedList[999];//存放数据的数组
     16     
     17     public void put(K key,V value){
     18         int hash = key.hashCode()%999;  //根据当前key计算hashC并对999取余,
     19         if(arr[hash]==null){ //如果对应arr[hash]为空则创建一个链表对象。
     20             MyLinkedList<K,V> l = new MyLinkedList<>();
     21             arr[hash] = l;   //将创建好的链表添加到数组中
     22             arr[hash].add(key, value);//并且将key,value添加到链表中
     23         }else{
     24             if(arr[hash].isKeyValue(key, value)); //遍历链表,如果有相同的key则用新的value覆盖原有的value
     25             else{
     26                 arr[hash].add(key, value);  //如果没有重复的key,则直接将数据添加到链表后面。
     27             }
     28         }
     29     }
     30     public V get(K key){ //通过key获取value       
     31         return arr[key.hashCode()].KeygetValue(key);//进去arr[key.hashCode()]中遍历,
     32     }                                               //如果有指定的key则返回对应value。
     33 }                               //如果没有则返回null
     34 
     35 
     36 class MyLinkedList<K,V>{/这个详细解释可参照https://www.cnblogs.com/huang-changfan/p/9583784.html
     37     int size = 0;
     38     private Node<K,V> first;
     39     private Node<K,V> last;
     40     public void add(K key,V value){
     41         if(first == null){
     42             Node<K,V> n = new Node<K,V>();
     43             n.setKey(key);
     44             n.setValue(value);
     45             n.setFirst(null);
     46             n.setLast(null);
     47             first = n;
     48             last = n;
     49             size++;
     50         }else{
     51             Node<K,V> n = new Node<K,V>();
     52             n.setFirst(last);
     53             last.setLast(n);
     54             n.setKey(key);
     55             n.setValue(value);
     56             n.setLast(null);
     57             last = n;
     58             size++;
     59         }
     60     }
     61     
     62     public boolean isKeyValue(K key,V value){//遍历链表,如果有key相等则覆盖原有value
     63         Node<K,V> f = new Node<K,V>();
     64         f= first; //获取链表头结点
     65         if(f.getKey().equals(key)){//判断当前结点key的值是否等于传递进来的key
     66             f.value = value;     //如果相等,替换原有value并返回true
     67             return true;
     68         }
     69         while(f.getLast()!= null){  //判断链表下一结点是否为空
     70             f = f.getLast();        //不为空则指向下一结点继续判断。
     71             if(f.getKey().equals(key)){
     72                 f.value = value;
     73                 return true;
     74             }
     75         }
     76         return false; //如果链表遍历结束,没有相同项则返回false
     77     }
     78     
     79     public V KeygetValue(K key){//通过key返回value
     80         Node<K,V> f = new Node<K,V>();//与上述差不过,遍历链表相同则返回对应的value。
     81         f= first;
     82         if(f.getKey().equals(key))
     83             return f.value;
     84         while(f.getLast()!= null){
     85             f = f.getLast();
     86             if(f.getKey().equals(key))
     87                 return f.value;
     88         }
     89         return null;
     90     }
     91     
     92 }
     93 
     94 class Node<K,V>{ //链表中的结点
     95     private Node<K,V> first;//记录头结点的位置
     96     private K key;  //存放key value
     97     V value;
     98     private Node<K,V> last; //记录尾结点位置
     99     public Node(){};
    100     public Node(K key,V value){//下面是构造方法和一些set,get方法。
    101         this.key = key;
    102         this.value = value;
    103     }
    104     public Node<K, V> getFirst() {
    105         return first;
    106     }
    107     public void setFirst(Node<K, V> first) {
    108         this.first = first;
    109     }
    110     public K getKey() {
    111         return key;
    112     }
    113     public void setKey(K key) {
    114         this.key = key;
    115     }
    116     public V getValue() {
    117         return value;
    118     }
    119     public void setValue(V value) {
    120         this.value = value;
    121     }
    122     public Node<K, V> getLast() {
    123         return last;
    124     }
    125     public void setLast(Node<K, V> last) {
    126         this.last = last;
    127     }    
    128 }
    运行结果:
    CB

    Map中还有一个子类,是TreeMap,这个子类中的数据是有序的,根据key排序。

    但是,如果是放入的key是自己创建的类时需要实现Comparator接口。

    因为系统已经定义好的类已经实现了Comparator接口指定了排序规则,而自己 定义的类没有

    实现Comparator接口指定排序规则会出现问题。有兴趣的可以去了解下比较器。

    public class TestMap{
        public static void main(String[] args) {
            Set<String> s  = new HashSet<>();
            Map<String,String> mh = new HashMap<>();
            Map<String,String> mt = new TreeMap<>();
            mh.put("ZS", "张三");
            mh.put("LS","李四");
            mh.put("WW", "王五");
            mh.put("ZL", "赵六");
            mt.put("ZS", "张三");
            mt.put("LS","李四");
            mt.put("WW", "王五");
            mt.put("ZL", "赵六");
            System.out.println(mh);
            System.out.println(mt);
        }
    }
    运行结果:
    {WW=王五, ZL=赵六, LS=李四, ZS=张三}
    {LS=李四, WW=王五, ZL=赵六, ZS=张三}

    HashMap输出的是无序的,二TreeMap则根据key进行了排序,

    可以看出这里的key是根据字母的顺序来进行排序的,L在前面,Z在最后面

    当第一个Z相同时,比较后一个字母的顺序,L在S前面所以赵六在张三前面。

    这里的规则是系统指定好的,如果是自己创建的类,需要自己实现接口并规定排序方法。

    五、Set接口

     Set接口中不允许加入重复的元素。

    1.Set接口 常用子类

      1.1 HashSet

        大家想一想HashSet和HashMap命名方式很像,而且Set接口中不允许加入重复读的元素。

        我们回忆下Map中的key是不是也不允许重复?那这两者有何关联呢?

        我们看下HashSet的源码:

        

        HashSet的底层就是新建一个HshMap对象,

        我们来看下添加方法。

        

        添加方法也是调用map的put方法,不过是把key作为hashSet数据存放位置。

        value存放的是

        所以只用到了key,把key作为Set的数据。

        

    import java.util.HashSet;
    import java.util.Set;
    
    public class TestSet {
        public static void main(String[] args) {
            Set<String> s  = new HashSet<>();
            s.add("张三");
            s.add("李四");
            s.add("王五");
            s.add("赵六");
            System.out.println(s);
        }
    }
    运行结果:
    [李四, 张三, 王五, 赵六]

    同样HashMap是无序的,上面可以看出HashSet中的add方法就是调用HashMap中的put方法。

    HashSet中很多方法其实内部都是通过调用Map来实现的。

        2.2TreeSet

        TreeSet和TreeMap相似,底部也是通过TreeMap来实现的。

        

        

        

    import java.util.Set;
    import java.util.TreeSet;
    
    public class TestSet {
        public static void main(String[] args) {
            Set<String> s  = new TreeSet<>();
            s.add("ZS");
            s.add("LS");
            s.add("WW");
            s.add("ZL");
            System.out.println(s);
        }
    }
    运行结果:
    [LS, WW, ZL, ZS]

    我们会发现这里的排序结果与之前TreeMap排序结果相同。

  • 相关阅读:
    WPF中各个Template的分析(转)
    WPF TreeView
    微信支付文章综合
    WPF 颜色渐变
    史上最全的厦门英语角!赶紧收藏啦!
    SQL008存储过程总结
    SQL SERVER事务处理
    HTTP 头部解释
    为你详细解读HTTP请求头的具体含意
    IIS部署常见问题总结
  • 原文地址:https://www.cnblogs.com/huang-changfan/p/9642560.html
Copyright © 2020-2023  润新知