Android 源码系列之【二十一】从源码的角度深入理解SafeIterableMap

转载请注明出处:https://blog.csdn.net/llew2011/article/details/85222413

说起HashMap想必小伙伴们都非常熟悉吧,它出现在面试中的概率是很高的,网上有很多文章对它的实现原理做了详细的分析,这里不再做过多的论述,今天给大家带来的是一个新的集合类SafeIterableMap,直接看名字貌似是一个Map类,但是它并没有实现Map接口,而是采用链表和键值对的存储方式来模拟Map的功能,它的核心就是支持在遍历元素的时候对元素进行删除操作。

在17年的Google IO大会上,官方发布了一套用来帮助开发者解决架构设计的方案库:Android Architecture Components,并在GitHub上给出了Simple:https://github.com/googlesamples/android-architecture-components,我在运行官方给的Simple后为了弄清楚它的原理,就着手阅读了它的源码,在阅读源码的过程中发现了一个比较好使的类SafeIterableMap,当时就计划写篇文章详细讲解一下该类,但是只写到一半就沉睡到草稿箱了......现对这篇文章补充完整并发表出来,希望能给小伙伴一点帮助......

SafeIterableMap类的官方说明如下:

/**
 * LinkedList, which pretends to be a map and supports modifications during iterations.
 * It is NOT thread safe.
 *
 * @param <K> Key type
 * @param <V> Value type
 * @hide
 */
public class SafeIterableMap<K, V> implements Iterable<Map.Entry<K, V>> {
    ...
}

SafeIterableMap实现了Iterable接口,它是一个链表结构,模拟了Map的特性,支持在对元素遍历期间做修改,但需要注意的是该类不是线程安全的。既然是链表结构,那就应该有表示Node结点并且还有Node结点的个数的属性,我们接着看它的源码:

private Entry<K, V> mStart;
private Entry<K, V> mEnd;
// using WeakHashMap over List<WeakReference>, so we don't have to manually remove
// WeakReferences that have null in them.
private WeakHashMap<SupportRemove<K, V>, Boolean> mIterators = new WeakHashMap<>();
private int mSize = 0;

SafeIterableMap的属性仅仅有4个,mStart代表着头结点,mEnd表示尾结点,他们的类型是Entry类型,mSize代表当前结点的数量,mIterators是一个弱引用集合(小伙伴们都应该清楚弱引用集合相对于强引用集合来说就是为了防止出现内存泄露的),它的具体作用我们稍后再看,先看一下Entry的源码,如下:

static class Entry<K, V> implements Map.Entry<K, V> {
    @NonNull
    final K mKey;
    @NonNull
    final V mValue;
    Entry<K, V> mNext;
    Entry<K, V> mPrevious;

    Entry(@NonNull K key, @NonNull V value) {
        mKey = key;
        this.mValue = value;
    }

    @NonNull
    @Override
    public K getKey() {
        return mKey;
    }

    @NonNull
    @Override
    public V getValue() {
        return mValue;
    }

    @Override
    public V setValue(V value) {
        throw new UnsupportedOperationException("An entry modification is not supported");
    }

    // 省略...
}

Entry类是一个具有包访问权限的内部静态类,它实现了Map.Entry接口并且重写了toString()和equals()方法,Entry类中定义了K,V,代表当前结点的key和value,而mNext和mPrevious分别表示当前结点的下一个结点和前一个结点。需要注意的是Entry没有对setValue()方法做支持,如果调用了Entry的setValue()则回抛出UnsupportedOperation异常。

现在我们知道了SafeIterableMap是通过Entry实现的链表结构,接下来我们看一下SafeIterableMap所支持的对链表的操作。首先看一下添加操作,SafeIterableMap定义了两个添加操作,分别是put()和putIfAbsent()方法,put()方法的源码如下:

protected Entry<K, V> put(@NonNull K key, @NonNull V v) {
    Entry<K, V> newEntry = new Entry<>(key, v);
    mSize++;
    if (mEnd == null) {
        mStart = newEntry;
        mEnd = mStart;
        return newEntry;
    }

    mEnd.mNext = newEntry;
    newEntry.mPrevious = mEnd;
    mEnd = newEntry;
    return newEntry;
}

