Map集合

  之前对Collection接口以及其对应的子接口已经有所了解,可以发现在Collection接口之中所保存的数据全部都只是单个对象,而在数据结构中除了可以进行单个对象的保存外,也可以进行二元偶对象的保存(key=value)的形式来存储,而存储二元偶对象的核心意义在于需要通过key获取对应的value。

在开发中,Collection集合保存数据的目的是为了输出,Map集合保存数据的目的是为了进行key的查找。

Map接口简介

  Map接口是进行二元偶对象保存的最大父接口。该接口定义如下:

public interface Map<K,V>

  该接口为一个独立的父接口,并且在进行接口对象实例化的时候需要设置K与V的类型,也就是在整体操作的时候需要保存两个内容,在Map接口中定义有许多操作方法,但是需要记住以下的核心操作方法:

  • 向集合中保存数据V put​(K key,V value)
  • 根据key查询数据V get​(Object key)
  • 将Map集合转为Set集合Set<Map.Entry<K,V>> entrySet()
  • 指定key是否存在:boolean containsKey​(Object key)
  • 将Map集合中的key转为Set集合:Set<K> keySet()
  • 根据key删除指定的数据:V remove​(Object key)

  从JDK1.9后Map接口中也扩充了一些静态方法提供用户使用。
范例:观察Map集合的特点

import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
//        Map<String,Integer> map=Map.of("one",1,"two",2);
//        System.out.println(map);//{two=2, one=1}
//       
//        Map<String,Integer> map=Map.of("one",1,"two",2,"one",101);
//        System.out.println(map);//Exception in thread "main" java.lang.IllegalArgumentException: duplicate key: one
        
        Map<String,Integer> map=Map.of("one",1,"two",2,null,0);
        System.out.println(map);//Exception in thread "main" java.lang.NullPointerException
    }
}

在Map集合中数据的保存就是按照“key=value”的形式存储的,并且使用of()方法时里面的key是不允许重复,如果重复则会出现java.lang.IllegalArgumentException: duplicate key: one,如果设置的key或value为null,则会出现java.lang.NullPointerException

  对于现在使用的of()方法严格意义上来说并不是Map集合的标准用法,因为正常的开发中需要通过Map集合的子类来进行接口对象的实例化,而常用的子类:HashMap、HashTable、TreeMap、LinkedHashMap。

image.png

HashMap子类

  HashMap是Map接口中最为常见的一个子类,该类的主要特点是无序存储,通过Java文档首先来观察一下HashMap子类的定义:

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

public abstract class AbstractMap<K,V> extends Object implements Map<K,V>

该类的定义继承形式符合之前的集合定义形式,依然提供有抽象类并且依然需要重复实现Map接口。

范例:观察Map集合的使用

import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String,Integer> map=new HashMap();
        map.put("1",1);
        map.put("2",2);
        map.put("1",101);//key重复
        map.put(null,0);//key为null
        map.put("0",null);//value为null
        System.out.println(map.get("1"));//key存在:101
        System.out.println(map.get(null));//key存在:0
        System.out.println(map.get("3"));//key不存在:null
    }
}

以上的操作形式为Map集合使用的最标准的处理形式,通过代码可以发现,通过HashMap实例化的Map接口可以针对key或者value保存null的数据,同时也可以发现即便保存数据的key重复,那么也不会出现错误,而是内容的覆盖。

  对于Map接口中提供的put()方法本身是提供有返回值的,那么这个返回值指的是在重复key的情况下返回旧的value。
范例:观察put()方法

import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new HashMap();
        Integer oldValue = map.put("1", 1);
        System.out.println(oldValue);//key不重复,返回null:null
        oldValue = map.put("1", 101);
        System.out.println(oldValue);//key重复,返回旧数据:1
    }
}

在设置了相同key的内容时,put()方法会返回原始的数据内容。

  清楚了HashMap的基本功能后下面就需要来研究一下HashMap中给出的源代码。

