Map接口 | 带你学《Java语言高级特性》之一百零七

简介: 本节介绍了Map接口以及它的子类HashMap、LinkedHashMap、HashTable等,需要掌握他们的基本用法。

上一篇:集合输出 | 带你学《Java语言高级特性》之一百零六
【本节目标】
本节介绍了Map接口以及它的子类HashMap、LinkedHashMap、HashTable等,需要掌握他们的基本用法。

Map接口

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

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

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

public interface Map<K,V>

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

No. 方法名称 类型 描述
01 public V put​(K key,V value) 普通 向集合中保存数据
02 public V get​(Object key) 普通 根据key查询数据
03 public Set<Map.Entry<K,V>> entrySet() 普通 将Map集合转为Set集合
04 public boolean containsKey​(Object key) 普通 查询指定的key是否存在
05 public Set<K> keySet() 普通 将Map集合中的key转为Set集合
06 public V remove​(Object key) 普通 根据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);     //{one=1,two=2}
//       
//        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()方法操作时里面的数据是不允许重复,如果重复则会出现“IllegalArgumentException”异常,如果设置的内容为null,则会出现“NullPointerException”异常。

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

HashMap子类

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

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

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

image.png
HashMap子类

范例:观察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<String,Integer>();
        map.put("one",1);
        map.put("two",2);
        map.put("one",101);    //key重复
        map.put(null,0);       //key为null
        map.put("zero",null);    //value为null
        System.out.println(map.get("one"));    //key存在:101
        System.out.println(map.get(null));    //key存在:0
        System.out.println(map.get("ten"));     //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<String,Integer>();
        System.out.println(map.put("one", 1));    //key不重复,返回null:null
        System.out.println(map.put("one", 101));   //key重复,返回旧数据:1
    }
}

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

清楚了HashMap的基本功能之后,接下来就需要研究一下HashMap中给出的源代码。HashMap之中肯定需要存储大量的数据,那么对于数据的存储,来看看HashMap是怎样操作的:

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

当使用无参构造的时候,会出现有一个loadFactor属性,并且该属性默认的内容为“0.75”(static final float DEFAULT_LOAD_FACTOR = 0.75f;

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

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

面试题:在进行HashMap的put()操作时,如何实现容量扩充?

  • 在HashMap类中提供了一个“DEFAULT_INITIAL_CAPACITY”的常量,作为初始化的容量配置,而这个常量的默认大小为16个元素,也就是说默认的可以保存的最大内容是16;
  • 当保存的内容的容量超过了一个阈值(DEFAULT_LOAD_FACTOR=0.75f),相当于“容量*阈值=12”保存12个元素的时候就会进行容量的扩充;
  • 在进行扩充的时候HashMap采用的是成倍的扩充模式,即:每一次都扩充2倍的容量。

面试题:请解释HashMap的工作原理(JDK1.8之后开始的)

  • 在HashMap中进行数据存储依然是利用Node类完成的,那么这种情况下就证明可以使用的数据结构只有两种:链表(时间复杂度“O(n)”)、二叉树(时间复杂度“O(logn)”);
  • 从JDK1.8开始,HashMap的实现出现了改变,因为其要适应于大数据时代的海量数据问题,所以对其存储发生了变化,并且在HashMap类的内部提供有一个常量:“static final int TREEIFY_THRESHOLD = 8;”,在使用HashMap进行数据保存时,如果保存的数据没有超过阈值8(TREEIFY_THRESHOLD),那么会按照链表的形式进行存储,如果超过了阈值,则会将链表转为红黑树以实现树的平衡,并且利用左旋与右旋保证数据的查询性能。

LinkedHashMap子类

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

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

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

image.png
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("one", 1);
        map.put("two", 2);
        map.put("one", 101);
        map.put("null", 0);
        map.put("zero", null);
        System.out.println(map);    //{one=101, two=2, null=0, zero=null}
    }
}

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

HashTable子类

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

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

HashTable的继承结构如下:

image.png
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<String,Integer>();
        map.put("one", 1);
        map.put("two", 2);
        map.put("one", 101);
        // map.put(null, 0);     //不能为空
        // map.put("zero",null);   //不能为空,Exception in thread "main" java.lang.NullPointerException
        System.out.println(map);  // {two=2, one=101}
    }
}

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

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

  • HashMap中的方法都属于异步操作,非线程安全,HashMap允许保存有null的数据;
  • HashTable都属于同步方法(线程安全),HashTable不允许保存null,否则会出现NullPointerException异常;