put()方法要求出入的key和value非空,然后根据参数初始化了一个Entry实例newEntry,接着把mSize的值累加1,在第一次调用put()方法的时候,mEnd此时为null,代码就进入了if(mEnd == null)语句块中了,这时候就把mStart和mEnd的地址都指向newEntry指向的内存地址,此时链表的头结点和尾结点都指向了newEntry,然后返回newEntry的值。在之后的调用put()方法时,mEnd属性就不为null了,则就跳过if()语句块了,这时候的操作是先把mEnd结点的mNext尾结点指向新建的newEntry结点,然后把新建newEntry结点的mPrevious头结点指向mEnd结点,最后mEnd结点的指针指向新建newEntry结点。这波操作后就实现了链表的添加操作并且所添加的结点都是依次添加到了链表的尾部。实现过程可如下图所示:

清楚了SafeIterableMap的put()方法后,我们在看一下putIfAbsent()方法的实现方法,源码如下所示:

扫描二维码关注公众号,回复: 9372823 查看本文章
public V putIfAbsent(@NonNull K key, @NonNull V v) {
    Entry<K, V> entry = get(key);
    if (entry != null) {
        return entry.mValue;
    }
    put(key, v);
    return null;
}

putIfAbsent()方法同样要求参数非null,它先根据传入的参数key从链表中查找对应的Entry,如果找到了则直接返回该Entry的值,否则执行put操作,我们接下来看看SafeIterableMap的get()方法是如何实现的,源码如下:

protected Entry<K, V> get(K k) {
    Entry<K, V> currentNode = mStart;
    while (currentNode != null) {
        if (currentNode.mKey.equals(k)) {
            break;
        }
        currentNode = currentNode.mNext;
    }
    return currentNode;
}

SafeIterableMap的get()方法是protected的,它的实现是从当前链表头结点mStart开始,依次循环当前链表,如果找到对应结点的key和传入的参数key相等则终止循环并返回当前结点,如果循环完当前链表还没有找到对应结点则最终返回null。添加和查询方法我们已经知道了实现过程,接下来看下删除方法remove(),源码如下:

public V remove(@NonNull K key) {
    Entry<K, V> toRemove = get(key);
    if (toRemove == null) {
        return null;
    }
    mSize--;
    if (!mIterators.isEmpty()) {
        for (SupportRemove<K, V> iter : mIterators.keySet()) {
            iter.supportRemove(toRemove);
        }
    }

    if (toRemove.mPrevious != null) {
        toRemove.mPrevious.mNext = toRemove.mNext;
    } else {
        mStart = toRemove.mNext;
    }

    if (toRemove.mNext != null) {
        toRemove.mNext.mPrevious = toRemove.mPrevious;
    } else {
        mEnd = toRemove.mPrevious;
    }

    toRemove.mNext = null;
    toRemove.mPrevious = null;
    return toRemove.mValue;
}

remove()方法同样要求传入参数非null,该方法看起来比较长,其实很好理解,先是根据传入参数key从链表中查找要删除的Entry结点toRemove,如果没有找到就直接返回null,否则继续往下执行,先是把mSize的值减去1,然后判断集合mIterators是否有元素,如果有则遍历该集合并把roRemove参数传递进去做相应操作,然后就是链表的删除操作:先是判断待删除结点toRemove的头结点是否为null,如果非null就表示toRemove结点是不整个链表的头结点,这时候把toRemove的头结点的mNext指针指向toRemove的下一个结点,否则就是头结点,就把原先指向链表头结点的指针mStart指向toRemove结点的下一个结点。toRemove的mPrevious的指针处理完了,就是处理toRemove的mNext指针了。先判断toRemove的mNext结点是否是null,如果为空就表示toRemove是不最后一个结点,把toRemove的mNext指针指向toRemove的mPrevious指向的对象,否则toRemove就是最后一个结点,就把原先指向链表结尾的mEnd指针指向toRemove结点的上一个结点,最后把toRemove的mNext和mPrevisour置null最终返回了toRemove结点,链表的删除过程如下所示:

目前我们清楚了SafeIterableMap的add()、get()、remove()方法,接下来就是遍历链表操作了。由于SafeIterableMap实现了Iterable接口,所以我们看一下iterator()方法的具体实现,源码如下所示:

