一、HashMap介绍:
上面一篇介绍了hashTable,这里HashMap的作用就不多啰嗦了。HashMap 实现的功能和hashTable 差不多,具体实现和功能我们从源码进行分析。
二、源码分析:
2.1 类实现:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable{...}
这里我们可以看到继承了 AbstractMap<K,V> 还实现了 Map<K,V>,其他的参考hashTable,其实 AbstractMap<K,V> 去看源码知道已经实现了Map<K,V> 接口,为什么这里还需要去实现呢?很多人这里有有疑问,觉得很啰嗦,下面我谈一下介绍,和自己的看法.
Map 接口提供了键值对 这类型数据的通用的方法,而AbstractMap 这是对Map实现的抽象类,我们知道一般抽象类都是用来定义一个概念,一种通用的,不指向具体事物。
这里举个例子:假设我们知道动物都有 eat(food) 的行为,相当于Map. 然后我们设计一个抽象类去实现 void eat(food) {System.out.println(food);} 相当于AbstractMap。然后这个时候我们去实现各个动物比如:人,熊猫等等,都去继承这个抽象类,相当于hashMap , TreeMap 一样,这样我们就可以省略eat 这个通用的实现了。当然实际上这些通用实现可能不满足我们的需求,那么我就要从写。
事实上这里还是没明白为什么 HashMap 还要实现Map接口,因为抽象类已经实现了啊。为啥还要实现呢?其实你进入AbstractMap ,仔细观察会发现:
public abstract Set<Entry<K,V>> entrySet();
这里其实没有对 entrySet 进行实现的,相当于复制了Map的接口方法。我们从JDK 解释中明白:
1.AbstractMap 提供 Map 接口的骨干实现,以最大限度地减少实现此接口所需的工作。
2.要实现不可修改的映射,编程人员只需扩展此类并提供 entrySet 方法的实现即可。
第一句话很好理解,提供方便,第二句话强调了 实现不可修改的映射,也就是说我们继承了AbstractMap ,实现entrySet()方法,那么当前类的集合就是不可修改的了。具体点说,就是你调用 put(),putAll(),remove(),clear() 方法,数据都会存在,并且put()还会报错,具体请看源码和API。
好了,说了这么多,其实是简单解释了AbstractMap 的作用。而实现Map 接口,从功能上来说,没啥作用,因为去掉也没影响。但是从接口上来说,我们可以从JAVA 集合的关系图中接口图就能看出,Map Collection Itertor 是最顶层的东西,我们习惯面向接口编程,这样我们的hashMap 也能能直接指向Map 接口,整个结构更加的完整,这是我的想法,请补充指正!下面继续进行 代码的详细研究吧!
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR;// 0.75 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY];// 16 init(); } // 这里我们new HashMap 无论带参数 或者不带参数,主要都是设置增长因子0.75 // 当前增长的位置 // 以及Entry 的数组,这里可以参看hashTable ,下面继续看 Entry 类
static class Entry<K,V> implements Map.Entry<K,V> { // 这是定义的内部类,Key value 内部属性, final K key; V value; Entry<K,V> next; final int hash; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { // 这里仅仅 提供了对值覆盖方法 V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { // 这里对类型 key value 地址值 和 值得判断 if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { // 这里是对key 和 value 两个的地址值 进行 异或运算,得出的hashcode return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } // 这两个方法 真没看懂,有什么作用。。。 // 知道了人,麻烦帮帮忙解释下 // 当然因为这方法在put remove 等地方都会自动调用估计在扩展的里面的东西留 // 下扩充空间吧 void recordAccess(HashMap<K,V> m) { } void recordRemoval(HashMap<K,V> m) { } }
我们可以看到,hashMap 也是一个内部类的数组,内部类 有ket value 属性而已,下面看看主要方法实现
// 可以看到 这里是有返回值的 ,而且是返回的value public V put(K key, V value) { if (key == null) // 可以看出 key 是可以为null的。并且对值 也进行了操作,请看下面 return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { // 这里做了便利,对key 为null 的值 if (e.key == null) { V oldValue = e.value; // 这里进行了赋值操作,并且返回的是 旧的一个值 e.value = value; e.recordAccess(this); return oldValue; } } // 数据自增一 modCount++; // 这里指定了 key 为null 的元素存放的位置,从下面的方法 以及构造,我们看出 // 所有key 为null 的存放位置,默认是放在0的位置,也就是数组第一个 addEntry(0, null, value, 0); return null; } //addEntry(0, null, value, 0); void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); } // 当然这里还进行可 扩容的操作,当当前元素size >= 增长位置threshold 的时候 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; // 这里我很意外,这里设置了一个最大初始化的位置,是Integer 最大值的一半 // 当元素刚好有MAXIMUM_CAPACITY这么多时,就一个赋值 return,后面就无法完成扩容了 // 而我们看到扩容是 都是2*table.length,而这个数是一个单数- -,那么这里理论上是无法 // 得到执行的。也就是没用的,谁知道 这里的设计是为了什么呢? if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 这里就是新的扩容了, Entry[] newTable = new Entry[newCapacity]; // 这里就是数组的复制,并且位置是从新计算了的。感觉上是比较耗的,因为会从新计算存放 // 的位置,不是单纯的复制,关于位置的计算,下面继续。 transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }
这是 put 方法 里面的两个东西 int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); // hash值算法,为了减小重复而这样方式的 // 这里先不谈,专门找章节介绍,可以参考:http://marystone.iteye.com/blog/709945 static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } //参考:http://blog.csdn.net/heyutao007/article/details/6206153 static int indexFor(int h, int length) { return h & (length-1); } // 通过相同的hash 和 indexFors 算法,快速的取值 public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
综上可以看看出:put 方法 是通过hash算法,以及indexFor 算出值应该存放在数组的哪个位置,并不是顺序存放的,这样的好处是 在取值的 也可以通过算法 就行读取。但是存放过程中进过了大量的计算,以及数组的操作,因此不难理解存放是比较耗时的,而取值是很快的。
下面我们来看看几个常用的方法:
remove():
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } // final 提高查找速度 final Entry<K,V> removeEntryForKey(Object key) { // 通过hash 和 i, 找出table 中元素的位置 int hash = (key == null) ? 0 : hash(key.hashCode()); 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; // 通过该元素的hash 和 key 值,进行对比 确认元素 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; // 通过数组用后一个元素 覆盖当前元素,完成移除操作 // 这里size --,当时数组空间其实并没减少,只是最后一个元素你无法访问了 // 相当于 1,2,3,4,5 移除3, 就变成了1,2,4,5,5,但是size 5变成了4! // 这里估计是想操作数组又需要时间 和空间吧! if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
下面来看我们常用的几个方法:
public Set<Map.Entry<K,V>> entrySet();
public Collection<V> values();
// 获得所有value 的集合 public Collection<V> values() { Collection<V> vs = values; return (vs != null ? vs : (values = new Values())); } private final class Values extends AbstractCollection<V> { public Iterator<V> iterator() { return newValueIterator(); } // ..略 } // 获得所有Entry的集合 public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); } private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } // ... 略 } // 获得所有key的集合 public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); } private final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return newKeyIterator(); } // ... 略 }如果看过hashTable 的源码就会发现,它是通过 3个标志 判断取的是什么,调用的一个方法,而hashMap
private final class ValueIterator extends HashIterator<V> { public V next() { return nextEntry().value; } } private final class KeyIterator extends HashIterator<K> { public K next() { return nextEntry().getKey(); } } private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } }就能获得你需要的元素了,下面主要看HashIterator:
private abstract class HashIterator<E> implements Iterator<E> {
// 这里感觉和Entry 差不多,其实是的,这里内部类主要是为了维护table[Entry]
Entry<K,V> next; // next entry to return
// 这里控制多线程修改,用来判断同时操作 快速的抛出异常
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry
//
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
// 这里其实是通过循环 对next 赋值,每次都取得第一个值,
// 这写法好真简洁!
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
// 这里就能获得entry,然后通过entry 就能获得需要的值了
final Entry<K,V> nextEntry() {
// 这里就是刚才线程控制啦,虽然是线程不安全的,但是迭代器中不允许几种同时操作
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table; while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
// 移除操作,对迭代过程的控制,迭代不能删除的
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
相关推荐
在这个主题中,我们将深入探讨HashMap类,它是Java集合框架中的一个关键组件,特别是在标题“20-集合框架020-HashMap-1080P 高清-AVC20”和描述中所提到的内容。 HashMap是Java.util包中的一个类,它实现了Map接口...
集合框架的"集合框架.pdf"将涵盖ArrayList、LinkedList、HashMap、HashSet、TreeMap等基础数据结构,以及泛型、迭代器、比较器等概念。求职者应能熟练运用这些集合,并了解其性能特点。 对于服务器端技术,"Tomcat...
本文将深入探讨Java中的迭代器模式及其在集合框架中的应用。 迭代器模式是一种行为设计模式,它提供了一种方法来顺序访问聚合对象的元素,而不暴露其底层表示。在Java中,迭代器模式广泛用于遍历集合类,如...
本资料主要关注Java集合的基础定义以及相关的练习,帮助开发者深入理解和掌握这些概念。 首先,我们来详细讲解Java集合的定义。在Java中,集合是一种容器,可以用来存储一组对象。集合框架包括了接口(如List、Set...
"Java-Interview-超全集合github上评分最高的jiva面试题"就是一个这样的宝藏,它涵盖了Java编程语言、Git版本控制工具以及面试策略等多个方面的知识点。以下是这些内容的详细解析: 1. **Java基础** - **数据类型...
在Java集合框架中,LinkedList、ArrayList、HashMap、TreeMap等都是非常常用的数据结构。本文将对Java集合框架的源码进行分析,深入探讨其实现原理和机制。 一、LinkedList源码分析 LinkedList是一种以双向链表...
Java基础-模拟HashMap集合(基于数组和链表) 在本文中,我们将详细介绍如何模拟Java的HashMap集合,使用数组和链表来实现Hash表的存储。我们将从基本概念开始,逐步深入到HashMap的实现细节中。 什么是HashMap? ...
本文将深入探讨Java集合类的汇总,包括List、Set和Map这三大核心接口及其实现类。 首先,让我们从List接口开始。List是一种有序的集合,允许有重复元素,并且支持通过索引来访问元素。ArrayList和LinkedList是List...
Java集合体系是Java编程中非常核心的部分,涵盖了用于存储和操作数据的各种数据结构。在Java中,集合主要分为三大接口:List、...在面试中,深入理解集合的底层原理和操作性能,能展现出扎实的Java基础和问题解决能力。
Java集合框架如ArrayList、LinkedList、HashSet、HashMap等会频繁出现,学习它们的使用有助于理解数据组织。 7. **IO流**:用于读取和写入文件,如保存和加载购物车数据。Java的FileInputStream、FileOutputStream...
- `java.util`包中的ArrayList, LinkedList, HashMap等是常用的集合类,提供了存储和操作对象的方法。 - 集合框架包括List, Set, Queue, Map等接口,以及它们的实现类。 7. **多线程**: - Java内置对多线程的...
在Java编程语言中,集合框架是开发者日常工作中不可或缺的一部分,HashMap作为其中的重要成员,它的实现原理对于理解Java性能优化和数据结构有深远的意义。HashMap是一个基于哈希表的数据结构,它实现了Map接口,...
本文将深入探讨Java集合框架的核心概念,包括`List`、`Set`、`Map`以及它们之间的区别和联系。 #### Java集合框架简介 Java集合框架是Java平台的一部分,它由一系列接口组成,这些接口描述了不同类型的容器,比如...
文本文件【7月3日.txt】至【7月25日.txt】可能是每日学习要点或作业的记录,随着时间推移,内容可能涉及更多高级主题,如集合框架(ArrayList、LinkedList、HashMap等)、IO流、线程并发、网络编程、数据库连接以及...
在本套课程中,将会非常深入、非常详细、非常全面的解读HashMap以及源码底层设计的思想。从底层的数据结构到底层源码分析以及怎样使用提高HashMap集合的效率问题等进行分析。如果掌握本套课程,那么再看其他javase的...
在Java编程语言中,HashMap是集合框架中一个重要的类,用于存储键值对的数据结构。这个实例教程将深入解析HashMap的遍历方法及其源代码,帮助开发者更好地理解和使用HashMap。以下是对这个主题的详细讲解: 1. **...
6. **集合框架**:Java集合框架包括List、Set、Queue和Map四大接口,以及ArrayList、LinkedList、HashSet、HashMap等实现类。熟练运用集合框架可以有效地组织和操作数据。 7. **IO流**:Java的IO流提供了读写文件、...
### JAVA编程高级——集合类知识点详解 #### 一、Java中的集合类概述 在Java编程中,集合类是一个非常重要的概念,它主要用于存储和管理对象的集合。...掌握这些知识点对于深入理解和使用Java集合框架至关重要。
ArrayList、LinkedList、HashSet、HashMap等都是常用的集合类,我们将探讨它们的用法和各自的特点。 5. **输入/输出(I/O)系统**:Java提供了丰富的I/O流API,用于处理文件读写、网络通信等。我们将会学习...