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

十三、Java集合框架之Map集合

程序员文章站 2024-02-08 21:39:52
...

1、Map接口简介

集合根据数据存储的不同分为两种形式:单值集合、二元偶对象集合,在之前所使用的Collection都属于单值集合。
现在所学习的Map属于二元偶对象集合,所谓的二元偶对象指的是存储的数据为“key=value”结构对,在使用的时候可以根据key查询出相应的value的内容。

所以Collection和Map存储数据的目的分别为:Collection是为了数据的输出而存储,而Map是为了数据的查询而存储。

java.util.Map是进行二元偶对象数据存储的最大父接口,在里面所有存放的内容会按照“key=value”的形式进行保存,所以在数据存放的时候就需要保存有两个内容,Map接口的常用方法如下:

V put(K key, V value) 
          将指定的值与此映射中的指定键关联(向集合中保存数据,如果key存在则发生替换,
          同时返回旧的内容,如果key不存在返回的内容为空) (非常重要)
 V get(Object key) 
          返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 (非常重要)
 V remove(Object key) 
          如果存在一个键的映射关系,则将其从此映射中移除 
 int size() 
          返回此映射中的键-值映射关系数(获取集合长度) 
Collection<V> values() 
          返回此映射中包含的值的 Collection 视图  (返回所有内容)
 Set<K> keySet() 
          返回此映射中包含的键的 Set 视图  (获取所有的key,key不可重复)
          (key一旦重复,会使用新的内容去替换旧的内容)
Set<Map.Entry<K,V>> entrySet() 
          返回此映射中包含的映射关系的 Set 视图  (将所有的内容以Map.Entry集合的形式返回) (非常重要)

十三、Java集合框架之Map集合
在JDK1.9之后在Map接口之中提供有许多的of)方法,利用此方法可以方便的创建一个key不重复的Map集合

**例:**创建Map集合

public class Demo05 {

    public static void main(String[] args) {
        Map<String,Integer> map =Map.of("one",1,"two",2,"three",3);
        System.out.println(map);

    }

}

程序执行结果:{two=2, three=3, one=1}

通过结果可以发现,此时的程序会按照“key=value”的形式进行保存,同时此时的存储是无序的(Map的功能是进行查询,是否有序意义不大),但是使用此种方式创建的Map集合,如果出现有key重复的问题,那么程序就会出现如下的异常:
Exception in thread"main"java.lang.Illegal ArgumentException:kluplicate key:

因为key作为Map操作的核心控制点,所以这个内容的重复实际上对于整个的Map而言就需要更新,如果想要正确的使用Map接口,那么就必须使用它的几个子类,常见的子类:HashMap、LinkedHashMap、TreeMap、HashTable。

2、HashMap

HashMap是Map接口中最为常见的一个子类,也是主要使用的一个子类,此类通过名称就可以发现,采用Hash的方式进行存储,所以其存储的时候都是无序的,此类的定义结构如下:

public class HashMap<K,V> extends  AbstractMap<K,V>
           implements Map<K,V>, Cloneable, Serializable

十三、Java集合框架之Map集合
:使用HashMap进行数据存储

public class Demo05 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        System.out.println("【未发生替换】"+map.put("hello","you"));
        System.out.println("【已发生替换】"+map.put("hello","guys"));
        System.out.println("【未发生替换】"+map.put("wow","gays"));

        map.put("empty",null);
        map.put(null,"empty");

        System.out.println(map);
    }
}

程序执行结果:
【未发生替换】null (由于没有相同的key,所以返回内容是null)
【已发生替换】you (返回旧的数据)
【未发生替换】null (由于没有相同的key,所以返回内容是null)
{null=empty, hello=guys, wow=gays, empty=null}
通过以上的存储可以发现,Map中的key绝对是唯一的标记,不可能重复,同时在Map集合里面也可以实现null的内容存储。 HashMap允许key 和 value 同时为空
使用Map集合的意义在于需要根据key进行内容的查找。