/**
 * @return an ascending iterator, which doesn't include new elements added during an
 * iteration.
 */
@NonNull
@Override
public Iterator<Map.Entry<K, V>> iterator() {
    ListIterator<K, V> iterator = new AscendingIterator<>(mStart, mEnd);
    mIterators.put(iterator, false);
    return iterator;
}

iterator()方法的注释说它返回了一个递增的迭代器,但是它在迭代过程中并不包含新增的元素。怎么理解这句话呢?源码是最好的答案。iterator()方法中首先创建了一个AscendingIterator的迭代器iterator,该迭代器把当前链表的头指(mStart)和尾指针(mEnd)作为参数传递给了AscendingIterator(也就是说又创建了两个指针,分别有指向了当前链表的头结点和尾结点)。然后又把该迭代器放入了mIterators集合中,最后返回该iterator,我们看一下AscendingIterator的源码:

private abstract static class ListIterator<K, V> implements Iterator<Map.Entry<K, V>>, SupportRemove<K, V> {
    Entry<K, V> mExpectedEnd;
    Entry<K, V> mNext;

    ListIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
        this.mExpectedEnd = expectedEnd;
        this.mNext = start;
    }

    @Override
    public boolean hasNext() {
        return mNext != null;
    }

    @Override
    public void supportRemove(@NonNull Entry<K, V> entry) {
        if (mExpectedEnd == entry && entry == mNext) {
            mNext = null;
            mExpectedEnd = null;
        }

        if (mExpectedEnd == entry) {
            mExpectedEnd = backward(mExpectedEnd);
        }

        if (mNext == entry) {
            mNext = nextNode();
        }
    }

    private Entry<K, V> nextNode() {
        if (mNext == mExpectedEnd || mExpectedEnd == null) {
            return null;
        }
        return forward(mNext);
    }

    @Override
    public Map.Entry<K, V> next() {
        Map.Entry<K, V> result = mNext;
        mNext = nextNode();
        return result;
    }

    abstract Entry<K, V> forward(Entry<K, V> entry);

    abstract Entry<K, V> backward(Entry<K, V> entry);
}

ListIterator是个抽象类并且实现了Iterator和SupportRemove接口,其属性mNext表示头结点,mExceptedEnd表示尾结点,然后对外提供了两个抽象方法forward()和backward(),表示向前遍历或者向后遍历链表。我们先看一下supportRemove()方法的实现,如果要删除的entry节点既和mExpectedEnd相等又和mNext相等,那么表示当前链表中只有一个元素,因此需要把mNext和mExpectedEnd置空。如果要删除的entry结点和mExpectedEnd结点相同,那么就调用backward()方法找到当前链表的上一个结点作为尾结点。如果要删除的entry结点和mNext结点相同,则调用nextNode()方法找到当前链表上的下一个结点作为头结点。由于ListIterator是个抽象类,他的子类分别是AscendingIterator和DescendingIterator,这俩子类的唯一区别就是对forward()和backward()方法的实现不同,一个是正序,一个是倒序。源码如下所示:

static class AscendingIterator<K, V> extends ListIterator<K, V> {
    AscendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
        super(start, expectedEnd);
    }

    @Override
    Entry<K, V> forward(Entry<K, V> entry) {
        return entry.mNext;
    }

    @Override
    Entry<K, V> backward(Entry<K, V> entry) {
        return entry.mPrevious;
    }
}

private static class DescendingIterator<K, V> extends ListIterator<K, V> {

    DescendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
        super(start, expectedEnd);
    }

    @Override
    Entry<K, V> forward(Entry<K, V> entry) {
        return entry.mPrevious;
    }

    @Override
    Entry<K, V> backward(Entry<K, V> entry) {
        return entry.mNext;
    }
}

