jdk8 源码之HashSet 和 LinkedHashMap

前面的博客中我们分析了HashMap的源码,这一篇我们说一下HashSet和LinkedHashMap的实现逻辑。他们其实都是基于HashMap来实现,因此把它们放在一起讲还是比较合适的。

HashSet

HashSet也被称为集合,但该容器中只能存储不重复的对象。

//实现了Set接口,继承了AbstractSet类
//Set接口中定了一个Set具有那些行为和default方法
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;
    //实际存储对象的地方
    private transient HashMap<E,Object> map;

    //key所映射对象的虚拟值
    private static final Object PRESENT = new Object();
.....    
}

可以看到HashSet内部声明了一个HashMap对象,主要是利用了HashMap中的键值不能重复的特性,和HashSet中对象不能重复的特性一致,我们只需要将key所对应的value设为PRESENT即可,这样就可以完美的使用HashMap来实现HashSet了。

HashSet构造方法
public HashSet() {
    map = new HashMap<>();
 }
 //初始值
public HashSet(Collection<? extends E> c) {
    //最小容量16
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
    boolean modified = false;
    for (E e : c)
        //本质还是调用add方法
        if (add(e))
            modified = true;
    return modified;
}


//指定初始容量和负载因子
public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}
//指定初始容量
public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}

可以看到HashSet构造方法基本都是对HashMap的构造方法的一层包装而已。其他增、删、遍历等方法都是直接调用HashMap对应的方法,就不再说了。

LinkedHashMap

HashMap是不记录插入顺序的,但是有时候我们在使用HashMap的时候,某些场景下,我们需要记录插入顺序,来完成一些特定的操作,而且也要保证按key查找的效率。因此LinkedHashMap就出现了,它在原有HashMap的基础下,再维护了一条链表,来记录插入顺序。

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{
 //链表第一个节点
 transient LinkedHashMap.Entry<K,V> head;
 //链表最后一个节点
 transient LinkedHashMap.Entry<K,V> tail;
 //此链表排序方法:true 访问顺序,false 插入顺序
 //LinkedHashMap中默认都是false的,即按插入顺序
 final boolean accessOrder;
...
}

LinkedHashMap是继承HashMap的,里面多维护了一条按插入顺序链表。

 //LinkedHashMap存储对象的节点
 static class Entry<K,V> extends HashMap.Node<K,V> {
        //按指定顺序记录上一个和下个节点,迭代链表
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
LinkedHashMap 构造方法
//指定负载因子和容量
public LinkedHashMap(int initialCapacity, float loadFactor) {
    //调用父类的构造方法
   super(initialCapacity, loadFactor);
    //指定链表迭代顺序:按插入
   accessOrder = false;
}
//其它构造方法都差不多
....
LinkedHashMap 增加

可以看到LinkedHashMap没有实现自身的add方法,还是直接调用父类HashMap的add方法

//插入节点
 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
//该方法具体细节,可以查看前面介绍HashMap的博客   
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    //创建一个链表节点,LinkedHashMap重写该方法
     ...newNode(hash, key, value, null);
   //情况一:map中存在key对应的节点,更该节点的value值
   if (e != null) { // existing mapping for key
       V oldValue = e.value;
       if (!onlyIfAbsent || oldValue == null)
           e.value = value;
       afterNodeAccess(e);
       return oldValue;
   }
   //情况二:map中不存在key对应的节点
   afterNodeInsertion(evict);
   return null;
 }
 //LinkedHashMap重写newNode方法,增加调用了linkNodeLast方法
  Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
        //实例一个链表节点
        LinkedHashMap.Entry<K,V> p =
            new LinkedHashMap.Entry<K,V>(hash, key, value, e);
        //将当前节点添加到迭代链表尾部
        linkNodeLast(p);
        return p;
    }
 //LinkedHashMap重写newTreeNode方法,增加调用了linkNodeLast方法
 TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        //实例一个树节点
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        //将当前节点添加到迭代链表尾部
        linkNodeLast(p);
        return p;
    }

   //将节点p添加值迭代链表尾部
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }
//插入节点后
void afterNodeInsertion(boolean evict) { 
        LinkedHashMap.Entry<K,V> first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
//是否删除最老的一个节点
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  return false;
}
//将当前节点移动值迭代链表后尾部
 void afterNodeAccess(Node<K,V> e) { 
        LinkedHashMap.Entry<K,V> last;
        //获取尾部节点tail 赋值给 last
        if (accessOrder && (last = tail) != e) {
        //accessOrder默认为false,按插入顺序,因此不会移动当前节点
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
    }

可以看到LinkedHashMap中,afterNodeInsertion和afterNodeAccess都没有起作用,因此这两个方法应该是子类去使用,需要改变accessOrder的初始值和重写removeEldestEntry方法。

LinkedHashMap 删除

LinkedHashMap也没有重写remove方法,只是重写afterNodeRemoval方法,在节点删除后,将当前节点从迭代列表删除

 //删除节点
 public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
  //删除节点
  inal Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {
   ...
   ++modCount;
   --size;
   //当前节点被删除后
   afterNodeRemoval(node);
   return node;
   }
 //当前节点被删除后
  void afterNodeRemoval(Node<K,V> e) { // unlink
  //将当前节点从迭代列表删除
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.before = p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a == null)
            tail = b;
        else
            a.before = b;
    }
LinkedHashMap 顺序遍历迭代器

由于LinkedHashMap维护了一条迭代列表,因此LinkedHashMap在遍历时直接遍历该迭代链表就可以了,因此新写了一个迭代器。

abstract class LinkedHashIterator {
        //下一个节点
        LinkedHashMap.Entry<K,V> next;
        //当前节点
        LinkedHashMap.Entry<K,V> current;
        //保证‘结构变更’时,快速失败机制
        int expectedModCount;

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            //指向下一个节点
            next = e.after;
            return e;
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            //调用removeNode删除当前节点
            removeNode(hash(key), key, null, false, false);
            //更新expectedModCount的值
            expectedModCount = modCount;
        }
    }

关于顺序遍历器Iterator我在前面的博客中详细介绍过,这里就不在赘述了。
另外说明:LinkedHashMap中的顺序遍历都是基于对迭代链表的遍历来实现的,如forEach、replaceAll等方法中的遍历都是这样做的。

LinkedHashMap 并行遍历迭代器

LinkedHashMap没有重新定义个并行遍历器,直接使用父类的方法来构造并行遍历迭代器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值