**例:**根据key查找数据

public class Demo05 {

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("hello","you");
        map.put("q","you");
        map.put("w","you");
        map.put("e","you gays");

        System.out.println(map.get("e"));
        System.out.println(map.get("everyone"));
    }

}

程序执行结果:
you gays (设置的key为e 可以正常查询)
null (由于key不存在返回的结果就是null)

如果只是进行方法的使用研究那么对于HashMap而言意义不大,因为必须要关注HashMap的源代码实现机制。(JDK1.8之后HashMap算法进行了重大的变更)。

分析HashMap源码实现机制

1、无参构造

 public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;    // all other fields defaulted
    }
    
 static final float DEFAULT_LOAD_FACTOR = 0.75f;  (默认的扩充阀值为“75%”)
 final float loadFactor;

2、put()方法

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

3、putVal()方法 实现了节点的相关创建以及扩容的调用(resize())

4、resize()

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 (默认容量大小为16个)
 transient Node<K,V>[] table;  (默认根据数组形式存储)
 static final int MAXIMUM_CAPACITY = 1 << 30; (最大存储30位)
 oldCap<<1(每次扩容1倍)

5、性能保证 static final int TREEIFY_THRESHOLD=8; (树状阈值)

if (binCount>=TREEIFY THRESHOLD-1)
treeifyBin(tab,hash);  //进行树状结构转换
 break;

对于HashMap来讲,如果要进行扩容,则表示当前的存储容量达到“75%”的时候才会选择扩容。在JDK1.8之后如果HashMap中的数据存储容量达到了8位,为了保证数据的查询性能,HashMap会将原始的链表存放结构转为红黑树结构进行保存,利用红黑树中自旋的处理实现树的平衡修复。

3、LinkedHashMap

HashMap之中进行数据存储的时候并不会进行顺序的定义,所以如果现在要想实现顺序的存储,就可以利用LinkedHashMap子类来完成,这个类的定义如下:

public class LinkedHashMap <K,V> extends HashMap <K,V> 
                                        implements Map<K,V>

十三、Java集合框架之Map集合
**例:**观察LinkedHashMap实现

public class Demo06 {

    public static void main(String[] args) {
        Map<String, String> map = new LinkedHashMap<>();
        map.put("hello","you");
        map.put("q","you");
        map.put("w","you");
        map.put("e","you gays");

        System.out.println(map);
    }

}

程序执行结果:
{hello=you, q=you, w=you, e=you gays}

4、TreeMap

java.util.TreeMap实现的是一个排序的树结构,可以依据key的自然顺序实现排序的处理,此类的定义如下:

public class TreeMap<K,V>extends AbstractMap<K,V>
                implements NavigableMap<K,V>, Cloneable, Serializable

十三、Java集合框架之Map集合
:使用TreeMap进行排序

public class Demo06 {

    public static void main(String[] args) {
        Map<String, String> map = new TreeMap<>();
        map.put("hello","you");
        map.put("q","you");
        map.put("empty",null);
//        map.put(null,"empty");

        System.out.println(map);
    }

}

程序执行结果:
{empty=null, hello=you, q=you}

既然此时是要进行key数据的排序,那么在使用的过程之中key的数据内容就绝对不能为null

5、Hashtable

类集中有三老(出现时间很古老,让人聊起来觉得很显老,使用起来觉得资格很老),Vectory、Enumeration、Hashtable常见的三老,Hashtable是在JDK1.0的时候提出的集合操作最早偶对象存储。
Hashtable定义如下:

public class Hashtable<K,V>extends Dictionary<K,V>
                   implements Map<K,V>, Cloneable, Serializable

Dictionary是最早实现字典存储结构的父类。

十三、Java集合框架之Map集合
例:使用Hashtable

public class Demo06 {

    public static void main(String[] args) {
        Map<String, String> map = new Hashtable<>();
        map.put("hello","you");
        map.put("q","you");
//        map.put("empty",null);
//        map.put(null,null);  //key value 都为空

        System.out.println(map);
    }

}

