我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java中详细解析Map接口

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java中详细解析Map接口

Map详解:

先看图,便于宏观了解Map的地位。

Map接口中键和值一一映射. 可以通过键来获取值。

  • 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
  • 当访问的值不存在的时候,方法就会抛出一个NoSuchElementException异常.
  • 当对象的类型和Map里元素类型不兼容的时候,就会抛出一个 ClassCastException异常。
  • 当在不允许使用Null对象的Map中使用Null对象,会抛出一个NullPointerException 异常。
  • 当尝试修改一个只读的Map时,会抛出一个UnsupportedOperationException异常。

Map基本操作:

Map 初始化

Map<String, String> map = new HashMap<String, String>();

插入元素

map.put("key1", "value1");

获取元素

map.get("key1")

移除元素

map.remove("key1");

清空map

map.clear();

hashMap原理:

hashMap是由数组和链表这两个结构来存储数据。

数组:存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);寻址容易,插入和删除困难;

链表:存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N);寻址困难,插入和删除容易。

hashMap则结合了两者的优点,既满足了寻址,又满足了操作,为什么呢?关键在于它的存储结构。

它底层是一个数组,数组元素就是一个链表形式,见下图:

Entry: 存储键值对。

Map类在设计时提供了一个静态修饰接口Entry。Entry将键值对的对应关系封装成了键值对对象,这样我们在遍历Map集合时,就可以从每一个键值对对象中获取相应的键与值。之所以被修饰成静态是为了可以用类名直接调用。

每次初始化HashMap都会构造一个table数组,而table数组的元素为Entry节点,它里面包含了键key,值value,下一个节点next,以及hash值。


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

查看hashMap的API发现,它有4个构造函数:

1、构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。

2、指定初始容量和默认加载因子 (0.75) 的空 HashMap。

3、指定初始容量和默认加载因子的空HashMap。

4、构造一个映射关系与指定Map相同的新HashMap。

注意:HashMap使用的是懒加载,构造完HashMap对象后,只要不进行put方法插入元素之前,HashMap并不会去初始化或者扩容table。

Put方法:

首先判断是否是空数组(table == EMPTY_TABLE),如果是,开始初始化HashMap的table数据结构,然后执行扩容函数,如果未指定容量,默认是大小为16的表,然后根据加载因子计算临界值。什么是加载因子呢?hashMap的大小是一定的,如果不够存储了肯定要扩容,那么扩容的依据是什么呢,什么时候确定要扩容了呢?这个时候就需要引入加载因子这个概念,我们假使依旧使用默认大小16,加载因子0.75,那么当hashMap的size大于12(16*0.75=12)的时候,那么就会进行扩容。

回来说put方法,如果key是null,调用putForNullKey方法,保存null与key,这是HashMap允许为null的原因。然后计算hash值和用indexFor计算数据存在的位置,然后从i出开始迭代e,找到 key 保存的位置。

上面说到如果数组扩容,那么每次要怎么扩容呢?

当size大于等于某一个阈值thresholdde时候且该table并不是一个空table,因为size 已经大于等于阈值了,说明Entry数量较多,哈希冲突严重,那么若该Entry对应的桶不是一个空桶,这个Entry的加入必然会把原来的链表拉得更长,因此需要扩容;若对应的桶是一个空桶,那么此时没有必要扩容。如果扩容,table会扩容为原来的两倍,直到达到数组的最大长度1<<30(2的30次方),如果size大于这个值,那么就直接修改为Integer.MAX_VALUE。扩容后的元素hash值对应的新的桶位置,然后在指定的桶位置上,创建一个新的Entry。

这里需要说明的是,hashmap是可以存放key和value均为null的,存放在table[0]的位置,此时使用put方法在添加元素的时候,如果在table[0]中已经存入key为null的元素则给null赋上新的value值并返回后面的值,否则则初始化null的元素,存入put里面存放的值。


public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        hashMap.put(null, null);
        System.out.println(hashMap.get(null));
        Integer a = (Integer) hashMap.put(null, 1);
        System.out.println(a);
        System.out.println(hashMap.get(null));
}

输出为:
null
null
1

Get方法:

Get比较好理解,判断key是不是null,如果是,返回getForNullKey的函数返回值,如果不是,则在table中去找。

Remove方法:

判断,如果hashMap的size是0,返回null;找到需要移除的元素的前一个节点,然后把前驱节点的next指向删除节点的next节点,此时当前节点没有任何引用指向,它在程序结束之后就会被gc回收。


final Entry<K,V> removeEntryForKey(Object key) {
    		if (size == 0) {
    		    return null;
    		}
   		 int hash = (key == null) ? 0 : hash(key);
   		 int i = indexFor(hash, table.length);
    		Entry<K,V> prev = table[i];
    		Entry<K,V> e = prev;
		 while (e != null) {
        		Entry<K,V> next = e.next;
       		 Object k;
       		 if (e.hash == hash &&
        		    ((k = e.key) == key || (key != null && key.equals(k)))) {
        		    modCount++;
         		   size--;
         		   if (prev == e)
          		      table[i] = next;
         		   else
          		      prev.next = next;
          		  e.recordRemoval(this);
         		   return e;
        		}
        		prev = e;
        		e = next;
   		 }
		 return e;
	}

Map的遍历:

