基础集合api

1,064 阅读11分钟

本文简单的介绍了我们最常中的集合的组成结构及特性,从以下几个方面展开:

  1. List
  2. Set
  3. Map
  4. Queue

注意在文章中使用实现箭头表示继承关系,虚线箭头表示实现关系:
—>表示继承关系
-->表示实现接口

一、数据结构的复杂度

数据结构的存储也有快慢之分,称之为时间复杂度O(N?),当然数据结构本身自带空间复杂度O(N),这里不做过多介绍,下面两篇文章,有详细介绍时间复杂度

数据结构时间复杂度

数据结构与算法(java)

二、Collection构成

classDiagram
Iterable <|-- Collection

Collection <|-- List
Collection <|.. AbstractCollection
Collection <|-- Set
Collection <|.. ArraySet
 
AbstractCollection <|-- AbstractList
AbstractCollection <|-- AbstractQueue
AbstractCollection <|-- AbstractSet

List <|.. CopyOnWriteArrayList
List <|.. AbstractList
 
AbstractList <|-- ArrayList
AbstractList <|-- Vector
AbstractList <|-- AbstractSequentialList
 
AbstractSequentialList <|-- LinkedList
 
Vector <|-- Stack

Set <|.. AbstractSet
Set <|-- SortSet

AbstractSet <|-- HashSet
AbstractSet <|-- TreeSet
AbstractSet <|-- EnumSet
HashSet <|-- LinkedHashSet
EnumSet <|-- JumboEnumSet

三、List

集合结构  所有的元素都属于一个总体,除了同属于一个集合外没有其他关系。

classDiagram

Collection <|-- List
Collection <|-- Queue

Queue <|-- Deque

RandomAccess <|.. ArrayList
RandomAccess <|.. CopyOnWriteArrayList
RandomAccess <|.. Vector

Cloneable <|.. ArrayList
Cloneable <|.. CopyOnWriteArrayList
Cloneable <|.. Vector
Cloneable <|.. LinkedList
 
Serializable <|.. ArrayList
Serializable <|.. CopyOnWriteArrayList
Serializable <|.. Vector
Serializable <|.. LinkedList

List <|.. ArrayList
List <|.. CopyOnWriteArrayList
List <|.. Vector
List <|.. LinkedList

Deque <|.. LinkedList

Vector\Stack

Vector 内部结构是由数组构成,是线程安全的,addElement()和removeElement都是同步方法。

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, Serializable {
    // 默认初始容量为10,可以自行设置
    protected Object[] elementData;
    protected int capacityIncrement;
    
    // 扩容函数,当capacityIncement = 0,扩容为旧数组的两倍,省略部分代码
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        ……
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    public synchronized E firstElement() {
        if (elementCount == 0) {
            throw new NoSuchElementException();
        }
        return elementData(0);
    }

    public synchronized E lastElement() {
        if (elementCount == 0) {
            throw new NoSuchElementException();
        }
        return elementData(elementCount - 1);
    }
    // 同步方法,线程安全
    public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }
}

Stack 栈继承自Vector,数据结构同Vector一样是数组,那么其也是线程安全的。初始容量和扩容方法同Vector一样。

栈中三个方法重要的方法,FILO(先进后出)的一套结构

public E push(E item) {
    addElement(item);
    return item;
}

public synchronized E pop() {
    E obj;
    int len = size();
    obj = peek();
    removeElementAt(len - 1);
    return obj;
}

public synchronized E peek() {
    int len = size();
    if (len == 0)
        throw new EmptyStackException();
    return elementAt(len - 1);
}
ArrayList
  • 底层以数组存储
  • 创建时,默认赋值elementData空数组
  • 当加入值时,赋值默认初始容量长度为10
  • 扩容算法为 size = size + size >> 1, (即 size = 1.5 * size)
  • size 是指集合中元素的个数,capacity 是指在内存中预先分配一个元素所占内存空间的个数(内存地址是连续的),预备用来存放元素的。
  • 在C语言中,数组名是当作指针来处理的。更确切的说,数组名就是指向数组首元素地址的指针,数组索引就是距数组首元素地址的偏移量。
CopyOnWriteArrayList

其内部数据结构也是数组,和 ReentrantReadWriteLock 读写锁的思想非常类似,也就是 读读共享、写写互斥、读写互斥、写读互斥

  • 和ArrayList不一样的是,COWAL没有默认值,也不存在扩容这么一说
  • 每次增加数据,都是新开辟一个数组,将旧数组copy进入,然后加入新值
  • 每次删除数据,就将旧数组中的其他元素使用Arrays.copyOf到新数组
  • 这样读写完全分开,当同时读写的时候,读的是就数组,写的是新数组

