MapDemo1+2 Map接口的常用方法及遍历 及HashMap原理

MapDemo1 Map接口的常用方法

/**
 *     java.util
 *    Map接口<K,V>
    类型参数:
    K - 此映射所维护的键的类型
    V - 映射值的类型
    定义: Map是一个接口,又称作查找表 java提供了一组可以以键值对(key-value)的形式存储数据的数据结构,
    这种数据结构成为Map。我们可以看成是一个多行两列的表格,一列是存放key,一列存放value.
    而每一行就相当于一组 key-value对,表示一组数据.
    注意: 1.Map对存入元素有一个要求,就是key不能重复,所谓key不能重复,就是不能有两个equals
              结果为true的key.
             2.Map对于key,value的类型没有严格要求,只要是引用类型均可。
    常用实现类:
    Map接口根据内部结构不同有很多实现类,其中常用的有内部为hash表实现的HashMap
    还有内部为排序二叉树实现的TreeMap
    1.HashMap<K,V> (散列表,哈希表,散列算法实现)
    2.TreeMap<K,V>
    常用方法:
    1. 存入数据 :  V put(K key,V value) 
        同样也是替换数据的value的方法.
        Map要求key不允许重复: 即Map中不能出现两个key的equals判断为true。
        若使用相同的key则是替换value操作,put方法会将被替换的value返回。
        若给定的不是重复的key,返回值为null。
        还有一种可能的现象,即一种key存在并关联的value值为null,存入相同key时,
        也会返回null.
    2. 获取数据:    V get(Object key)
         该方法的作用就是根据给定的key去查找Map中对应的value并返回,
         若当前Map中不包含给定的key,那么返回值为null。
    3. 检测是否包含给定的key:    boolean containsKey (Object key)
        该方法的作用是查找Map中是否包含有给定的key.
        若当前Map中包含给定的key(这里检查是否包含是根据key的equals比较结果为依据的。)
    4. 移除数据: V remove(Object key)
        该方法的作用将给定的key 对应的(key-value)对删除.返回被删除的value值.
 */
public class MapDemo1 {

    public static void main(String[] args) {
        //1.创建一个Map接口的实现类HashMap
        Map<String,Integer> scoreMap = new HashMap<String,Integer>();

        /*
         * 2.将给定的key-value存入到Map中。
         * 注意:
         * 因为Map不允许包含重复的key存在,所以若有两个key的equals判断结果为true,
         * 那么put方法会替换并返回原来的value值.
         * 若没有相同的key存在时,put方法的返回值为null.
         */
        Integer returnValue = scoreMap.put("语文", 99);
        /*此时因为Map中没有key.equals("语文"), 所以put方法返回值应该是null,*/
        System.out.println("第一次实验性输出语句  put方法返回值是 :"+returnValue);
        /*在此处,输出语句,试验一下,输出put返回值,检验是否是null.
            输出结果是null, 那么证明当存入Map的key值时,无相同的key时,返回值为null.*/

        //加入一批key-value对到scoreMap中
        scoreMap.put("英语",97);
        scoreMap.put("物理",92);
        scoreMap.put("政治",80);
        System.out.println("scoreMap表: "+scoreMap);

        /*
         * 思考?如果有一个名为"数学"这个key值原来就存在,并关联的value值是null,
         * 则put会返回什么结果?
         * */
        scoreMap.put("数学",null);
        returnValue = scoreMap.put("数学", 98);
        System.out.println("第二次实验性输出语句  put方法返回值是 :"+returnValue);    
        /*根据上面的思考,我们做个测试,先放入"数学"key关联value值为null
         * 输出结果同样是null.证明即还有一种可能的现象,当一种key存在并关联的value值为null,
         * 存入相同key时,也会返回null.*/

        /*
         * 3.获取数据的方法
         * V get (Object key)
         * 获取指定的key所对应的value,若指定的key在Map中不存在,则返回null
         */
        returnValue = scoreMap.get("语文");
        System.out.println("语文的成绩: "+returnValue); //语文的成绩: 99

        /* 4.检查是否包含"体育"这个key*/
        boolean containSport = scoreMap.containsKey("体育");
        System.out.println("是否包含体育的结果: "+containSport);    
        //是否包含体育的结果: false

        /*
         * 5.删除数据的方法
         */
        returnValue = scoreMap.remove("数学");
        System.out.println("删除的数学value值为: "+returnValue);
        //删除的数学value值为: 98
    }
}
View Code

MapDemo2  Map的遍历 

/**
 *     Map的遍历 
    遍历Map有三种方式: 
    1:遍历所有的key:    Set<K> keySet()
    返回值: Set集合(因为Set集合是无序,不可重复的,而Map的key正好是无序的,不可重复的)

    2:遍历所有的key-value对:    Set<Map.Entry<K,V>> entrySet()
    返回值: Set集合    上面的方法可以理解成: Set<Entry> entrySet()
    注意: 1.这里的Entry(Map.Entry)是Map的内部类,每一个Entry实例都表示Map中的一组key-value对
         2.Entry提供了获取key,value的方法:  K getKey()  ,  V getValue()
         3.entrySet方法则是将每一组key-value对(即:若干Entry实例)存入一个Set集合后返回。

    3:遍历所有的value(相对不常用)   Collection<V> values()
    返回值: Collection<V>集合
    将Map中所有的value存入一个集合后返回。
    注意: 因为value可以重复,所以不能是Set集来接收了.

    总结:  1.三种遍历方式,相对复杂的是第2个.但三种实用性不高,单纯的取出value值意义不大,除非有特别的用处.
          2.遍历key和遍历entry(key-value)对,注意用Set集接收,因为是不重复无序的集合.
          3.遍历entry注意书写格式.还有entry代表的是一组数据.
 */