map这里可以用增强for和迭代器两种方式遍历:


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapDemo {
    public static void main(String[] args) {
        HashMap<String, String> sets = new HashMap<>();
        sets.put("username", "value1");
        sets.put("password", "value2");
        sets.put("key3", "value3");
        sets.put("key4", "value4");
        sets.put(null,null);
        // 增强for循环 =========== keySet ===================
        for (String s : sets.keySet()) {
            System.out.println(s + ".." + sets.get(s));
        }
        //================== entrySet ======================
        for (Map.Entry<String, String> m : sets.entrySet()) {
            System.out.println(m.getKey() + ".." + m.getValue());
        }
        // 迭代器 ================ keySet ===================
        Iterator it = sets.keySet().iterator();
        while (it.hasNext()) {
            String key = (String) it.next();
            System.out.println(key + ".." + sets.get(key));
        }
        //================== entrySet ======================
        Iterator<Map.Entry<String, String>> iterator = sets.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> m = iterator.next();
            System.out.println(m.getKey() + ".." + m.getValue());
        }
    }
}

TreeMap

这里简要介绍下:TreeMap 是一个有序的key-value集合,继承于AbstractMap,它是通过红黑树实现的。TreeMap 实现了NavigableMap接口,实现了Cloneable接口,实现了java.io.Serializable接口。

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它有五个特点如下:

性质1:节点是红色或黑色。

性质2:根节点是黑色。

性质3:每个叶节点(NIL节点,空节点)是黑色的。

性质4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)。

性质5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

详细了解请点击。

LinkedHashMap:

HashMap是无序的,只要不涉及线程安全问题,Map基本都可以使用HashMap。如果我们期待一个有序的Map,这个时候,LinkedHashMap就派上用场了,它虽然增加了时间和空间上的开销,但是通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序,该迭代顺序可以是插入顺序或者是访问顺序。那么是如何维护的呢,首先参考HashMap的存储结构,将其中的Entry元素增加一个pre指针和一个next指针,这样,根据插入元素的顺序将各个元素依次连接起来,这样LinkedHashMap就保证了元素的顺序。

继承自HashMap,实现了Map接口,LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时(即按访问顺序排序),先将当前节点从链表中移除,然后再将当前节点插入到链表尾部。

实现LRU缓存:

LinkedHashMap和HashMap+LinkedList的操作都是类似的,LRU缓存是我最近看到一个很巧妙的东西,所以推荐大家看一下这篇文章。

对比下Hashmap、Hashtable和ConcurrentHashmap:

第一、Hashmap是线程不安全的,Hashtable和ConcurrentHashMap是线程安全的,在Hashtable中使用了关键字synchronized修饰,加上了同步锁;ConcurrentHashMap在JDK1.7中采用了锁分离的技术,每一个Segment都独立上锁,保证了并发的安全性;每一个Segment元素存储的是HashEntry数组+ 链表,Segment的大小是一开始就确定的,后期不能再进行扩容,但是单个Segment里面的数组是可以扩容的。

但是在JDK1.8上则摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,如下图所示,并发控制使用Synchronized和CAS来操作,每一个Node节点都是用volatile修饰的,整个看起来就像是优化过且线程安全的HashMap。

第二、Hashmap是可以存放key和value均为null的,存放在table[0]的位置,此时使用put方法在添加元素的时候,如果在table[0]中已经存入key为null的元素则给null赋上新的value值并返回后面的值,否则则初始化null的元素,存入put里面存放的值。Hashtable和ConcurrentHashMap是不可以存放null的key或者value的,原因和并发状态下的操作有关,当在并发状态下执行无法分辨是key没找到的null还是有key值为null,这在多线程里面是模糊不清的,所以不允许put、get为null的元素,如果强行操作就会报空指针异常。

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注编程网的更多内容!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java中详细解析Map接口

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java集合之Map接口与实现类详解

这篇文章主要为大家详细介绍了Java集合中的Map接口与实现类,文中的示例代码讲解详细,对我们学习Java有一定的帮助,感兴趣的可以了解一下
2022-12-08

Java Map接口概述和常用方法详解

现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map接口。本文就来聊聊Map接口概述和常用方法
2022-11-13

Java中Future接口详解

这篇文章主要介绍了Java中Future接口详解,本文通过案例给大家详细讲解了Java中Future接口,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2023-05-16

vector,map,list,queue的区别详细解析

如果我们需要随机访问一个容器则vector要比list好得多。如果我们已知要存储元素的个数则vector 又是一个比list好的选择。如果我们需要的不只是在容器两端插入和删除元素则list显然要比vector好
2022-11-15

vector与map的erase()函数详细解析

vector和map都不能将it++写在for循环中,而在循环体内erase(it)
2022-11-15

C++BoostMPI接口详细讲解

Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
2022-11-21

java中Serializable接口作用详解

本文为大家解析java中Serializable接口的作用,具体内容如下 1.(serializable)主要支持对象的回复,所以可以用来保存当前的程序系统状态,远程方法调用RMI(远程机器必须含有必要的.class文件,否则将掷出clas
2023-05-31

Java中collection和map接口有什么区别

Java中的Collection接口和Map接口是两个不同的接口,它们有以下区别:Collection接口是最基本的集合接口,它代表一组对象的集合。它的实现类包括List、Set和Queue等。它主要用于存储和操作一组对象。Map接口也是一
2023-10-26

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录