百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

从浅入深,讲透java的map与set(java map entryset)

toyiye 2024-07-26 22:02 8 浏览 0 评论

本文主要介绍:

  1. map(字典)与set(集合)的概念
  2. 它们是如何被实现的,也就是实现原理
  3. 在java中他们在具体的实现过程
  4. 如何进行遍历,删除,以及具体的实现过程
  5. 介绍下java中常用的几个map与set的类,以及它们的异同

读完这篇文章,你肯定会对map与set了如指掌。下面我们先从介绍概念开始吧。

1. 简单介绍

1.1 什么是map

俗称的字典。就是将一个唯一标识的对象与另外一个对象关联起来的一个结构。

  1. 这个唯一标识对象称为key,被关联的对象称为value。
  2. key到value的映射关系为n对1.比方说身份证关联到一个具体的人,身份证就是key,而人就是value。一个身份证可以唯一确定到一个人,而一个人可不可以有多个身份证号?完全可以。

map有什么作用

当你查字典的时候,你不是一页一页去翻找到那个字位置,而是根据拼音或者部首跳到指定页面。而map就类似这个,在一个列表中,当你给出要查询的对象时,他就能根据你这个对象计算出这个对象可能放在的位置。也就是说,用key值 a 映射拼音a开头第一个字的页码即value,用key值b映射拼音b开头的第一个字的页码,以此类推。

1.2 什么是set

即为集合。存储对象的集合,重复的数据只存一份,区别于上一期讲的列表(list)。例如如果有 a,b,c,e,a,b,c,d。这样的列表,转换为set就变成了a,b,c,d,e。

2. 实现原理

前面在list的篇章中提到,计算机底层只有数组与链表。因此map也是基于这两个之上来实现的。


这里我们先以HashMap为例,介绍其是如何使用数组来完成实现映射的过程的。

2.1 添加元素过程

添加元素的过程有以下步骤:

  1. 首先先初始化一个数组我们记为table,这个数组的长度记为size
  2. 先将key通过哈希函数做hash处理,将其变成一个数值记为keyHash
  3. 将数值keyHash对size取模,得到对应的下标值index
    • 为了优化取模这个计算,将table的长度设计成了2的n次幂
    • 因为计算机是二进制,因此在取模时,可以直接做keyHash与2^n-1的位与运算,即可得到index
    • 因为与2^n-1做位与 其实就是取2进制的后(n-1)值,即为其取模后的值,这个运算远比除法高效快速。
  1. 然后用一个entry对象来保存key,value值
  2. 将这个entry放入table对应index下标中

那么问题来了,如果有两个key对应的index值一样,要怎么处理?那么只要在entry中加入一个指针,指向下一个entry的地址即可。

举个例子:

我们有这样的键值对:1-》4,2-》7,3-》2,5-》11,6-》10。我们用一个简单的哈希函数index=key%4。此时我们新建一个长度为4的table,那么就会有:

此时可以看到table中的每个对象都是一个链表的结构。当冲突的时候就通过拉链表的方式来解决。因此上面步骤的第五步将变为:

  1. 遍历index下标中的链表,如果链表中不包含此元素,就将它添加到链表尾部

2.2 根据key获取元素的过程

map的内部根据key获取到存入的数据过程如下:

  1. 先将key做hash处理,将其变成一个数值记为keyHash
  2. 将数值keyHash对size取模,得到对应的下标值index
  3. 然后去table指定index中,遍历链表元素,判断是否当前的key,如果是则获取返回,如果到链尾依然没有,则返回null即可

2.3 hashMap元素不断增加时的优化

当hashMap存储的元素不断增加时,其中元素冲突情况就越多。如果对应index的链表太长,那么也就大大降低了获取的性能。

为了优化这种情况则有两种做法:

  1. 为table进行扩容,新建一个更大的list称为newEntryList,然后将所有元素添加进这个newEntryList中,然后放入map替代原来的table。
    • 每次扩容都会保持为2的n次幂的策略
    • 当元素大于table的size的一定比例时(默认是0.75*size的个数),就会触发扩容的操作。
  1. 将链表结构更改为红黑树,将原本O(n)复杂度的查找变为了O(log(n))。
    • 默认当链长大于等于8时会触发转换成红黑树的操作