程序执行结果:{hello=you, q=you}
Hashtable在进行数据存储的时候是不允许存放null数据的,不管是key还是value,如果发现有null,那么最终都会出现“NullPointerException”。
而相比较HashMap不管是在key还是在value上都没有关于null的限制。

面试题:请解释HashMap与Hashtable的区别?

  • HashMap在进行存储的时候默认的大小为16,在存储量达到8位之后为了保证数据的查询性能使用红黑树进行存储;HashMap中的全部方法都使用异步处理,属于非线程的安全操作; HashMap存储的key和value都允许为null。
  • Hashtable进行存储时默认的大小为11;Hashtable中的方法使用同步处理,属于线程安全的操作。Hashtable存储的key和value都不允许为null。

关于红黑树 推荐看这篇博文 漫画讲解红黑树

6、Map.Entry

通过一系列的分析之后实际上就可以得出整个Map接口的基本使用情况,但是对于数据的存储必须进行详细说明,Map集合与Collection集合的最大不同在于,它所存储的数据是二元偶对象。

Collection:
十三、Java集合框架之Map集合
Map:
十三、Java集合框架之Map集合
在Map里面由于需要存放有两个内容,很明显为了可以进行整体的处理方便,就在Map接口里面定义有一个Map.Entry的内部接口,此接口主要是进行key和value封装的,而且在Map接口里面也可以发现Map.Entry的子类

static class Node<K,V> implements Map. Entry<K,V>{
final int hash;
 final K key;
 V value; 
 Node<K,V>next;
 }

Map.Entry 里面可以包装key和value,那么也可以通过Map.Entry获取对应的key和value此接口定义如下:

public static interface Map.Entry<K,V>

关键方法

 K getKey() 
          返回与此项对应的键。 
 V getValue() 
          返回与此项对应的值。 

:创建Map.Entry对象
·JDK1.9之后增加的方法:static <K,V> Map.Entry <K,V> entry(K k,V v)

public class Demo06 {

    public static void main(String[] args) {
        Map.Entry<String,String> entry = Map.entry("hello","guys");
        System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());

    }

}

程序执行结果:
key:hello value:guys

Map.Entry实质上定义了一个Map偶对象的存储标准,所有的Map接口的子类都依据此标准实现相应的节点数据的存储,可以直接利用此实例实现key与value的分离。

7、Iterator输出Map集合

面对于集合数据的输出,肯定要考虑使用Iterator接口完成,但是在Map接口中并没有任何一个方法可以直接获取到Iterator接口实例,所以此时就必须经过一系列的转换得来。

之所以在Map接口中没有提供有直接获取Iterator接口实例的方法,原因就在于它的存储不是一个普通的数据,而是一个偶对象,而Iterator每一次可以输出的全部都是单个实例为此基本的输出流程如下:

  • 1、通过Map接口中的entrySet()方法,将Map实例转为Set接口实例;
Set<Map.Entry<K,V>> entrySet() 
          返回此映射中包含的映射关系的 Set 视图 
  • 2、获取了Set集合实例之后就可以调用iterator()方法获取Iterator接口实例,泛型类型为“Map.Entry<K,V>”;

  • 3、通过Iterator进行迭代操作,获取每一组的“Map.Entry<K,V>”实例,进行key与value的分离。

**例:**通过entrySet()方法实现Iterator输出

public class Demo06 {

    public static void main(String[] args) {
        Map<String, String> all = new HashMap<>();
        all.put("hello", "你好");
        all.put("gyus", "伙计们");
        all.put("happay", "非常开心见到你们");

        Set<Map.Entry<String, String>> set = all.entrySet();
        Iterator<Map.Entry<String,String>> it = set.iterator();
        while (it.hasNext()){
            Map.Entry<String,String> entry = it.next();
            System.out.println("key="+entry.getKey()+"  "+"value="+entry.getValue());
        }
    }

}