想学习更多的Java的课程吗?从小白到大神,从入门到精通,更多精彩不容错过!免费为您提供更多的学习资源。
本内容视频来源于阿里云大学

更多Java面向对象编程文章查看此处

相关文章
|
21小时前
|
Java ice
【Java开发指南 | 第二十九篇】Java接口
【Java开发指南 | 第二十九篇】Java接口
6 0
|
22小时前
|
Java ice
【Java开发指南 | 第九篇】访问实例变量和方法、继承、接口
【Java开发指南 | 第九篇】访问实例变量和方法、继承、接口
10 4
|
2天前
|
Java 开发者
Java一分钟之-Lambda表达式与函数式接口
【5月更文挑战第12天】Java 8引入的Lambda表达式简化了函数式编程,与函数式接口结合,实现了代码高效编写。本文介绍了Lambda的基本语法,如参数列表、箭头符号和函数体,并展示了如何使用Lambda实现`Runnable`接口。函数式接口仅有一个抽象方法,可与Lambda搭配使用。`@FunctionalInterface`注解用于确保接口具有单一抽象方法。文章还讨论了常见的问题和易错点,如非函数式接口、类型冲突以及Lambda表达式的局部变量可见性,并提供了避免这些问题的策略。通过理解Lambda和函数式接口,开发者能提高代码可读性和效率。
32 4
|
2天前
|
安全 Java 调度
Java一分钟:多线程编程初步:Thread类与Runnable接口
【5月更文挑战第11天】本文介绍了Java中创建线程的两种方式:继承Thread类和实现Runnable接口,并讨论了多线程编程中的常见问题,如资源浪费、线程安全、死锁和优先级问题,提出了解决策略。示例展示了线程通信的生产者-消费者模型,强调理解和掌握线程操作对编写高效并发程序的重要性。
42 3
|
3天前
|
Java API
Java 接口
5月更文挑战第6天
|
3天前
|
存储 安全 Java
Java一分钟之-Map接口与HashMap详解
【5月更文挑战第10天】Java集合框架中的`Map`接口用于存储唯一键值对,而`HashMap`是其快速实现,基于哈希表支持高效查找、添加和删除。本文介绍了`Map`的核心方法,如`put`、`get`和`remove`,以及`HashMap`的特性:快速访问、无序和非线程安全。讨论了键的唯一性、`equals()`和`hashCode()`的正确实现以及线程安全问题。通过示例展示了基本操作和自定义键的使用,强调理解这些概念对编写健壮代码的重要性。
6 0
|
3天前
|
存储 安全 Java
Java一分钟之-集合框架进阶:Set接口与HashSet
【5月更文挑战第10天】本文介绍了Java集合框架中的`Set`接口和`HashSet`类。`Set`接口继承自`Collection`,特征是不允许重复元素,顺序不确定。`HashSet`是`Set`的实现,基于哈希表,提供快速添加、删除和查找操作,但无序且非线程安全。文章讨论了`HashSet`的特性、常见问题(如元素比较规则、非唯一性和线程安全性)以及如何避免这些问题,并提供了代码示例展示基本操作和自定义对象的使用。理解这些概念和注意事项能提升代码效率和可维护性。
9 0
|
3天前
|
存储 安全 算法
Java一分钟之-Java集合框架入门:List接口与ArrayList
【5月更文挑战第10天】本文介绍了Java集合框架中的`List`接口和`ArrayList`实现类。`List`是有序集合,支持元素重复并能按索引访问。核心方法包括添加、删除、获取和设置元素。`ArrayList`基于动态数组,提供高效随机访问和自动扩容,但非线程安全。文章讨论了三个常见问题:索引越界、遍历时修改集合和并发修改,并给出避免策略。通过示例代码展示了基本操作和安全遍历删除。理解并正确使用`List`和`ArrayList`能提升程序效率和稳定性。
7 0
|
3天前
|
Java
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
|
11月前
|
存储 Java 编译器
Java语言------图书馆管理系统(入门简略版)
Java语言------图书馆管理系统(入门简略版)
94 0
Java语言------图书馆管理系统(入门简略版)