Java常用集合简析

在Java中常用到的集合如ArrayList,HashMap等,本文针对这些集合做一个简单的总结和分析。

ArrayLIst:基于动态数组的集合,初始大小为10,如果添加元素时长度不够就会扩容。因为是基于数组,所以查询及顺序插入快。而指定位置的插入删除和修改则较慢,且线程不安全。
扩容代码:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // >>1表示除以2,即取原长度的一半
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 扩容后长度不够则直接取添加元素后的长度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 长度大于最大长度,则返回Integer的最大值0x7fffffff
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

ArrayList实现线程安全的方法:
1.使用Collections.synchronizedList(List<T> list)
2.使用Vector

LinkedList:基于双向链表,因此插入,删除较快,查询及顺序插入较慢。且线程不安全。

HashMap:基于数组+链表,用键值对的形式存储元素。具有上述两个集合的优点,线程不安全。(在Java1.8中加入了红黑树优化链表的遍历效率问题)
HashMap相对于上述两个集合优势明显,自然它的实现也较复杂一些。HashMap实际上还是一个数组,不过数组的每一个元素都是一个Node,这是继承了Map.Entry接口的一个内部类。

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;// hash值
        // key-value  键值对的值
        final K key;
        V value;
        // 单向链表的实现
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

这个内部类实际上就是一个单向链表的实现。也就解释了为什么HashMap是一个数组+链表的结构。
然后让我们看看HashMap的put过程:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 非空判断 resize()在这里的作用是重新定义数组,也是hashmap的扩容方法
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 通过Hash值从数组取值,若不存在则直接将该key-value塞入对应下标内
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        // 存在的话 则加入链表头部,即取代原位置所在的Node,Java1.8中若长度大于8则把链表转为树
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 链表长度大于8  转为树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 容量不够 扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

上面这段代码就是HashMapput(key,value)中调用的方法。
首先是判断数组是否为空,然后判断对应数组下标是否有值。没有则直接插入就行了。如果有值则要判断是否同一个key。因为这个下标位置是根据hash值判定的,hash值存在hash碰撞的可能。也就是不同的key在同一个下表位置。所以这也是为什么HashMap是数组+链表的结构。
有值的情况操作就比较复杂。首先判断是否有一样的key,一样则取代就行。没有则需要往链表中插入。上面注释也提到,在Java1.8中,如果链表长度超过了8,则会转化为树结构。

// 链表转为树的源码
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

思路比较简单,但是源码中做了很多判断和操作。有些还没理解,就不在这里详述以免误导大家。

猜你喜欢

转载自blog.csdn.net/qq_19656425/article/details/81141305