面试之敌系列 9 集合知识汇总

LinkedHashMap
LinkedHashMap 很像一个HashMap 加 一个双向链表,其实HashMap 用于存放数据,而双向链表维护访问顺序,这样的组合功能。在一般的情况下,HashMap的迭代访问是没没有顺序的,但是有些情况下既需要使用HashMap 的高性能,也需要使用到顺序访问的功能,因此就将两者结合起来。

首先LinkedHashMap 的put 和HashMap 的put 是一模一样的。但是!!!,LinkedHashMap 覆写了put 中会使用到的newNode方法和afterNodeInsertion方法。

HashMap的newNode
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}

LinkedHashMap的newNode
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);
    //从这里可以看出,对于新加入的节点,LinkedHashMap 会维护到双向链表的尾部。同时在HashMap 中也维护。
    return p;
}
//这是LinkedHashMap 的节点类,和HashMap的Node 不太一样。多了两个节点,用于构建双向链表。
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);
    }
}
//插入到尾部并同时维护双向链表的操作过程
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;
    }
}

复制代码
对于后面的afterNodeInsertion()

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}
这个方法里面提供了一个插入一个新节点之后,头节点的改变情况,其中,removeEldestEntry(first)可以覆写,
用于实现一些常见的操作。例如我们经常面试中被问及到的LRU算法就可以在这里实现逻辑。
表示的是什么时候需要对头结点进行删除。LinkedHashMap是尾插法,
但是删除的时候这里提供了头部删除逻辑,因此非常适合进行LRU 的实现。

复制代码
对于accessOrder,主要就是加了一个afterNodeAccess来讲上次访问的元素移到双向链表的后面。这样就可以实现最近浏览的功能。

欣赏一下LinkHashMap的get

public V get(Object key) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        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;
    }
}

复制代码

同时还有迭代器,可以看到到,此时的迭代是在一个新的LinkedEntrySet上执行的。而HashMap 是在EntrySet上执行。
public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
    }

final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { LinkedHashMap.this.clear(); }
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new LinkedEntryIterator();
    }

    //Set迭代的实现流程
    public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
        if (action == null)
            throw new NullPointerException();
        int mc = modCount;
        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
            action.accept(e);
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }
}

复制代码

最后附上实现LRU的代码

class LRUCache extends LinkedHashMap{
private int cahceSize = 0;
public LRUCache(int maxSize){
    super(maxSize, 0.75F, true);
    this.cahceSize = maxSize;
}

@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
    return size()>cahceSize;
}

}
复制代码
一波总结
其实和HashMap是基本没有什么区别的,只是我们在操作元素的时候,额外的维护了一个双向链表来保证我们能按顺序地获取和插入元素,而且在迭代的时候实现了自己基于双向链表的跌代器。此外常考的就是实现LRU 算法,这里就是覆写removeEldestEntry()方法的判断逻辑即可。

List 之ArrayList, LinkedList 和 vector
首先这三个都是实现了List 接口,但是他们的不同主要表现在线程安全,扩容等方面。
ArrayList 和vector 底层都是基于数组来实现的,而LinkedList底层是基于双向链表来做的。
ArrayList是线程不安全的,LinkedList也是线程不安全的,vector 是线程安全的,线程安全的操作是在方法前面加入了synchronized 关键字。
ArrayList是1.5倍扩容,LinkedList是双向链表,vector是2倍扩容。
LinkedList提供了index的功能,但是需要额外的开销
vector 提供了更多的元素遍历方法。
Map 之HashMap , ConcurrentHashMap, HashTable
首先,HashMap是线程不安全的,其他的chm和ht是线程安全的。其中,ht的线程安全是基于方法前面直接加synchronized的,也就是会直接将整个map 锁住,所以在数据访问大的情况下,性能会下降。为此,出现了chm,chm提供了cas加synchronized的方式来实现线程安全,在并发访问的时候只会锁住一个桶而已,不同的桶之间的并发完全可以同步进行。

1.7 和 1.8 的HashMap的改变
1.7的HashMap 在发生hash冲突的时候,采用的是单向链表的形式来插入数据的,当碰撞很严重的时候,查询的效率就变成了单向链表的效率。1.8中采用了链表加红黑树的形式来解决hash碰撞,当链表的长度大于8的时候,直接将链表树化成红黑树,这样搜索效率会提高。同时,当红黑树较小的时候,6,会反树化成链表。这是最明显的和1.7的区别
1.7是在插入前进行扩容,而1.8是插入后判断再进行扩容的。
1.7 是采用的头插法,因为JDK1.7是用单链表进行的纵向延伸,但是头插法时会容易出现逆序且环形链表死循环问题;1.8采用的是尾插法,来避免之前提到的各种问题。
扩容后新的位置的计算方式不同 rehash方式详解
1.7 和 1.8 的ConcurrentHashMap
将分段锁替换为cas+synchronized.提高并发粒度
引入了红黑树和链表组合的方式来避免索索性能的退化

全部评论

相关推荐

04-08 13:31
已编辑
门头沟学院 前端工程师
D0cC:京东营收1万多亿人民币,阿里9000多亿,虽然他俩利润都没腾讯和字节多,但是很恐怖了啊,负担了多少打工人的薪水
投递拼多多集团-PDD等公司10个岗位
点赞 评论 收藏
分享
03-26 22:55
门头沟学院 Java
烤冷面在迎接:河南byd,应该就是郑大了。不过24届计算机是特殊情况,那年除了九✌和强2,以及两三个关系够硬的双非,其他的都是炮灰,感觉是十几年来互联网行业最烂的一年,如果想了解最新的就业情况,得找现在的大四。
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客企业服务