ListIterator的next()方法很重要,该方法先用result指针指向mNext结点,然后调用nextNode()方法使mNext指针指向下一结点,最后把result结点返回。在nextNode()方法中,如果mNext==mExpectedEnd或者mExpectedEnd==null就返回null,而mNext==mExpectedEnd就意味着遍历当前链表到mExpectedEnd结点处就终止遍历......这下终于明白了刚刚iterator()方法所说的在遍历期间不包括新添加的元素的原因了,简单说就是:iterator()方法中返回的是AscendingIterator类,该类创建了两个指针分别指向当前链表的头结点mStart和尾结点mEnd,通过Iterator遍历链表时,其实是移动的AscendingIterator类中的头指针,当这两个指针指向同一个结点时就会赋值mNext为null,因此再调用hasNext()方法时就返回false,这样就终止了遍历......为了解决在遍历过程中不包括新加元素的问题,SafeIteratorMap又对外提供了一个新的方法iteratorWithAdditions()方法,该方法源码如下:

/**
 * return an iterator with additions.
 */
public IteratorWithAdditions iteratorWithAdditions() {
    @SuppressWarnings("unchecked")
    IteratorWithAdditions iterator = new IteratorWithAdditions();
    mIterators.put(iterator, false);
    return iterator;
}

iteratorWithAdditions()方法返回了一个IteratorWithAdditions类型的iterator实例并把该实例添加到了mIterators结合中,IteratorWithAdditions源码如下所示:

private class IteratorWithAdditions implements Iterator<Map.Entry<K, V>>, SupportRemove<K, V> {
    private Entry<K, V> mCurrent;
    private boolean mBeforeStart = true;

    @Override
    public void supportRemove(@NonNull Entry<K, V> entry) {
        if (entry == mCurrent) {
            mCurrent = mCurrent.mPrevious;
            mBeforeStart = mCurrent == null;
        }
    }

    @Override
    public boolean hasNext() {
        if (mBeforeStart) {
            return mStart != null;
        }
        return mCurrent != null && mCurrent.mNext != null;
    }

    @Override
    public Map.Entry<K, V> next() {
        if (mBeforeStart) {
            mBeforeStart = false;
            mCurrent = mStart;
        } else {
            mCurrent = mCurrent != null ? mCurrent.mNext : null;
        }
        return mCurrent;
    }
}

IteratorWithAdditions的hasNext()方法先根据boolean变量mBeforeStart判断是否还有下一个元素,默认情况下mBeforeStart为true,所以进入if语句块中,这时候next()方法中链表头结点mStart的值并把mBeforeStart设置为false,在下一次循环中hasNext()方法就是判断当前节点不为null并且当前结点的下一个结点也不为null同时成立,才表示还有下一个,这样就实现了在遍历过程中如果在其它线程添加了新的元素,也会在遍历过程中把新添加的元素包含进来,这种方法很是巧妙,值得借鉴。

SafeIterableMap的核心是解决HashMap在不使用Iterator进行元素遍历时若对该元素做删除则抛出ConcurrentModificationException的异常而设计的新的数据结构,那么它是怎么实现的了?在iterator()的实现方法中会把新生成的AscendingIterator实例添加到mIterators集合中,当执行SafeIterableMap的remove()方法时,都会遍历mIterators集合并把待删除的toRemove参数传递进入,我们直接看一下ListIterator的实现,源码如下:

@Override
public void supportRemove(@NonNull Entry<K, V> entry) {
    if (mExpectedEnd == entry && entry == mNext) {
        mNext = null;
        mExpectedEnd = null;
    }

    if (mExpectedEnd == entry) {
        mExpectedEnd = backward(mExpectedEnd);
    }

    if (mNext == entry) {
        mNext = nextNode();
    }
}

supportRemove()方法的实现很简单,如果待删除的entry结点既和头结点mNext又和尾结点mExpectedEnd相等,就表示链表只有一个元素,则直接置空头结点和尾结点;如果待删除结点entry和mExpectedEnd相等,就表示删除链表的尾结点,则把mExpectedEnd的指针前移指向上一个结点;如果待删除结点entry和头结点mNext相等,就表示要删除头结点,则把mNext指针下移指向下一个结点,这波操作后就完成了链表的删除......

好了,到这里我们已经讲完了SafeIterableMap的实现原理,很简单有木有?阅读源码最重要的是理解其实现原理,原理弄清楚后我们可以写自己的数据结构了,由于篇幅原因,我将在下篇文章中带领大家写一个属于自己的数据结构集合类,敬请期待......

发布了39 篇原创文章 · 获赞 87 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/llew2011/article/details/85222413