扩容策略使得map的key冲突的概率降低。红黑树使得冲突的key的查找速率更快。


2.4. 遍历

2.4.1 entrySet()方法与keySet()方法

这两个方法返回的对象本身不包含数据。但返回对象都是Map的内部类对象,它覆盖了遍历,contain等一些跟访问、删除相关的方法,使得每次对返回对象的访问其实就是对Map的访问。返回对象覆盖的方法有:

  1. size() 返回Map的size
  2. clear() 清空Map的数据
  3. iterator 返回Map的iterator
  4. contains 调用Map的contains
  5. remove 调用map的remove
  6. foreach() 遍历父类中的table。逐个遍历table对应index的链表

2.4.2 遍历的方式

因此遍历的方式主要有:

1. 迭代器遍历entry

  1. 迭代器遍历keyset
  2. for-each 对entry循环遍历,本质上是使用iterator实现的
  3. for-each 对key循环遍历
  4. labmda foreach遍历,会先获取spliterator()方法返回Spliterator对象,然后调用forEachRemaining方法
  5. stream foreach 遍历
//1. 迭代器遍历entry
it=map.entrySet().iterator();
while(it.hasNext()){
    entry=it.next();
}
//2. 迭代器遍历keyset
it=map.keySet().iterator();
while(it.hasNext()){
    key=it.next();
}
//3. foreach entry遍历
for(entry:map.entrySet()){
}
//4. foreach key遍历
for(key:map.keySet()){
}
//5.lambda foreach遍历
map.foreach((key,value)->{});

//6. stream api单线程
map.entrySet().stream().foreach((entry)->{});

//7. stream api多线程
map.entrySet().parallelStream().foreach((entry)->{});

2.5 删除

map的remove删除步骤:

  1. 与获取元素类似,先找到对应的元素
  2. 如果找到了,则执行链表的删除过程
//可正确删除,依赖于iterator的remove方法
map.keySet().removeIf(key->{})
//使用filter删除,只有filter函数中的运行结果返回true,才会被accept进stream中
map.entrySet().stream().filter(entry->{entry.key()>0}).foreach();
//remove删除
for(Object key:arrayList){
    map.remove(key);
}

2.6 Map的总结

常见的有HashMap,LinkHashMap。与并发相关的有HashTable,ConcurrentHashMap。

  1. linkHashMap是在HashMap的基础上维护了一个元素添加顺序的链表,为了能够在遍历的时候能够按添加顺序遍历出来而已。
  2. hashTable与HashMap类似,只是在新增、删除、更改等修改操作的方法上增加了对象锁,保证线程安全性
  3. ConcurrentHashMap改进了hashTable将整个对象锁定的问题,他每次只锁定table的某个index。
    • 好处:允许不同index的元素同时进行修改,而不会出现问题。

3. set

Set是基于Map来实现的,常见的set与Map相对应的有HashSet,LinkHashSet。

  1. 每次新增的元素都作为Map的key,value中放入一个统一的空的Object对象。也就是说每次对set.add(item)其实就是做map.put(item,object)处理。
  2. 每次删除也是基于map的删除,也就是说set.remove调用的就是map.remove。

4. 总结

通过上面的内容,我们对重点部分进行总结:

  1. map通过使用hash将key映射到list的对应下标来存储数据,通过链表的方式来解决冲突的问题
  2. map的元素个数总为2的n次幂,这是为了简化计算映射到数组下标的过程
  3. 当元素超过一定数量时,就会进行扩容,如果table的某个index冲突数大于等于8,会被转换为红黑树,来提高搜索速度
  4. set通过Map来实现,将set的数据作为Map的key,将一个统一的Object空对象作为Map的value值
  5. LinkedHashMap只比HashMap多维护了一个元素添加进来的顺序链表,使得遍历的时候可以按照元素添加顺序输出
  6. HashMap的遍历是对内部的table进行从前到后的遍历,对每个table的元素进行链表的遍历方式
  7. hashTable的更改会锁定整个对象,而concurrentHashMap的更改只锁定对应index的对象

