最近再看集合包源码 必须要记下来 要么就白看了。 有点忧虑,最后还是按照 Map和Collection 划分了。
Map 接口的具体定义 直接去看源码哈,只把重要的记下。 Entry<K,V> 这个接口就是 Map中存放的核心数据 ,对于不同的子类有不同的实现。
下面来围观一下 AbstractMap<K,V> 直接实现了 Map<K,V>接口 其中大部分方法都是通过得到Entry<K,V>的迭代器来实现的。 它定义了两个数据结构
transient volatile Set<K>
transient volatile Collection<V>
通过entrySe()t 获得迭代器 ,entrySet()被定义为抽象方法 。
public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
HashSet
看一下HashSet 数据结构
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
Object域被 PRESENT 填充, 实现上没什么特别就不多说了
上面这个例子是get 方法的实现 。
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
put 方法 抛出 异常 这个是可以理解的,因为在AbstractMap中我们并没有定义存储结构,所以我们只能根据entrySet的迭代器来处理
这是 keySet的实现
public Set<K> keySet() {
if (keySet == null) {
keySet = 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 contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
}
return keySet;
}
这个实现使用了双重匿名内部类。 写得很不错哦 , Collection<V> 也是同样的实现方式。
另外这个类用到了大量的 模版方法模式
public abstract Set<Entry<K,V>> entrySet(); 在子类中分别实现。
下面来看 HashMap
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
看看它的属性
static final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
transient int size;
/**
* The next size value at which to resize (capacity * load factor).
* @serial
*/
int threshold;
final float loadFactor;
transient volatile int modCount;
主要 看下 Entry[ ] table 这个是最主要的 , threshold 通过注释可以看出来 下次需要扩容的大小 , modCount 是用来标识 迭代器是否过期的。
transient 关键字保证被它修饰的属性是不可以 序列化的。 volitile 则保证了该属性具有”视图完整性“,也就是当多个cpu 同时使用该变量时保证每次更改或者获取都从内存中,而不是CPU的缓存中。
看一下 Entry<K,V>的数据结构
final K key;
V value;
Entry<K,V> next;
final int hash;
这是可以看出 Entry<K,V>维护了一个链表,当经过计算后落入同一个桶中的时候,将Entry放入链表尾部。
这样整个 HashMap 的核心数据结构我们就清楚了 ,下面看一些具体的实现,注意 默认的容量是16 加载因子是0.75。
下面看看它的构造器,来看最典型的
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);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
注意下抛出的异常类型哈, 注意上面初始容量如何确定的
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);
}
这个是具体的散列函数
static int indexFor(int h, int length) {
return h & (length-1);
}
这个函数返具体在table 中的下标
需要注意的是 HashMap 是可以存放 key==null的元素的,key==null的元素被放在数组下表为0处。
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;
}
无论是插入查找还是删除,总是先查看 key==null的情况
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;
}
上面 put 方法的实现 通过第四句 我们通过 hash(int h)方法将 key的hashcode 再hash 一次然后通过indexFor(hash, table.length);找到在数组中的位置,放于链表后
另外注意 我们首先查看是否已经存在key相同的元素,如存在我们就把他重新赋值,如果没有我们则在末端插入。
注意 modCount++一句,以后会做解释。
另外注意 hash值也会保存在实体中。 而且我们比较两元素是否相等的时候,先比较他们的hash值。这也算得上是一个优化吧。
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);
}
上面是resize 方法 每次resize后我们重新将元素分桶。
那么哪些方法会引起 resize 呢?
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);
}
当然是addEntry 方法啦 会引起2被扩容,当然还有putAll(Map<? extends K, ? extends V> m) 方法
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<K,V> m = (Map<K,V>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
equals实现的很好哈。《effective java》里面就推荐这么写 要借鉴。
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
hashCode 方法是这样实现的
下面看下 怎样会让 迭代器快速失效
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;
}
}
这就是我们迭代器的实现, 注意每次调用方法的时候都需要比较modCount 来查看迭代器是否过期 ,如果过期了会抛出ConcurrentModificationException 这个异常
那么哪些方法会引起迭代器过期呢? 当然是增加 或者 删除元素啦。
所以对于HashMap这种线程 不安全的容器来说我们要注意它的使用。
HashMap 大概就是这样了。
再看一下LinkedHashMap
private transient Entry<K,V> header;
private final boolean accessOrder;
第二个是 用于确定是否按 LRU 顺序保存
void init() {
header = new Entry<K,V>(-1, null, null, null);
header.before = header.after = header;
}
这个初始化很简单
Entry<K,V> 除了继承了父类的属性 新添加了 Entry<K,V> before, after;
看了这个数据结构大家就明白了。
这个优化在顺序遍历,保存了元素的插入顺序 ,可用于顺序遍历,其他与HashMap 相似,至于 Lru 只是提供了一个接口 并没有实现。