Java集合类

Featured image

两个终极接口Collection和Map;
Collection下面主要是List、Set、Queue

List

继承关系如下:
List继承关系

ListIterator相比iterator增加了:add、set、nextIndex、previousIndex以及前向访问相关方法。

ArrayList: 使用数组存储transient Object[] elementData;,当List为空时,默认共享同一个静态变量。当第一个元素加入到List时,数组默认大小是10,当插入元素时如果发现空间不够,首先扩展为原来的三倍,如果还是不够,则需要多少就创建多大的数组。最大是Interger.MAX_VALUE;由于扩容造成的内存浪费,可以使用trimToSize方法解决
ArrayList并非是线程安全的。

Vector: 使用数组存储,与ArrayList的主要区别在于 很多方法是同步实现的。

Stack: 基于Vevtor实现栈结构。很多方法也是线程安全的。提供了search方法,可以返回被搜索元素到栈顶的距离。

LinkedList: 元素以Node的方式存储,只记录头结点和尾结点,其余结点通过Node之间的指针关联。Node定义如下:

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

LinkedList实现了双向队列接口

Map

继承关系如下:
Map继承关系
备注:图中缺少了CorrentHashMap
可以认为Map就是元素为Entry的集合,keySet和valueSet只不过是其另外的展示形式。
HashMap:采用数组+链表+红黑树实现。首先是以数组的形式存储Node,数组的扩容根据负载因子(数组的使用率),默认当使用率超过0.75就扩容为原来的2倍。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        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;
        }
    }
transient Node<K,V>[] table;  

当插入元素的时候,先根据key找到该元素在数组中应该对应的位置,如果该位置为null,则直接将新的Node放到该位置。否则判断该位置是TreeNode还是链表Node,如果是TreeNode,则加入到红黑树中,如果是链表Node,则放到链表最后。加入到链表最后时,检查是否超过“链表转树的阈值”,如果超过则转为红黑树。数组的该位置应该是红黑树的根。

if ((p = tab[i = (n - 1) & hash]) == null) //hash 是根据key得来的
    tab[i] = newNode(hash, key, value, null);
...
else if (p instanceof TreeNode)
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
...

LinkedHashMap:是HashMap的子类,与HashMap的主要区别是可保证有序迭代,迭代的顺序是key插入的顺序。主要是自己实现了Entity类,可以认为是一个双向链表的Node。数据存储上应该是跟HashMap一样的。

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);
        }
    }

TreeMap:使用红黑树存储数据。

    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;
        ...

WeakHashMap:使用数组加单向链表存储,没有使用红黑树。与其它Map的主要区别是Entity定义时继承了WeakReference类。

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
...

个人理解:对于其他Map,如果将K,V放入到其他Map中,只要这个Map不被回收,那么这个K,V便不会被回收,即使在其他任何地方都没有引用。但是如果放到WeakHashMap中,通过K构造的是弱引用的Entity,super(key, queue);,当K在外部没有引用时,这时候Map的Entity是可回收的。这种特性一般用于缓存场景。

关于弱引用:弱引用在垃圾回收时会被回收,无论外部是否还有引用。 弱引用可以和队列联合使用,当弱引用被垃圾回收后,通过弱引用的get方法会返回null值。在和一个队列关联的情况下,那么这个对象就会被放到引用队列中,后续可能需要自己去处理这个队列。WeakHashMap就是保存了这样一个队列,某些时刻就会从map中清除队列中的元素。

HashTable:不允许null作为key或者value。通过数组加链表实现。最大的特点就是很多方法都使用了同步。

ConcurrentHashMap: 利用分段锁更高效的实现了线程安全。具体细节:??

IdentityHashMap:最大的特点是在判断key和value是否相等的时候,使用的是‘==’而不是‘equal’;在一些涉及到拓扑关系的应用中较多。使用数组实现,第i个存储key第i+1个存储value。

Set

继承关系如下:
Set继承关系
HashSet:使用hashMap存储数据,提供了构造方法,可将hashMap换成LinkedHashMap,但是外部不可以调用,貌似专门为LinkedHashSet设计的。

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

TreeSet:默认使用treeMap存储数据,可定制传入实现NavigableMap接口的对象即可。
LinkedHashSet:HashSet的子类,使用LinkedHashMap存储。

Queue

继承关系如下:
Queue继承关系

Queue接口如下:

方法名 操作 备注
add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常
offer 添加一个元素并返回true 如果队列已满,则返回false
poll 移除并返问队列头部的元素 如果队列为空,则返回null
peek 返回队列头部的元素 如果队列为空,则返回null

阻塞队列的方法

方法名 操作 备注
put 添加一个元素 如果队列满,则阻塞
take 移除并返回队列头部的元素 如果队列为空,则阻塞

Deque: 双端队列,可以先进先出,也可以后进先出。

PriorityQueue: 队列元素时有序的,无容量限制,基于有序堆实现(小顶堆,平衡二叉堆)。这里的堆采用的是数组结构存储。插入和删除的时间复杂度是O(log(n)).需要注意的是iterate是继承自collection的,所以使用iterate遍历并不能保证有序遍历。不允许有NULL元素。

ConcurrentLinkedQueue:无界,线程安全的Queue,先进先出。基于单向链表实现。

ArrayBlockingQueue:有容量限制,使用固定大小的数组存储队列元素。在内部记录了下一个要写入元素和取出元素的索引,所以实际上是一个环操作,并不存在移动元素。并且一旦创建队列;队列的容量就不可以再改变。并且提供了一种公平的机制可以对生产者和消费者进行排序。这种公平机制通过ReentrantLock来实现。

LinkedBlockingQueue:基于单向链表实现,无容量限制。

PriorityBlockingQueue:与PriorityQueue区别在于插入和读取元素时,如果队列是满的或者空的,可以阻塞。

DelayQueue:实现了BlockingQueue接口,无界队列,使用PriorityQueue存储数据;所放置的对象必须实现delay接口。队列是有序的。

SynChronousQueue:自身不会存储数据,每次插入数据都会阻塞,直到其他线程取数据。

从代码来分析,BlockingQueue 是线程安全的,在插入和读取元素时都加了锁。

面试

1.HashMap 和 HashTable的区别
存储层面,HashMap采用数组+链表+红黑树实现,而HashTable是数组+链表实现 HashTable是线程安全的,通过synchronized实现;
HashTable不允许null作为key或者value,hashMap允许null作为key/value;理由是多线程条件下,get方法返回null时不确定是该key不存在,还是key对应的值是null;hashMap在单线程条件下,可以用containsKey判断是否存在,而hashTable多线程条件下,containsKey与get不能保证原子性,所以containsKey的结果不能用于get返回的判断。