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的错误的时候,实际上是我们在写代码的时候少打了 ...
随机推荐
- mysql根据查询结果批量更新多条数据(插入或更新)
mysql根据查询结果批量更新多条数据(插入或更新) 1.1 前言 mysql根据查询结果执行批量更新或插入时经常会遇到1093的错误问题.基本上批量插入或新增都会涉及到子查询,mysql是建议不要对 ...
- Ubuntu VIM下实现python自动缩进
1.打开vimrc文件 sudo vi /usr/share/vim/vimrc 2.添加 set filetype=python au BufNewFile,BufRead *.py,*.pyw s ...
- SSIS - 5.优先约束
一.优先约束和执行逻辑 任务和容器是SSIS中的可执行文件,一个优先约束连接着两个可执行文件:优先的可执行文件和约束的可执行文件,如下图. 它的执行逻辑如下图: 1)先执行优先可执行文件 2)判断 ...
- 合适么?现在学ASP.NET Core入门编程……
现在都快找不到ASP.NET的培训课程了. 知道我要开课做培训,有同学劝我:“憋讲那什么.NET,讲Java,现在这个火!”我说我Java不熟,“唉呀!C#转Java,分分钟的事!以飞哥你的经验,…… ...
- JDK的下载,安装,环境变量配置
JDK 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html 环境变量配置:在"系统变量" ...
- Linux atop监控
200 ? "200px" : this.width)!important;} --> 介绍 atop是一个功能非常强大的linux服务器监控工具,它的数据采集主要包括:CP ...
- 安卓开发学习笔记(七):仿写腾讯QQ登录注册界面
这段代码的关键主要是在我们的相对布局以及线性布局上面,我们首先在总体布局里设置为线性布局,然后再在里面设置为相对布局,这是一个十分常见的XML布局模式. 废话不多说,直接上代码:一.activity. ...
- 百度地图API 自定义标注图标
通过Icon类可实现自定义标注的图标,下面示例通过参数MarkerOptions的icon属性进行设置, 也可以使用marker.setIcon()方法. <script type=" ...
- Go语言数组和切片的原理
目录 数组 创建 访问和赋值 切片 结构 初始化 访问 追加 拷贝 总结 数组和切片是 Go 语言中常见的数据结构,很多刚刚使用 Go 的开发者往往会混淆这两个概念,数组作为最常见的集合在编程语言中是 ...
- [Swift]LeetCode327. 区间和的个数 | Count of Range Sum
Given an integer array nums, return the number of range sums that lie in [lower, upper] inclusive.Ra ...