final float loadFactor;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

当使用无参构造的时候,会出现有一个loadFactor属性,并且该属性默认的内容为“0.75”

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

在使用put()方法进行数据保存时会调用一个putVal()方法,同时会将key进行hash处理(生成一个hash编码),而对于putVal()方法中会发现会提供一个Node节点类进行数据的保存,而在使用putVal()方法操作的过程中,会调用一个resize()方法可以进行容量的扩充。

LinkedHashMap

  HashMap虽然是Map集合中最为常用的子类,但是其本身保存的数据都是无序的(有序与否对Map没有影响),如果现在希望Map集合中的保存的数据的顺序为其增加的顺序,则就可以更换子类为LinkedHashMap(基于链表实现的),观察LinkedHashMap的定义:

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

  既然是链表保存,所以一般在使用LinkedHashMap类时数据量不要特别大,因为会造成时间复杂度攀升,通过继承的结构可以发现LinkedHashMap是HashMap的子类,继承关系如下:

LinkedHashMap

范例:使用LinkedHashMap

import java.util.LinkedHashMap;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new LinkedHashMap<String, Integer>();
        map.put("1", 1);
        map.put("2", 2);
        map.put("5", 5);
        map.put("4", 4);
        map.put("3", 3);
        System.out.println(map);//{1=1, 2=2, 5=5, 4=4, 3=3}
    }
}

通过此时的程序执行可以发现当使用LinkedHashMap进行存储之后所有数据的保存顺序为添加顺序。

HashTable

  HashTable类是从JDK1.0时提供的,与Vector、Enumeration属于最早的一批动态数组的实现类,后来为了将其继续保留下来,所以让其多实现了一个Map接口,HashTable的定义如下:

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

public abstract class Dictionary<K,V> extends Object

  HashTable的继承结构如下:

HashTable

范例:观察HashTable类的使用

import java.util.Hashtable;
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new Hashtable();
        map.put("1", 1);
        map.put("2", 2);
        map.put("5", 5);
        map.put(null, 4);//不能为空
        map.put("3",null);//不能为空
        System.out.println(map);
        //Exception in thread "main" java.lang.NullPointerException
    }
}

通过观察可以发现在HashTable中进行数据存储时设置的key和value都不允许为null,否则会出现NullPointerException异常。

Map.Entry接口

  虽然清楚了整个Map集合的基本操作形式,但是依然有一个核心问题要解决,Map集合中是如何进行数据存储的?对于List而言(LinkedList)依靠的是链表的形式实现的数据存储,那么在进行数据存储的时一定要将数据保存在Node节点中,虽然HashMap中也可以见到Node类型定义,通过源代码定义可以发现,HashMap类中Node内部类本身实现Map.Entry接口。

static class Node<K,V> implements Map.Entry<K,V> 

  所以可以得出结论:所有的key和value的数据都被封装在Map.Entry接口之中,而此接口定义如下:

public static interface Map.Entry<K,V>

  在这个内部接口中提供了两个重要的操作方法:

  • 获取key:K getKey()
  • 获取value:V getValue()

  在JDK1.9以前的开发版本中,开发者基本上都不会去考虑创建Map.Entry的对象,实际上在正常的开发过程中开发者也不需要关心Map.Entry对象,但是从JDK1.9后,Map接口追加有一个新的方法:

  • 创建Map.Entry对象:static <K,V> Map.Entry<K,V> entry​(K k, V v)
    范例:创建Map.Entry对象
import java.util.Map;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map.Entry<String, Integer> entry = Map.entry("one",1);
        System.out.println(entry.getKey());//one
        System.out.println(entry.getValue());//1
        System.out.println(entry.getClass().getName());//java.util.KeyValueHolder
    }
}

通过分析可以发现在整个Map集合中,Map.Entry的主要作用就是作为一个Key和Value的包装类型使用,而大部分情况下在进行数据存储的时候都会将Key和Value包装为一个Map.Entry对象进行使用。

