Java开发笔记(六十六)映射:HashMap和TreeMap
前面介绍了两种集合的用法,它们的共性为每个元素都是唯一的,区别在于一个无序一个有序。虽说往集合里面保存数据还算容易,但要从集合中取出数据就没那么方便了,因为集合居然不提供get方法,没有get方法怎么从一堆数据之中挑出你想要的东西呢?难道只能从头遍历集合的所有元素,再逐个加以辨别吗?显然这个缺陷是集合的硬伤,好比去银行开账户,存钱的时候大家都开开心心,可是等到取钱的时候,却发现柜员拿出一叠存单一张一张找过去,等找到你的存单之时,黄花菜儿都凉了。因此,实际开发中一般很少直接使用集合,而是使用集合的升级版本——映射。
映射指的是两个实体之间存在一对一的关系,例如一个身份证号码对应某个公民,一个书名对应某本书籍等等。有了映射关系之后,从一堆数据中寻找目标对象就好办了,只要给定目标对应的号码或者名称,根据映射关系能够立刻找到号码或名称代表的对象。这样下次去银行取钱,就不必等柜员兀自地翻存单,只要在电脑上输入身份证号,即可自动找到当初的存款记录。
Java编程通过“键值对”的概念来表达映射关系,它包含“键名”和“键值”两个实体,且键名与键值是一一对应的,相同的键名指向的键值也必然是相同的。如此一来,映射里面的每个元素都是一组键值对,即“Key→Value”,在代码中采取形如“Map<Key, Value>”的格式来表达,其中Key表示键名的数据类型,Value表示键值的数据类型。往映射里面保存数据的时候,需要填写完整的键值对信息;而从映射中取出数据,只需提供键名即可获得相应的键值信息。
由于Map属于接口,因此开发过程通常调用它的两个实现类,包括哈希图HashMap和红黑树TreeMap。映射与集合密切相关,它们的存储原理也类似,比如HashMap和HashSet一样采取哈希表结构,而TreeMap和TreeSet一样采取二叉树结构;不同的是,映射元素的唯一性和有序性是由各元素的键名决定的。因为HashMap和TreeMap仅仅是内部存储结构存在差异,外部的代码调用仍然保持一致,所以接下来就以HashMap为例阐述映射的具体用法。
与HashSet相比,HashMap在编码上主要有三处改动,分别说明如下。
一、创建映射实例必须同时指定键名和键值的数据类型,即HashMap后面的那对尖括号内部要有两个类型名称。下面是创建一个手机映射的代码例子:
// 创建一个哈希映射,该映射的键名为String类型,键值为MobilePhone类型
HashMap<String, MobilePhone> map = new HashMap<String, MobilePhone>();
二、往映射中添加新的键值对,调用的是put方法而非add方法,并且put方法的第一个参数为新元素的键名,第二个参数为新元素的键值。如果映射内部不存在该键名,则映射会直接增加一组键值对;如果映射已经存在该键名,则映射会自动将新的键值覆盖旧的键值。给手机映射添加若干组手机信息的代码示例如下:
map.put("米8", new MobilePhone("小米", 3000));
map.put("Mate20", new MobilePhone("华为", 6000));
map.put("荣耀10", new MobilePhone("荣耀", 2000));
map.put("红米6", new MobilePhone("红米", 1000));
map.put("OPPO R17", new MobilePhone("OPPO", 2800));
三、遍历映射内部的所有元素,也有好几种方式,依次说明如下。
1、通过迭代器遍历。首先调用映射实例的entrySet获得该映射的集合入口,再调用入口对象的iterator方法获得映射的迭代器,然后使用迭代器遍历整个映射。在遍历过程中,每次调用next方法得到的是下个位置的键值对记录,此时还需调用该记录的getKey方法才能获取键值对中的键名,调用getValue方法获取键值对中的键值。详细的迭代器遍历代码如下所示:
// 第一种遍历方式:显式指针,即使用迭代器
Set<Map.Entry<String, MobilePhone>> entry_set = map.entrySet();
Iterator<Map.Entry<String, MobilePhone>> iterator = entry_set.iterator();
while (iterator.hasNext()) {
// 注意这里要先把入口取出来,这样才能分别getKey和getValue
Map.Entry<String, MobilePhone> iterator_item = iterator.next();
// 获取该键值对的键名
String key = iterator_item.getKey();
// 获取该键值对的键值
MobilePhone value = iterator_item.getValue();
System.out.println(String.format("iterator_item key=%s, value=%s %d",
key, value.getBrand(), value.getPrice()));
}
2、通过for循环遍历。第一种遍历方式可以看到明确的迭代器对象,故而又被称作显式指针。其实迭代器仅仅起到了指示的作用,它完全可以被简化的for循环所取代。尽管在for循环中看不到迭代器对象,但编译器知道这里有个隐含着的迭代器,因此for循环遍历也被称作隐式指针。下面是采取for循环遍历手机映射的代码例子:
// 第二种遍历方式:隐式指针,即使用for循环
for (Map.Entry<String, MobilePhone> for_item : map.entrySet()) {
// 获取该键值对的键名
String key = for_item.getKey();
// 获取该键值对的键值
MobilePhone value = for_item.getValue();
System.out.println(String.format("for_item key=%s, value=%s %d",
key, value.getBrand(), value.getPrice()));
}
3、通过键名集合遍历。有别于上述两种依次遍历键值对的方式,第三种方式先调用映射的keySet方法获得只包含键名的集合,再通过遍历键名集合来获取每个键名对应的键值。该方式的映射遍历代码示例如下:
// 第三种遍历方式:先获得键名的集合,再通过键名集合遍历整个映射
// 注意:HashMap的keySet方法返回的是无序集合
Set<String> key_set = map.keySet();
for (String key : key_set) {
// 通过键名获取该键值对的键值
MobilePhone value = map.get(key);
System.out.println(String.format("set_item key=%s, value=%s %d",
key, value.getBrand(), value.getPrice()));
}
4、通过forEach方法遍历。显然前面的几种方式都很啰嗦,自从Java8引入了Lambda表达式,遍历映射的所有元素也变得异常简洁了,单单下面一行代码就全部搞定:
// 第四种遍历方式:使用forEach方法夹带Lambda表达式进行遍历
map.forEach((key, value) ->
System.out.println(String.format("each_item key=%s, value=%s %d",
key, value.getBrand(), value.getPrice())) );
最后简要描述一下TreeMap背后的红黑树概念,这是各家公司面试Java开发人员的常见知识点。红黑树是一种自平衡二叉查找树,所谓平衡指的是像天平那样左右两边的重量相等,从而使天平保持不偏不倚的水平状态。当然对于二叉树来说,绝对的平衡是难以达到的,只能做到相对平衡,即左右两棵子树的高度差不大于1,同时左右两棵子树本身也是平衡二叉树。鉴于平衡二叉树的平衡特性,从根节点出发到达每个叶子节点的距离比较平均,使得整棵树的查找性能相对优越。高度平衡的二叉树在进行查找操作时表现近乎完美,但是给它添加新节点或者删除原节点的时候,二叉树为了在节点增删之后仍然保持高度平衡,可能不得不多次左旋右旋,一旦这棵树遇到频繁的节点添加和删除操作,它的整体性能将会急剧下降,真可谓鱼与熊掌不可兼得。
为了兼顾平衡二叉树的查找性能和节点增删后的旋转性能,另一种折中的自平衡二叉树(即红黑树)应运而生,红黑树并非高度平衡的,而是一种相对平衡,它的每次插入操作最多只要两次旋转,删除操作最多只要三次旋转。平衡二叉树要求左右子树的高度差不大于1,而红黑树只要求左右子树的高度差不大于根节点到叶子节点的最短距离,也就是说,根节点到叶子节点的最长距离不大于最短距离的两倍。红黑树不处于最理想的平衡状态,而是处于大致平衡的状态,那么对它的叶子节点进行增删之时,这样不管左旋还是右旋,只需少数几次旋转就能让左右字书的高度差保持在合理的范围内了。
更多Java技术文章参见《Java开发笔记(序)章节目录》
Java开发笔记(六十六)映射:HashMap和TreeMap的更多相关文章
- Java开发笔记(十六)非此即彼的条件分支
前面花了大量篇幅介绍布尔类型及相应的关系运算和逻辑运算,那可不仅仅是为了求真值或假值,更是为了通过布尔值控制流程的走向.在现实生活中,常常需要在岔路口抉择走去何方,往南还是往北,向东还是向西?在Jav ...
- Java开发笔记(九十六)线程的基本用法
每启动一个程序,操作系统的内存中通常会驻留该程序的一个进程,进程包含了程序的完整代码逻辑.一旦程序退出,进程也就随之结束:反之,一旦强行结束进程,程序也会跟着退出.普通的程序代码是从上往下执行的,遇到 ...
- Java开发学习(三十六)----SpringBoot三种配置文件解析
一. 配置文件格式 我们现在启动服务器默认的端口号是 8080,访问路径可以书写为 http://localhost:8080/books/1 在线上环境我们还是希望将端口号改为 80,这样在访问的时 ...
- Java开发学习(二十六)----SpringMVC返回响应结果
SpringMVC接收到请求和数据后,进行了一些处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户. 比如:根据用户ID查 ...
- Java学习笔记(十六)——Java RMI
[前面的话] 最近过的好舒服,每天过的感觉很充实,一些生活和工作的技巧注意了就会发现,其实生活也是可以过的如此的有滋有味,满足现在的状况,并且感觉很幸福. 学习java RMI的原因是最近在使用dub ...
- 【Java学习笔记之十六】浅谈Java中的继承与多态
1. 什么是继承,继承的特点? 子类继承父类的特征和行为,使得子类具有父类的各种属性和方法.或子类从父类继承方法,使得子类具有父类相同的行为. 特点:在继承关系中,父类更通用.子类更具体.父类具有更 ...
- Java学习笔记二十六:Java多态中的引用类型转换
Java多态中的引用类型转换 引用类型转换: 1.向上类型转换(隐式/自动类型转换),是小类型到大类型的转换: 2.向下类型转换(强制类型转换),是大类型到小类型的转换: 3.instanceof运算 ...
- Java基础笔记(十六)——继承
继承 提取出一些共性特征,作为父类,子类就可以继承父类的这些开放成员,子类再添加自己独有的属性和方法.如果再有类具有这些共同特征,也可继承这个父类. 特点:1.利于代码复用 2.缩短开发周期 ...
- .net开发笔记(十六) 对前部分文章的一些补充和总结
补充有两个: 一个是系列(五)中讲到的事件编程(网址链接),该文提及到了事件编程的几种方式以及容易引起的一些异常,本文补充“多线程事件编程”这一块. 第二个是前三篇博客中提及到的“泵”结构在编程中的应 ...
- 安卓开发笔记(十六):'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request
当出现了'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request的错误的时候,实际上是我们在写代码的时候少打了 ...
随机推荐
- js-day03-事件响应和练习题
DOM事件编程 事件驱动编程:所谓事件驱动,简单地说就是你点什么按钮(即产生什么事件),电脑执行什么操作(即调用什么函数).当然事件不仅限于用户的操作. 当对象处于某种状态时,可以发出一个消息通知,然 ...
- 数据调试~~TCP转串口、串口转TCP调试
Android socket开发了一个socket客户端,当输入服务器ip以及端口,建立连接之后,Android可以发送数据到电脑接收服务器端. 如果电脑端没有socket服务器怎么办?方法如下: 1 ...
- SQA计划和验收测试规程设计
一.SQA(软件质量保证)的定义 软件质量保证(SQA-Software Quality Assurance)是建立一套有计划,有系统的方法,来向管理层保证拟定出的标准.步骤.实践和方法能够正确地被所 ...
- 写书好累 <HTTP抓包实战>终于出版
我的新书<HTTP抓包实战>终于开始在京东销售了.内容是关于HTTP包,Fiddler抓包,JMeter发包,适合任何IT工程师阅读.我将自己十年所学的知识,融会贯通总结为一本书.阅读后肯 ...
- emWin录音机,含uCOS-III和FreeRTOS两个版本
第12期:录音机配套例子:V6-921_STemWin提高篇实验_录音机(uCOS-III)V6-922_STemWin提高篇实验_录音机(FreeRTOS) 例程下载地址: http://forum ...
- Java实现单例模式的9种方法
一. 什么是单例模式 因程序需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计. 二. 单例模式的特点 1. 单例模式只能有一个实例. 2. 单例类必须创建 ...
- 1 小时 SQL 极速入门(二)
上篇我们说了 SQL 的基本语法,掌握了这些基本语法后,我们可以对单表进行查询及计算分析.但是一个大的系统,往往会有数十上百张表,而业务关系又错综复杂.我们要查的数据往往在好几张表中,而要从多张表中来 ...
- [Swift]LeetCode997. 找到小镇的法官 | Find the Town Judge
In a town, there are N people labelled from 1 to N. There is a rumor that one of these people is se ...
- Android-线程池下载多个图片并保存,如果本地有该图,则不下载,直接展示到view
做了个工具方法,用来下载图片,如果本地有这个图,则不下载,直接展示到view setHP()方法可以多次使用,因为使用了线程池,所以是个异步操作,如果使用的多,建议根据需要增加线程池的线程数量 看代码 ...
- 【转】关于 python ImportError: No module named 的问题
今天在 centos 下安装 python setup.py install 时报错:ImportError: No module named sysconfig, 当时急着用,就顺手直接源码编译了一 ...