Java 笔记 -HashMap
HashMap使用的数据结构,专业术语“链表散列”,代码实
/**
* 定义了一个Entry的数组用来存储数据。
*/
transient Entry[] table;
```java
/**
* Entry是内部定义的类
*/
static class Entry<K,V> implements Map.Entry<K,V> {
/**
* 定义了两个常量key,就是HashMap的key
* value,key对应的value
* next,下一个Entry对象,故Entry的数据结构是一个单项的链表
* 常量hash值,可以说他是bucket,存储的其实是table数组的下标值。
*/
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;
}
/**
设置value的同时会返回旧值
*/
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
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() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* 会在调用HashMap put方法时调用它,但是它本身是空方法,
*目前了解的是LinkedHashMap会重写这个方法,
*每次put时会将对象放到双向链表的尾,
*因为LinkeHashMap要实现LRU算法,我会专门写LinkedHashMap,
*要详解可以移步那里看看。
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}
- transient 实现了Serializable的类中,被标识了transient 的属性序列化时会被忽略。
返回true意味着传入的loadFactor 为if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
NaN = 0.0f / 0.0f;
/** hashMap存储数据的地方,长度永远是2的次幂(因为定位bucket时有对长度进行‘与’操作,其他作用后续说),默认初始16 */ transient Entry[] table;
/** table表里允许存储的最大数据,其实可以计算出来(table.size()*loadFactor+1)但程序中多处使用,使用常量表示更方便。 */ final float loadFactor;
/** 负载因子,意味着数据可以存储相对于总容量的百分比,默认0.75, 当数据超过了loadFactor(table.size()*loadFactor+1),会调用resize(),增大table容量,重新hash定位所有数据,耗费性能,所以对于数据集可以提前可以预知数据大小的,可以初始化时设置此值还有初始容量initialCapacity */ final float loadFactor;
/** 当hashMap结构发生改变(移除数据或者添加数据),会修改这个值, modCount++,主要的作用在于hashMap是非线程安全的,在调用迭代器每次取数据时会判断modCount在这段期间内是否被修改,如果修改了说明数据发 * 生变化,会抛出ConcurrentModificationException,一种安全机制吧。 */ transient int modCount;
/**
hashMap本身是散列表结构,它定位元素的逻辑是先取key的hashcode,再通过此hash函数
再次hash,之后调用indexFor()根据table表的长度取余,找到存储的偏移量,之后再比较value的值。
此次hash的目的是因为key的hashcode无法保证高质量的将元素散列开来。
NULL的key,将定位到0
*/
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
```java
/**
get方法,其实没啥好说的,时间复杂度因为散列数据结构故O(1)
*/
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;
}
/**
这个也挺简单的,主要说下recordAccess(this),Entry中的方法,在hashMap中,是没有操作的,
放这个方法还有recordRemoval,是留给其他类使用的如LinkedHashMap :用来实现LRU置换算法。
感兴趣的可以看下LinkedHashMap
addEntry(hash, key, value, i)是将value添加到key的链表上。
*/
public V put(K key, V value) {
if (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;
}
/**
新建一个Entry,头插法。
主要在resize()方法
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
/**
当hashMap中存储的size不在小于threshold(capacity * load factor)时,需要
重新调整table的大小,每次调整完都会将元素重新排序,当数据量大了这会是一个耗时操作,
并且我们使用的时候应该尽量让我们初始的hashMap容量接近要使用的容量/loadFactor,减少置换次数。
*/
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* 将旧表中的数据重新hash,放入新表中。
*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
```java
/**
remove操作
*/
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
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;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
```java
/**
hashMap中是否含有相应的value,还有一个containsKey方法没说,
想提下这个是因为,判断包含value需要循环比对,时间复杂度O(n)。慎重使用。
*/
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
```java
/**
最后的是hashMap的迭代了,一起来说,HashIterator内部私有类,几个属性里说下expectedModCount
上面的hashMap添加删除操作,都会更新一个modCount,开头已经说了,是hashMap是非线程安全的,这个属性
是在迭代时用来判断迭代过程中,hashMap的值是否被更新过,expectedModCount就是在Iterateor构造时赋值
迭代过程中判断。
所以,当我们使用迭代器遍历hashmap时,如果想同时修改hashMap内容(只能删除)。使用Iterator提供的remove方法
*/
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;
//定位到第一个value中不为null的位置。
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K,V> nextEntry() {
//迭代过程中,元素被修改,抛出ConcurrentModificationException
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。
*/
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();
}
}
// Subclass overrides these to alter behavior of views‘ iterator() method
Iterator<K> newKeyIterator() {
return new KeyIterator();
}
Iterator<V> newValueIterator() {
return new ValueIterator();
}
Iterator<Map.Entry<K,V>> newEntryIterator() {
return new EntryIterator();
}
/**
好吧,三面的三种迭代初始化,只能提供包内部使用,真正暴露出来的方法是下面三个,但是换汤不换药,
各位判官自行看看就好。可以根据自己的需求调用相应的Iterator
这三种的初始赋值使用的分别是keySet,values和entrySet。
*/
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();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
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();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
HashMap.this.clear();
}
}
private transient Set<Map.Entry<K,V>> entrySet = null;
/**
Note:这里,这里,说实话我也没搞懂,为什么要这种写法entrySet()再调entrySet0()。
网上找了找,也没啥令人信服的说法。如果哪位了解,请务必要告诉我一下。
*/
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();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> e = (Map.Entry<K,V>) o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
HashMap.this.clear();
}
}
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。