使用Iterator输出Map集合

  对于集合的输出而言,最标准的做法就是利用Iterator接口来完成,但是需要明确一点就是在Map集合中并没有方法可以直接返回Iterator接口对象,所以这种情况下就必须分析不直接提供Iterator接口实例化的方法的原因,下面对Collection和Map集合的存储结构进行一个比较。

Collection与Map

  发现在Map集合中保存的实际上是一组Map.Entry接口对象(里面包装的是Key与Value),所以整个来说Map依然实现的是单值的保存,查看文档可以发现Map集合中提供了一个方法“Set<Map.Entry<K,V>> entrySet()”,将全部的Map集合转为Set集合。

  经过分析可以发现,如果想要使用Iterator实现Map集合的输出则必须按照如下步骤处理:

  • 利用Map接口中提供的entrySet()方法见Map集合转为Set集合;
  • 利用Set接口中的iterator()方法将Set集合转为Iterator接口实例;
  • 利用Iterator进行迭代输出获取每一组的Map.Entry对象,随后通过getKey()和getValue()获取数据。

范例:利用Iterator输出Map集合

import java.util.Iterator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new HashMap();
        map.put("one", 1);
        map.put("two", 2);
        Set<Map.Entry<String, Integer>> set = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }
}

虽然Map集合可以支持迭代输出,但是从实际开发来说,Map集合最主要的用法在于实现数据的Key查找操作,另外需要注意的是,如果现在不适用Iterator而使用foreach语法输出则也需要将Map集合转为Set集合。

范例:利用foreach输出Map集合

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<String, Integer> map = new HashMap();
        map.put("one", 1);
        map.put("two", 2);
        Set<Map.Entry<String, Integer>> set = map.entrySet();
        for (Map.Entry<String, Integer> entry:set) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }
}

由于Map迭代输出的情况相对较少,所以对于此类的语法应该深入理解一下,并且一定要灵活掌握。

关于KEY的定义

  在使用Map集合时可以发现对于Key和Value的类型实际上都可以由开发者任意决定,那么意味着现在依然可以使用自定义类来进行Key类型的设置。对于自定义Key类型所在类中一定要覆写hashCode()和equals()方法,否则无法查找到。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

在进行数据保存的时候发现会自动使用传入的key的数据数据生成一个hash码,也就是说存储的时候是有这个Hash数值。
在根据key获取数据的时候依然要将传入的key通过hash()方法来获取其对应的hash码,也就是说查询的过程中首先要利用hashCode()来进行数据查询,当使用getNode()方法查询时还需要使用到equals()方法。

范例:使用自定义类作为Key类型

import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
@AllArgsConstructor
class Person{
    private String name;
    private int age;
}
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Map<Person, String> map = new HashMap();
        map.put(new Person("张三",20), "HELLO");
        map.put(new Person("李四",31), "WORLD");
        System.out.println(map.get(new Person("张三",20)));//null
    }
}

虽然运行你使用自定义的类作为Key的类型,但是而需要注意一点,在实际的开发之中对于Map集合的Key常用的三种类型:String、Long、Integer。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • 12.1 Map集合 Map集合用于保存具有映射关系的数据,key和value都可以是任意类型的数据,key不允许...
    王毅巽阅读 825评论 0 3
  • Map 今天的主要内容 Map接口概述 Map集合的两种遍历方式通过键找值keySet通过键值对对象获取键和值涉及...
    须臾之北阅读 253评论 0 0
  • HashMap数据结构 HashMap是基于hashing的原理; value>插到table[i]中,如果有两个...
    魏树鑫阅读 524评论 0 1
  • Map接口不属于Collection的继承或实现,Map接口是维护键值对的,并且不能有重复的键,但是在Map的底层...
    螺丝钉25阅读 293评论 0 0
  • 正在赶来战场的路上 你听 DurDurDur!!! 明天进入状态喔! 今天没有Dur! 任性!
    Echo也可以阅读 362评论 0 0