因此会导致如下两个问题:

  • 内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行add()、set()、remove()的话,那是比较耗费内存的。因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。

  • 数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。但是线程A迭代出来的是原有的数据。

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 以前是 ReentrantLock,现在改成对象锁。
    final transient Object lock = new Object();
    public E get(int index) {
        return get(getArray(), index);
    }

    public boolean add(E e) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        }
    }
}
LinkedList
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // 头结点
    transient Node<E> first;
    // 尾结点
    transient Node<E> last;
    // 结点
    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;
        }
    }
    // 获取第一个节点
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    // 获取第一个节点
    public E element() {
        return getFirst();
    }
    // 返回并且删除第一个节点
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    // 插入成为最后一个节点
    public boolean offer(E e) {
        return add(e);
    }
    // 插入成为第一个节点
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }
    // 插入成为最后一个节点
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }
    // 返回第一个节点
    public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
    // 返回最后一个节点
    public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }
    // 返回并删除第一节点
    public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    // 返回并删除最后一个节点
    public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }
    // 插入成为第一个节点
    public void push(E e) {
        addFirst(e);
    }
    // 返回并删除第一个节点
    public E pop() {
        return removeFirst();
    }
    // get(int index)取节点数据,当index大于一般时,从后面开始遍历,否则从前面开始遍历
    Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    // 普通添加链表方法
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
}

从上面的代码可以看出,LinkedList可以实现多种数据结构:

  • 普通的链表
  • 不仅可以实现队列(首尾结点)(pollFirst、add),而且还实现了双端队列(Node结点中前后结点)(pollFirst, push,pollLast,add)
  • 栈 (push,pop)FIFO

四、Set

classDiagram
SortSet <|-- NavigableSet
NavigableSet <|.. TreeSet

Cloneable <|.. HashSet
Cloneable <|.. LinkedHashSet
Cloneable <|.. TreeSet

Serializable <|.. HashSet
Serializable <|.. LinkedHashSet
Serializable <|.. TreeSet

Set <|.. HashSet
Set <|.. LinkedHashSet
Set <|.. ArraySet

简单介绍一下set底层的数据结构。

ArraySet

ArraySet在进行addremove操作时,操作的是int[]类型的mHashesObject[]类型的mArray,其中mHashes保存mArray每个元素的hash值,且mHashesmArray相同下标的元素一一对应。也就是说其有两个数组,一个是用来保存obj的hashcode位置,另一数组对应位置则存放 obj。

其中使用 开放定址法来解决散列冲突,先向后搜索,搜索不到再向前搜索。ArraySet中还包含有大量的知识点,包括扩容、缓存(freeArrays、allocArrays),可以查看下边的文章。 ArraySet 添加和删除元素分析

HashSet
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    private transient HashMap<E,Object> map;
    private static final Object PRESENT = new Object();

    public HashSet() {
        map = new HashMap<>();
    }

    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

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

    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

从上面简化的代码,可以看出HashSet是用HashMap或者LinkedHashMap存储值的。key是我们存的值,value是同一个object,用map.put(key,value)返回旧值来判断是否是重复数据。

五、Map

classDiagram
Map <|-- ConcurrentMap
Map <|.. AbstractMap
Map <|-- SortMap
Map <|.. Dictionary
Map <|.. ArrayMap

ConcurrentMap <|-- ConcurrentHashMap
ConcurrentMap <|-- LocalCache

AbstractMap <|-- ConcurrentHashMap
AbstractMap <|-- LocalCache
AbstractMap <|-- HashMap
AbstractMap <|-- WeakHashMap
AbstractMap <|-- IdentityHashMap
AbstractMap <|-- EnumMap
AbstractMap <|-- TreeMap

Dictionary <|-- HashTable

HashMap <|-- LinkedHashMap

SortMap <|-- NavigableMap
NavigableMap <|.. TreeMap
classDiagram
Map <|.. HashMap
Map <|.. WeakHashMap
Map <|.. LinkedHashMap
Map <|.. IdentityHashMap
Map <|.. HashTable

Cloneable <|.. HashMap
Cloneable <|.. HashTable
Cloneable <|.. TreeMap
Cloneable <|.. IdentityHashMap
Cloneable <|.. EnumMap

Serializable <|.. HashMap
Serializable <|.. HashTable
Serializable <|.. TreeMap
Serializable <|.. IdentityHashMap
Serializable <|.. EnumMap
HashMap
  • tableSizeFor(initialCapacity),将传入初始值改为>=initialCapacity的2^n值
  • 没有传initialCapacity值时,默认容量为16,负载因子为0.75
  • 调整数组大小(当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n)
  • 调用 hash(K) 方法计算 K 的 hash 值,
  • 当碰撞导致链表大于 TREEIFY_THRESHOLD = 8 时,就把链表转换成红黑树,当链表数量减小到6时,将红黑树转换成链表。
  • HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