程序执行结果:
key=gyus value=伙计们
key=happay value=非常开心见到你们
key=hello value=你好

十三、Java集合框架之Map集合
从JDK1.5之后Map集合也可以使用foreach进行输出,因为其内部实现了Iterator接口:
源代码:

final class EntryIterator extends HashIterator implements Iterator<Map. Entry<K,V>>{
public final Map. Entry<K,V> next(){ 
return nextNode();
}
}

:使用foreach输出

public class Demo06 {

    public static void main(String[] args) {
        Map<String, String> all = new HashMap<>();
        all.put("hello", "你好");
        all.put("gyus", "伙计们");
        all.put("happay", "非常开心见到你们");

        for(Map.Entry<String,String> entry:all.entrySet()){
            System.out.println("key="+entry.getKey()+"  "+"value="+entry.getValue());
        }
    }

}

程序执行结果:
key=gyus value=伙计们
key=happay value=非常开心见到你们
key=hello value=你好

**例:**使用keySet()方法实现Iterator输出

public class Demo06 {

    public static void main(String[] args) {
        Map<String, String> all = new HashMap<>();
        all.put("hello", "你好");
        all.put("gyus", "伙计们");
        all.put("happay", "非常开心见到你们");

        Set<String> set = all.keySet();
        Iterator<String> it = set.iterator();
        while (it.hasNext()){
            String key = it.next();
            String value = all.get(key);
            System.out.println("key="+key+"  "+"value="+value);

        }
    }

}

程序执行结果:
key=gyus value=伙计们
key=happay value=非常开心见到你们
key=hello value=你好

不管是何种集合最终的输出的归宿只有一点就是通过Iterator,但是需要注意的是,Map一般很少直接输出,因为其功能主要是进行数据查询。

8、自定义key类型

对于此时的Map集合可以发现,设置的K和V两个泛型类型只要是引用数据类型就可以了,这也就包括了自定义的类,即自定义的类也可以成为Map中的key类型。

但是此时作为key类型所在的类一定要覆写hashCode()与equals()两个方法,因为牵扯到对象比较问题

Map集合根据key获取数据时的流程:
1、利用hashCode()方法生成结果进行比较,因为这只是一个数字,它的比较速度会更加快;
2、如果发现哈希码相同的时候才会进行内容的比较。

class Member{
    private String name;
    private int age;

    public Member(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "姓名:"+this.name+"  "+"年龄:"+this.age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Member member = (Member) o;
        return age == member.age &&
                Objects.equals(name, member.name);
    }

    @Override
    public int hashCode() {

        return Objects.hash(name, age);
    }

}


public class Demo07 {

    public static void main(String[] args) {
        Map<Member,String> map = new HashMap<>();
        map.put(new Member("张三",10),new String("张三"));

        System.out.println(map.get(new Member("张三",10)));
    }

}

程序执行结果:
张三

在以后实际编写的代码过程之中,如果遇见了Map集合,则一般对于Map集合中的Key类型,最为常见的是String,其次是Long或者Integer。

哈希冲突(了解)

结论:
哈希码实际上是进行对象比较的关键所在,而在进行Map存储的时候实际上也是依靠哈希码得到的一个存储空间,但是在很多情况下依然有可能会出现哈希冲突的问题。
那么这个时候的解决方案(四种解决方案):

  • 1、开放定址法
  • 2、链地址法
  • 3、再哈希法
  • 4、建立公共溢出区
    而Java是利用链地址法的形式解决的。把所有重复的内容放在一个链表之中进行保存。
    十三、Java集合框架之Map集合

总结

1、只要碰见单值集合优先考虑使用List,List接口优先考虑的是ArrayList;

2、只要进行key的内容查找操作,就属于Map接口,Map接口优先考虑HashMap子类

3、集合的输出全部使用Iterator接口完成。

相关标签: Java重点知识

上一篇: Vue项目优化小结

下一篇: 2020-11-17