JDK容器与并发—Map—HashMap
2016-04-20 17:34
393 查看
概述
基于数组+单链表优点构造的Map,非线程安全。1)实现原理等效于Hashtable,Hashtable虽然为线程安全的,但由于其读写方法均为synchronized,并发效率低;
2)在哈希函数哈希合理情况下,基础操作get、put等为固定时间;容器视图迭代器的遍历时间与(table数组长度+所有bucket包含的键值对数)成比例;
3)影响性能的两个参数:初始容量、负载因子:容量为table数组长度(即bucket数);负载因子为权衡HashMap在时间成本与内存空间成本中的参数,默认为0.75,太大则会增加内存开销、减少遍历时间;负载阈值为容量*负载因子,当键值对数达到量负载阈值时,容量会调整增加一倍,进行重哈希;需要设置合理的初始容量及负载因子,尽量避免重哈希;
4)迭代器fail-fast;
数据结构
数组+单链表:transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } }
构造器
// 指定初始容量、负载因子构造 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; init(); } // 指定初始容量,采用默认负载因子0.75 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 无参构造:默认初始容量16,默认负载因子0.75 public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } // 带Map参数构造,初始容量足够容下m键值对,采用默认负载因子,将m键值对加进来 public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); inflateTable(threshold); putAllForCreate(m); }
增删改查
table容量调整策略
1)当HashMap中键值对数达到负载阈值则对其table进行容量调整,table容量翻倍;2)table最大容量为MAXIMUM_CAPACITY = 1 << 30;
3)table容量达到MAXIMUM_CAPACITY 后,如果有resize请求,则仅仅将负载阈值调整为Integer.MAX_VALUE,不会产生新的table,不会控制键值对的添加;
4)若table resize过程中产生新的table,需要将旧table关联的键值对重新在新table中确定bucket,再添加进来,也就是所说的hash table rehash。
// 将带有指定的key、value、hash code的entry添加到bucketIndex指定的bucket void addEntry(int hash, K key, V value, int bucketIndex) { // HashMap中键值对数达到负载阈值则对其进行容量调整 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); // table容量翻倍 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } // table扩展,且将旧table中的键值对在新table中重新确定bucket,再添加进来 // table最大容量为MAXIMUM_CAPACITY,之后如果有新的键值对添加,不会控制 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; // 仅仅调整阈值为Integer.MAX_VALUE return; } Entry[] newTable = new Entry[newCapacity]; // 新table transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; // 相应地调整负载阈值 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { // table数组遍历 while(null != e) { // 每一个bucket遍历 Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); // 重新确定bucket e.next = newTable[i]; // 在bucket首位添加 newTable[i] = e; e = next; } } } // 根据key、value、hash创建Entry,在bucketIndex位置的bucket首位添加 void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
增、改
步骤:1)根据key的hashCode获取hash码;
2)用hash码确定bucketIndex;
3)先遍历bucket中键值对,确定是否已有相等key的,有则替换新value后返回;否则用key、value、hash创建Entry,将其链接到bucket首位。
public V put(K key, V value) { if (table == EMPTY_TABLE) { // table还没有初始化 inflateTable(threshold); } if (key == null) return putForNullKey(value); // 约定:当key为null时,对应bucketIndex为0 int hash = hash(key); // 根据key的hashCode获取hash码 int i = indexFor(hash, table.length);// 确定bucketIndex for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 遍历bucket是否存在相等key的键值对 Object k; // hash相同且(key相同或相等)则存在 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; // 替换新value e.recordAccess(this); return oldValue; // 返回旧的value,该value也有可能为null } } modCount++; // fail-fast控制 addEntry(hash, key, value, i); return null; // null表明新增了键值对(一般尽量避免在HashMap中用null的key或value) } // 初始化table private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); // 初始化负载阈值,超过则重哈希 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); } // 对key的hashCode进行再次哈希,对其低位进一步散列 // 由于table length为2的幂次方,所以对那些低位相同的hashCode,用indexFor函数容易产生hash碰撞; final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // 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); } // 根据hash值找bucketIndex static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }
删
步骤:1)根据key的hashCode获取hash码;
2)用hash码确定bucketIndex;
3)遍历bucket中键值对,删除指定key的Entry,若存在,则返回该Entry;否则返回null。
// 删除指定key的键值对 // 如果存在,则返回关联的value(可能为null,尽量避免);否则返回null public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } // 删除关联key的Entry // 若存在,则返回该Entry;否则返回null final Entry<K,V> removeE 4000 ntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); // 根据key的hashCode获取hash码 int i = indexFor(hash, table.length); // 用hash码确定bucketIndex Entry<K,V> prev = table[i]; Entry<K,V> e = prev; // 遍历bucket中键值对,若存在则删除 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--; if (prev == e) // e为bucket链表首节点 table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
查
步骤:1)根据key的hashCode获取hash码;
2)用hash码确定bucketIndex;
3)遍历bucket中键值对,若存在指定key关联的Entry,则返回关联的value(可能为null,应该尽量避免);否则返回null
// 获取指定key关联的value。若存在,则返回关联的value(可能为null,应该尽量避免);否则返回null public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); 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 != null && key.equals(k)))) return e; } return null; }
迭代器
基础Iterator为HashIterator,KeyIterator、ValueIterator、EntryIterator都继承于它。迭代器在遍历过程中,是对table数组,每一个bucket,从头至尾的遍历,会遍历到每一个Entry:private abstract class HashIterator<E> implements Iterator<E> { 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; while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } 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; } }
特性
解决hash碰撞
1)HashMap的table数组的长度为2的幂次方;2)hash(Object k)函数是对key的hashCode再次哈希,对其低位进一步散列;
3)基于1)2),采用indexFor(int h, int length)函数,用单链表解决key的hashCode的hash碰撞问题。
如何利用数组和单链表的优势?
增删改查的步骤一般有三步:1)根据key的hashCode获取hash码;
2)用hash码确定bucketIndex;
3)遍历bucket;
其中1)2)利用数组高效随机访问的优点找到bucket;3)利用单链表动态增删的优点。
相关文章推荐
- Ubuntu 安装 JDK 问题
- 探究在C++程序并发时保护共享数据的问题
- Nodejs实战心得之eventproxy模块控制并发
- 并发环境下mysql插入检查方案
- jdk与jre的区别 很形象,很清晰,通俗易懂
- jdk中String类设计成final的原由
- win7下安装 JDK 基本流程
- jdk环境变量配置
- win2003 jsp运行环境架设心得(jdk+tomcat)
- windows linux jdk安装配置方法
- Java编程之jdk1.4,jdk1.5和jdk1.6的区别分析(经典)
- 详解JDK 5 Annotation 注解之@Target的用法介绍
- PHP编程中尝试程序并发的几种方式总结
- 浅析PHP中Session可能会引起并发问题
- php session的锁和并发
- Oracle 数据库针对表主键列并发导致行级锁简单演示
- 简单记录Cent OS服务器配置JDK+Tomcat+MySQL
- MySQL中SELECT+UPDATE处理并发更新问题解决方案分享
- Android开发的IDE、ADT、SDK、JDK、NDK等名词解释
- Java4Android开发教程(一)JDK安装与配置