WeakHashMap

WeakHashMap,是用来存放可以被回收的数据,Entry节点用于存放k和v,继承自WeakReference,可以在GC时被回收。

其内部有ReferenceQueue用来判断被回收的数据。

默认值和扩容等和HashMap相似,只是没有红黑树,只有链表。

LinkedHashMap
  • 继承自HashMap,存取数据和HashMap是一样的。
  • accessOrder,表示按照访问顺序还是插入顺序,默认为false,按照插入顺序访问。

HashMap

void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

LinkedHashMap

static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
    LinkedHashMapEntry<K,V> before, after;
    ……
}
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMapEntry<K,V> p =
        new LinkedHashMapEntry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMapEntry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<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;
    }
}

在put时,会在完成插入后调用 afterNodeAccess(),然后连接前后结点,这样在LinkedHashMap中不仅采用原HashMap存储数据的方式,还讲整个数据组成了一个链表,这样既能平衡查找,又能使用链表功能。

LruCache
public class LruCache<K, V> {
    @UnsupportedAppUsage
    private final LinkedHashMap<K, V> map;

    private int size;
    private int maxSize;
    // 必须给定个数
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        // 按照访问顺序排版数据
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    // 调整容量,去除超出容量的数据。
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

    public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }
        V mapValue;
        // 获取安全,代码块
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }
        // 需继承自己重写
        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

    public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
                // 数据超过了容量
                if (size <= maxSize) {
                    break;
                }
                // 头节点,需要被删除
                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                // 
                map.remove(key);
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }
}
IdentityHashMap

HashMap

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

IdentityHashMap

private static int hash(Object x, int length) {
    int h = System.identityHashCode(x);
    // Multiply by -127, and left-shift to use least bit as part of hash
    return ((h << 1) - (h << 8)) & (length - 1);
}

两个计算hash值不同。

HashTable

Hashtable 也是用数组数组来存节点的,只不过查找数组index = (hash & 0x7FFFFFFF) % tab.length

Hashtable不同于HashMap,前者既不允许key为null,也不允许value为null;

HashMap中用于定位桶位的Key的hash的计算过程要比Hashtable复杂一点,没有Hashtable如此简单、直接;

在HashMap的插入K/V对的过程中,总是先插入后检查是否需要扩容;而Hashtable则是先检查是否需要扩容后插入;

Hashtable不同于HashMap,前者的put操作是线程安全的。

(1). HashMap和Hashtable的实现模板不同:虽然二者都实现了Map接口,但HashTable继承于Dictionary类,而HashMap是继承于AbstractMap。Dictionary是是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的实现,它以最大限度地减少实现此接口所需的工作。

  (2). HashMap和Hashtable对键值的限制不同:HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。

  (3). HashMap和Hashtable的线程安全性不同:Hashtable的方法是同步的,实现线程安全的Map;而HashMap的方法不是同步的,是Map的非线程安全实现。

  (4). HashMap和Hashtable的地位不同:在并发环境下,Hashtable虽然是线程安全的,但是我们一般不推荐使用它,因为有比它更高效、更好的选择ConcurrentHashMap;而单线程环境下,HashMap拥有比Hashtable更高的效率(Hashtable的操作都是同步的,导致效率低下),所以更没必要选择它了。

ArrayMap

ArrayMap在进行addremove操作时,操作的是int[]类型的mHashesObject[]类型的mArray,其中mHashes保存mArray每个key元素的hash值。也就是说其有两个数组,一个是用来保存key的hashcode位置,另一数组相对应位置则存放 节点(key,value)。

mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;

mArray数组的长度是mHashes长度的两倍,扩容规则如下:

  • 当map个数满足条件 osize<4时,则扩容后的大小为4;
  • 当map个数满足条件 4<= osize < 8时,则扩容后的大小为8;
  • 当map个数满足条件 osize>=8时,则扩容后的大小为原来的1.5倍;

其中使用 开放定址法来解决散列冲突,先向后搜索,搜索不到再向前搜索。ArraySet中还包含有大量的知识点,包括扩容、缓存(freeArrays、allocArrays),可以查看下边的文章。

深度解读ArrayMap优势与缺陷

TreeMap

是一棵红黑树:

  1. 每个节点都只能是红色或者黑色
  2. 根节点是黑色
  3. 每个叶节点(NIL节点,空节点)是黑色的。
  4. 如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

根据用户提供的comparator进行比较,也需要key值实现了Comparable,进行插值比较。

