Java高级编程

Map集合

2019-08-06  本文已影响17人  江湖非良人

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

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

Map接口简介

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

public interface Map<K,V>

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

  从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>

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

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

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集合的输出则必须按照如下步骤处理:

范例:利用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。

上一篇下一篇

猜你喜欢

热点阅读