如果各位觉得不错的话,欢迎点赞,评论,关注,转发,一波四连呀。还可以关注我的公众号“空亦语”。你的鼓励就是作者持续创作的动力。

相关推荐

「编程」Java GUI 基础(java编程语言基础)

图形化学习是外功,内功外功配合才能所向披靡。一、JFrameJAVASWING导入包importjavax.swing.*导入包importjava.awt.*JFrameframe=new...

这六个Java技术当年是多么风光,而现在又有几个人用过

嗨,雷猴啊,今天我给大家分享下我的开发历程中,我知道的那些被淘汰的技术或者框架。不知道你们都知道吗?也不知道你们都有没有用过,但是它们之前都是风靡一时,让我们再来了解一次吧。偷偷告诉大家有些我甚至都没...

开发第一个Swing程序(开发第一个java程序实验报告)

packagecom.web.www;importjavax.swing.*;/**第一个Swing程序*/publicclassSwing1extendsJFrame{publicSw...

Java课程设计项目实例《远程屏幕分享监视》第2部分

Java课程设计项目实例《远程屏幕分享监视》第2部分1、服务器端ScreenMonitoringServer程序类及相关的功能方法的编程实现(1)创建出服务器端的ScreenMonitoringSer...

新手学Java编程语言怎么入门?知识点都帮你整理好了

新手学Java编程语言怎么入门?下面和千锋广州小编一起来看看吧!掌握语言基础是第一步,了解Java基础知识,Java关键字、核心概念或者基本编码技术。掌握操作符、控制执行流程、访问权限控制、复用类、多...

Java Swing组件“HelloWorld”程序演示实例

Java源代码:/*首先导入Swing需要的包*/importjavax.swing.*;importjavax.swing.UIManager;importjava.awt.*;import...

新年Java小游戏之「年兽大作战」祝您笑口常开

这个游戏加上编写文章,上班摸鱼时间加上回家的空闲时间,大概花了三天多。java写这玩应真的很痛苦,各种状态位,各种图片和逻辑判断,脑袋都快炸了。而且肯定没有前端的精致,效果一般,偶尔会有卡顿,各位就图...

10分钟掌握 JMeter接口测试的基础入门

嘿。大家好,我是4U:...

JMeter 的简单安装说明(jmeter安装配置)

最近在做一组性能测试,接触到了JMeter这个测试工具,在这里记录一下JMeter的介绍以及简单安装过程。JMeter简介...

Jmeter压测实例分享——新手儿也能一学就会!

JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。它可以用于测试静态和动态资源,例如静态文件、Java...

过年必备Java动态烟花教程如何用Canvas和Timer实现炫酷烟花动画

烟花是一种常见的庆祝活动和节日的方式,它们在夜空中绽放出各种颜色和形状,给人们带来美丽和欢乐。你是否想过用Java编程来模拟烟花的效果呢?如果你对此感兴趣,那么这篇教程就是为你准备的。在这篇教程中,你...

全程软件测试(九十五):Jmeter技能基础—读书笔记

jmeter是一款优秀的开源性能测试工具,目前最新版本3.0版本,官网文档地址:http://jmeter.apache.org/usermanual/index.html一、优点...

原创 JAVA Swing JFrame窗口的建立

importjava.awt.*;importjavax.swing.*;publicclassExample1extendsJFrame{//定义一个类继承JFrame类public...

Java Swing组件下的JComboBox组件实例

运行成功的界面:java源代码:一定要注意:执行环境(JRE)javaSE-1.8/*首先导入JButtontest所需要的包*/importjavax.swing.*;importjavax.s...

Java引包的几种方法(java 引用)

第一种方法可以在Superclass这里输入javax.swing.JFrame进行引包也可以在类体外面输入importjavax.swing.JFrame;进行引包还可以点击JFrame然后点击I...

取消回复欢迎 发表评论:

请填写验证码