public class MapDemo2 {

    public static void main(String[] args) {
        //定义一个Map接口的实现类HashMap的对象.
        Map<String,Integer> scoreMap = new HashMap<String,Integer>();
        //添加一批数据
        scoreMap.put("语文", 90);
        scoreMap.put("数学", 82);
        scoreMap.put("化学", 66);
        scoreMap.put("政治", 67);
        scoreMap.put("历史", 80);

        /*
         * 1.遍历所有的key 方法  Set<K> keySet()
         * */
        Set<String> scoreKeySet = scoreMap.keySet();
        //注意返回值为Set<K> 集合.
        //遍历Set集合(新循环或者迭代器)
        for(String key: scoreKeySet){
            System.out.println("遍历scoreMap表的key值 : "+key);
        /*    输出结果:
            遍历scoreMap表的key值 : 政治
            遍历scoreMap表的key值 : 历史
            遍历scoreMap表的key值 : 数学
            遍历scoreMap表的key值 : 化学
            遍历scoreMap表的key值 : 语文
         */
        }

        /*
         * 2.遍历所有的key-value对方法  Set<Entry> entrySet()
         * 注意: Entry是个实例,每一个Entry实例, 代表的是Key和Value对一组数据.
         *         遍历时,也应当注意书写格式 .循环体内,用Entry自己的get方法,获取key和value的值.
         */
        Set<Entry<String,Integer>> scoreEntrySet = scoreMap.entrySet();
        for(Entry<String,Integer> entry : scoreEntrySet){
            String subject = entry.getKey();
            Integer score = entry.getValue();
            System.out.println("科目 :" + subject + "  分数 :" + score);
            /*    输出结果:
                科目 :政治  分数 :67
                科目 :历史  分数 :80
                科目 :数学  分数 :82
                科目 :化学  分数 :66
                科目 :语文  分数 :90
            */
        }

        /*
         * 3.遍历所有的Value值(不常用)  Collection<V> values()
         * 注意: 因为value可以重复,所以不能是Set集来接收了.
         */
        Collection<Integer> valueCollect = scoreMap.values();
        for(Integer value:valueCollect){
            System.out.println("分数:"+ value);
            /*
                    输出结果:
                分数:67
                分数:80
                分数:82
                分数:66
                分数:90
             */
        }
    }
}
View Code

HashMap原理

HashMap是Map的一个常用的子类实现。其实使用散列算法实现的。

HashMap内部维护着一个散列数组(就是一个存放元素的数组),我们称其为散列桶,而当我们向HashMap中存入一组键值对时,HashMap首先获取key这个对象的hashcode()方法的返回值,然后使用该值进行一个散列算法,得出一个数字,这个数字就是这组键值对要存入散列数组中的下标位置。

那么得知了下标位置后,HashMap还会查看散列数组当前位置是否包含该元素。(这里要注意的是,散列数组中每个元素并非是直接存储键值对的,而是存入了一个链表,这个链表中的每个节点才是真实保存这组键值对的。)检查是否包含该元素时根据当前要存入的key在当前散列数组对应位置中的链表里是否已经包含这个key,若不包含则将这组键值对存入链表,否则就替换value。

那么在获取元素时,HashMap同样先根据key的hashcode值进行散列算法,找到它在散列数组中的位置,然后遍历该位置的链表,找到该key所对应的value之后返回。

看到这里可能有个疑问,链表中应该只能存入一个元素,那么HashMap是如何将key-value存入链表的某个节点的呢?实际上,HashMap会将每组键值对封装为一个Entry的实例,然后将该实例存入链表。

在散列表中有一下名词需要了解:

  • Capacity:容量, hash表里bucket(桶)的数量, 也就是散列数组大小.
  • Initial capacity:初始容量, 创建hash表的时 初始bucket的数量, 默认构建容量是16. 也可以使用特定容量.
  • Size : 大小, 当前散列表中存储数据的数量.
  • Load factor:加载因子, 默认值0.75(就是75%), 向散列表增加数据时如果 size/capacity 的值大于Load factor则发生扩容并且重新散列(rehash).

那么当加载因子较小时候散列查找性能会提高, 同时也浪费了散列桶空间容量. 0.75是性能和空间相对平衡结果. 在创建散列表时候指定合理容量, 从而可以减少rehash提高性能。

 

/*
* 当一个类作为HashMap中的key使用时,它的equals方法与hashcode方法的结果直接影响散列表的查询性能。
* 原因: 根据HashMap表的原理.详见图.
* API手册中明确说明:当我们重写一个类的equals方法时就应当连同重写hashCode方法。
* 重写hashCode方法时应遵循:
* 1:稳定性:当参与equals比较的属性的值没有发生过改变的前提下,多次调用hashCode方法返回的数字应当相同。
* 2:一致性: 当两个对象equals比较为true时,那么hashcode方法返回的数字必须相同
             反过来如果hashcode相同,equals的结果虽然不强制,但也尽量保证一致。
              因为若两个对象hashcode值相同,但是equal方法比较为false,在散列表中会产生链表,影响查询性能  
*/
public class KeyHashCode {
    private int x;
    private int y;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        KeyHashCode other = (KeyHashCode) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }
}
View Code

 

posted @ 2016-04-30 15:03  安仔80  阅读(238)  评论(0编辑  收藏  举报