HashMap之KeySet分析
本篇涵盖
1、HashMap并不是用keySet来存储key的原因及证明
2、keySet方法返回后的remove、add操作原理
一、方法作用
概括一下
1、keySet方法返回map中包含的键的集合视图
2、集合由map支持,改变集合会影响map,反之亦然
3、集合支持删除操作,不支持添加
二、原理分析
1、HashMap源码分析
keySet方法查看keySet是否为null
不为null则直接返回,若为null则创建后返回
接下来看构造函数中做了什么
/**
* {@inheritDoc}
*
* @implSpec
* This implementation returns a set that subclasses {@link AbstractSet}.
* The subclass's iterator method returns a "wrapper object" over this
* map's <tt>entrySet()</tt> iterator. The <tt>size</tt> method
* delegates to this map's <tt>size</tt> method and the
* <tt>contains</tt> method delegates to this map's
* <tt>containsKey</tt> method.
*
* <p>The set is created the first time this method is called,
* and returned in response to all subsequent calls. No synchronization
* is performed, so there is a slight chance that multiple calls to this
* method will not all return the same set.
*/
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() {
return i.hasNext();
} public K next() {
return i.next().getKey();
} public void remove() {
i.remove();
}
};
} public int size() {
return AbstractMap.this.size();
} public boolean isEmpty() {
return AbstractMap.this.isEmpty();
} public void clear() {
AbstractMap.this.clear();
} public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
keySet = ks;
}
return ks;
}
keySet构造函数
代码注释中提到,创建的对象是AbstractSet的子类
并且说明了keySet集合是在第一次调用此方法时创建的
再来看KeySet这个类
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
KeySet
里面包含了clear、remove、forEach等方法
需要注意,这里不存在任何能存放键的数据结构
那keySet集合是怎样拿到所有键的呢?不要着急,我们进入父类查看
2、AbstractSet分析(removeAll实现)
这是一个抽象类,还是没有任何存储结构
不过我们找到了removeAll方法
可以看到需要传入一个collection
利用迭代器遍历,通过remove方法实现删除
按照这种思想,keySet方法会不会也是利用了迭代器来获取key?
我们继续进入父类
3、AbstractCollection分析(add抛异常原因)
还是不存在任何存储结构
因为实现了collection接口,所以有迭代方法
我们还看到了add和addAll方法
使用add时直接抛出不支持异常
addAll调用add,所以还是会报异常
到这里我们知道add抛异常的出处了,是在AbstractCollection中规定的
4、KeyIterator迭代器分析
到此我们没有发现任何的存储结构
接下来来验证keySet方法是利用了迭代器来获取key的
使用了KeyIterator迭代器
进入父类
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
} public final boolean hasNext() {
return next != null;
} final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
} public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
HashIterator
构造方法中拿到table
循环并将index置于table的第一位(第一个有元素的位置)
将next置为下一位
再来看看获取下一个节点的方法
1、将e设为next
2、将next置为下一位元素
3、返回e
使用迭代器一直迭代的话,就应该是从数组前到后
每次获取数组当前下标链表的下一个元素
直到元素为null,代表前端链表结束
数组下标增加,继续遍历下一个链表
三、总结
keySet方法并没有存储HashMap的key,而是以迭代器的形式,遍历获取HashMap中的所有key
对于HashMap的values方法,也是类似的原理
HashMap之KeySet分析的更多相关文章
- 【JAVA集合】HashMap源码分析(转载)
原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...
- HashMap源码分析和应用实例的介绍
1.HashMap介绍 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.S ...
- HashMap源码分析(史上最详细的源码分析)
HashMap简介 HashMap是开发中使用频率最高的用于映射(键值对 key value)处理的数据结构,我们经常把hashMap数据结构叫做散列链表: ObjectI entry<Key, ...
- Java中HashMap源码分析
一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtab ...
- 基础进阶(一)之HashMap实现原理分析
HashMap实现原理分析 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端. 数组 数组存储区间是连续的,占用内存严重,故空间复杂的很大.但数组的二 ...
- JDK1.8 HashMap源码分析
一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...
- 【Java】HashMap源码分析——常用方法详解
上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...
- 【Java】HashMap源码分析——基本概念
在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...
- jdk1.8 HashMap的keySet方法详解
我在看HashMap源码的时候有一个问题让我产生了兴趣,那就是HashMap的keySet方法,没有调用HashMap的有关数据的任何方法就能获取到map的所有的键,他是怎么做到的,然后我就通过模拟k ...
随机推荐
- alsa-lib及alsa-utils成功移植(转载)
准备工作 alsa-lib版本:alsa-lib-1.0.23.tar.bz2 alsa-util版本:alsa-utils-1.0.23.tar.bz2 其他版本的alsa-lib和alsa-uti ...
- Servlet(五)----Request登录案例
## 案例:用户登录 准备工作: 准备Maven 配置pom.xml <?xml version="1.0" encoding="UTF-8"?> ...
- "xaml+cs"桌面客户端跨平台初体验
"Xaml+C#"桌面客户端跨平台初体验 前言 随着 .Net 5的到来,微软在 .Net 跨平台路上又开始了一个更高的起点.回顾.Net Core近几年的成果,可谓是让.Ne ...
- matplotlib.pyplot.text
matplotlib.pyplot.text(x, y, s, fontdict=None, withdash=<deprecated parameter>, **kwargs)[sour ...
- 生日Party 玄学多维DP
题目描述 今天是hidadz小朋友的生日,她邀请了许多朋友来参加她的生日party. hidadz带着朋友们来到花园中,打算坐成一排玩游戏.为了游戏不至于无聊,就座的方案应满足如下条件:对于任意连续的 ...
- OSLab:实模式与保护模式
日期:2019/5/18 12:00 内容:操作系统实验作业:x86:IA-32:实模式与保护模式. PS:如果我们上的是同一门课,有借鉴代码的铁汁请留言告知嗷.只是作业笔记,不推荐学习. 一.实模式 ...
- CentOS6.10下安装mysql-5.7.24
卸载原有mysql 因为mysql数据库在Linux上实在是太流行了 所以目前下载的主流Linux系统版本基本上都集成了mysql数据库在里面 我们可以通过如下命令来查看我们的操作系统上是否已经安装了 ...
- C 怪兽游戏
时间限制 : - MS 空间限制 : - KB 评测说明 : 1s,256m 问题描述 何老板在玩一款怪兽游戏.游戏虽然简单,何老板仍旧乐此不疲.游戏一开始有N只怪兽,编号1到N.其中第i只怪兽 ...
- 从零开始,做一个简单的Vuetify项目,图标安装成功
安装Vuefity的时候,碰到一些坑,经过一番折腾,终于成功,记录正确的姿势如下: 创建一个Vue项目: vue init webpack-simple vular-admin 进入项目目录: cd ...
- iOS开发 - 循环滚动的ScrollView
源码在这里,请多多指教. 由于开发需要,要用到循环自动滚动的scrollView,借鉴了前人的思路,重新设计了一个AutoSlideScrollView.先自吹自擂一翻吧: 借鉴UITableView ...