public V put(K key, V value) {
    ……
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    fixAfterInsertion(e);
    ……
}

先插值,然后进行在进行旋转和着色。插入的节点默认颜色是红色。

blog.csdn.net/chenssy/art…

六、Queue

classDiagram

Collection <|.. AbstractCollection
Collection <|-- Queue

AbstractCollection <|-- AbstractQueue
AbstractCollection <|-- ConcurrentLinkedDeque
AbstractCollection <|-- ArrayDeque

BlockingQueue <|-- SynchronousQueue
BlockingQueue <|-- ArrayBlockingQueue
BlockingQueue <|-- LinkedBlockingQueue
BlockingQueue <|-- PriorityBlockingQueue

AbstractQueue <-- SynchronousQueue
AbstractQueue <-- ArrayBlockingQueue
AbstractQueue <-- LinkedBlockingQueue
AbstractQueue <-- PriorityBlockingQueue
AbstractQueue <-- PriorityQueue

Queue <|.. AbstractQueue
Queue <|-- Deque

Deque <|.. LinkedList
Deque <|.. ArrayDeque
Deque <|.. ConcurrentLinkedDeque

image.png 图片出自【细谈Java并发】谈谈LinkedBlockingQueue

ArrayDeque

数组形式实现的双端队列,有以下几个特点:

  • 初始容量为16
  • doubleCapacity,顾名思义每次扩容是原先的两倍
  • allocateElements,初始化容量,会将initialCapacity转换成 > numElements的2^n的数,这一点和hashmap的相似又有区别。
  • 双端队列也可以当做栈使用,也有push和pop等方法。
ConcurrentLinkedDeque

ConcurrentLinkedDeque使用了自旋+CAS的非阻塞算法来保证线程并发访问时的数据一致性。由于队列本身是一种双链表结构,所以虽然算法看起来很简单,但其实需要考虑各种并发的情况,实现复杂度较高,并且ConcurrentLinkedDeque不具备实时的数据一致性,实际运用中,如果需要一种线程安全的栈结构,可以使用ConcurrentLinkedDeque。

www.lanxinbase.com/?p=2037

PriorityQueue
  • 底层结构使用数组来保存数据
  • 初始默认容量是11
  • 先扩容,然后再入值 oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1))
  • 权限是通过比较传入的Comparator或者是值本身实现了Comparable接口
  • 是通过小顶堆实现的

父子节点的编号之间有如下关系:

leftNo = parentNo*2+1

rightNo = parentNo*2+2

parentNo = (nodeNo-1)/2

构建小顶堆,插入数据上浮

@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (key.compareTo((E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

删除数据,下沉

// k = 0; x 数组中倒数第一个数。
@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        //首先找到左右孩子中较小的那个,记录到c里,并用child记录其下标
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        // 右下标小于size,并且左子节点大于右子节点
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        if (key.compareTo((E) c) <= 0)
            break;
        queue[k] = c; //然后用c取代原来的值
        k = child;
    }
    queue[k] = key;
}

二叉堆就是一颗二叉树,是一颗完全二叉树,最直观表现一个二叉树左边最多比右边深 1 层,二叉堆我们常常讨论的就是大顶堆和小顶堆,其中大顶堆根结点最大,左右节点依次递归,小顶堆类似

二叉堆算是一种比较重要的数据结构,实际中我们的堆排序就涉及到二叉堆,它也是优先级队列的基础

完全二叉树 遵循一定的大小规律 自行调整的特性

blog.csdn.net/u010623927/…

blog.csdn.net/u011240877/…

阻塞队列

在线程池中有应用。

www.cnblogs.com/tjudzj/p/44…

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 当队列中没有消息则会等待nanos时长,否则立即取消息。
        while (count == 0) {
            if (nanos <= 0L)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    if (count.get() == capacity)
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);
    // 插入锁
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}
private void signalNotEmpty() {
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 释放锁
        notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
}

总结

这是我在准备面试过程中总结的集合资料,简单的介绍了各种数据结构的基本组成结构。所有的数据结构都是数组和链表的,数据结构只是在这两个结构上扩展的。

其中有一次面试官问我,ArrayList实现的接口,那么要知道面试官考察的是你对ArrayList的掌握程度,ArrayList实现了Cloneable、Serializable、RandomAccess和List,那么从这几个点来看ArrayList可以被序列化、clone和产生随机数等几个特性。

所有的集合都实现了Serializable,除了BlockingQueue其他集合都实现了Cloneable。在学习数据结构中我们不仅要学习其数据结构组成原理,还要学习其继承类和实现接口,这样才能更好地了解数据结构的特性。

这个总结还会持续不断更新。