各数据结构汇总

84 阅读10分钟

image.png

image.png

Collecttion和Map之间不存在任何关系。

image.png

1,HashMap

HashMap不安全的原因JDK7HashMap不安全原因 常见的HashMap面试题

-   JDK1.7Hash数组+链表
-   JDK1.8Hsah数组+链表+红黑树

transient Node<K,V>[] table; 

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

//红黑树
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }
}

HashMap 1.71.8除了不同:
- 1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙地采用和扩容后容量进行&操作来计算新的索引位置。
- 1.7是采用表头插入法插入链表,1.8采用的是尾部插入法。
- 在1.7中采用表头插入法,在扩容时会改变链表中元素原本的顺序,以至于在并发场景下导致链表成环的问题;在1.8中采用尾部插入法,
  在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

2,LinkedHashMap

LinkedHashMap的底层分析 LinkedHashMap 是 HashMap 的子类,在 HashMap 的基础上,对于每一个出现的节点 Node e,用双向链表来连接。可以理解为 LinkedHashMap = HashMap + LinkedList

下方为一个 LinkedHashMap,由 head 到 tail 的顺序是 table[0] -> a -> table[3] -> c ->b。

image.png

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {


    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);
        }
    }
    
    //Entry 头节点
    transient LinkedHashMap.Entry<K,V> head;

    //Entry 尾结点
    transient LinkedHashMap.Entry<K,V> tail;
    
    //accessOrder 控制链表排序
    final boolean accessOrder;

    
}

3,ConcurrentHashMap

image.png

image.png ConcurrentHashMap初始化操作 image.png

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable {
 
  	static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }
    }
    //hash表,装载Node数组,数据容器,在第一次put时初始化,大小始终是2的幂
    transient volatile Node<K,V>[] table;

    //扩容时使用,平时为 null,只有在扩容的时候才为非 null
    private transient volatile Node<K,V>[] nextTable;

    /**
     *  计数器,baseCount+CounterCell[] 记录容器的大小;
     *  size()方法会使用到
     */
    private transient volatile long baseCount;

    //数组容量
    private transient volatile int sizeCtl;

    //调整大小时要拆分的下一个表索引(加一)
    private transient volatile int transferIndex;

    //创建CounterCells时使用,0:空间,1:表示在忙
    private transient volatile int cellsBusy;

  
    //计数器单元格表。大小是2的幂,结合baseCount使用
    private transient volatile CounterCell[] counterCells;
}

4,TreeMap

image.png


红黑树的特点:
1.  节点分为红色或者黑色;
2.  根节点必为黑色;
3.  叶子节点都为黑色,且为null;
4.  连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点);
5.  从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点;
6.  新加入到红黑树的节点为红色节点。

image.png

TreeMap的底层时红黑树的结构。
TreeMapHashMap多了一个排序的功能。

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;

    ...
}

`TreeMap` 提供了四个构造方法,实现了方法的重载,无参构造方法中比较器的值为null,
则采用自然排序的方法,如果指定了比较轻则称之为定制排序。

- 自然排序:TreeMap 的所有 key 必须实现Comparable 接口,所有的 key 都是同一个类的对象;
- 定制排序:创建 TreeMap 对象传入一个 COmparable 对象,
    该对象负责对 TreeMap 中所有的 key 进行排序,采用定制排序不要求 key 实现 Comparable 接口。


   public TreeMap() {
        comparator = null;
    }
    
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
    
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }
    
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 保存ArrayList中数据的数组
    transient Object[] elementData; // non-private to simplify nested class access
    
    //数量
    private int size;
}

在进行查询操作比较多,添加删除操作比较的少的程序中,建议使用 ArrayList ;
在进行添加删除的操作比较多,查询比较少的程序中,建议使用 LinkedList ;

LinkedList

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    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;
        }
    }
    
    //List接口
    public boolean add(E e)..  //将元素添加到链表尾部
    public void add(int index, E element).. //在指定位置添加元素
    public E get(int index)  //查询指定位置元素并返回
    public E remove(int index) //移除指定位置的元素
    public void clear() // 清空链表
    
    //Deque接口
    public void addFirst(E e) //在链表头插入指定元素
    public void addLast(E e) //在链表尾部添加元素e
    public void push(E e) {addFirst(e)} //往链表头部添加元素e
    public E getFirst() //得到头元素
    public E getLast() //得到尾部元素
    public E peek() //返回头元素,并且不删除。如果不存在则返回null
    public E peekFirst() //返回头元素,并且不删除。如果不存在则返回null
    public E peekLast() //返回尾元素,并且不删除。如果不存在则返回null
    public E poll() { //返回头节点元素,并删除头节点。并将下一个节点设为头节点
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    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 E pop() { //删除头节点,如果头结点为null.则抛出异常
        return removeFirst();
    }
    
}

采用LinkedList可以实现LRU缓存淘汰算法:
我们维护一个有序单链表,越靠近链表尾部的节点是越早之前访问的。当有一个新的数据被访问时,我们从链表头开始
顺序遍历链表。
1,如果此数据之前已经缓存存在链表中了,我们遍历得到这个数据对应的节点,并将其在原位置删除,然后插入到链表的头部。
2,如果此数据没有存在缓存链表中,分两个情况:
  1),如果此时缓存未满,则将此节点直接插入到链表的头部。
  2),如果此时缓存已满,则链表尾节点删除,将新的数据节点插入到链表的头部。


Vector

线程安全

public class Vector<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    protected Object[] elementData;

    protected int elementCount;

    protected int capacityIncrement;

    public synchronized boolean add(E e)
    public synchronized void insertElementAt(E obj, int index)
    public synchronized E remove(int index)
}

image.png

HashSet

JDK1.7 数组+链表
JDK1.8 数组+链表+红黑树

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<>();  
    }  
    //将传入的集合添加到HashSet的构造器  
    public HashSet(Collection<? extends E> c) {  
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 116));  
        addAll(c);  
    }  
    //明确初始容量和装载因子的构造器  
    public HashSet(int initialCapacity, float loadFactor) {  
        map = new HashMap<>(initialCapacity, loadFactor);  
    }  
    //仅明确初始容量的构造器(装载因子默认0.75)  
    public HashSet(int initialCapacity) {  
        map = new HashMap<>(initialCapacity);  
    }
    
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;  //存的是数据放着Map中的key里,值时一个默认的new Object()
    }
    
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
    
    public Iterator<E> iterator() {
        return map.keySet().iterator();   //迭代的是map的key
    }
    

}
就是说HashSet只用到了HashMap的key,也就是说HashMap的key拥有哪些特性HashSet就拥有哪些特性。
所以HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
HashSet 是无序的,线程不安全。

如果想使HashSet安全,可以用以下方法进行包装:
Set s = Collections.synchronizedSet(new HashSet(...));

LinkedHashSet

image.png

public class LinkedHashSet<E> extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    
    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    public LinkedHashSet() {
        super(16, .75f, true);
    }
     
}
其中的 super(initialCapacity, loadFactor, true); 链接的是HashSet的以下方法:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
所以,LinkedHashSet就是基于LinkedHashMap的。

TreeSet

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable
{

    private transient NavigableMap<E,Object> m; //底层实现就是TreeMap

    private static final Object PRESENT = new Object(); //一个固定的虚拟值,TreeSet只用到了TreeMap的key
    
    	TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }
    
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
    }
    public E first() {
        return m.firstKey();
    }
    public E last() {
        return m.lastKey();
    }
    
}

备注:
public interface NavigableMap<K,V> extends SortedMap<K,V> {..}
public interface SortedMap<K,V> extends Map<K,V> {..}


Queue

image.png

队列常用方法:
    add       增加一个元索                如果队列已满,则抛出一个IIIegaISlabEepeplian异常
  remove     移除并返回队列头部的元素      如果队列为空,则抛出一个NoSuchElementException异常
  element    返回队列头部的元素           如果队列为空,则抛出一个NoSuchElementException异常
  offer      添加一个元素并返回true       如果队列已满,则返回false
  poll       移除并返问队列头部的元素      如果队列为空,则返回null
  peek       返回队列头部的元素           如果队列为空,则返回null
  put        添加一个元素                如果队列满,则阻塞
  take       移除并返回队列头部的元素      如果队列为空,则阻塞
阻塞和非阻塞
阻塞队列
      入列(添加元素)时,如果元素数量超过队列总数,会进行等待(阻塞),待队列的中的元素出列后,元素数量未超过队列总数时,就会解除阻塞状态,进而可以继续入列;
      出列(删除元素)时,如果队列为空的情况下,也会进行等待(阻塞),待队列有值的时候即会解除阻塞状态,进而继续出列;
      阻塞队列的好处是可以防止队列容器溢出;只要满了就会进行阻塞等待;也就不存在溢出的情况;
      只要是阻塞队列,都是线程安全的;
          

非阻塞队列
      不管出列还是入列,都不会进行阻塞,
      入列时,如果元素数量超过队列总数,则会抛出异常,
      出列时,如果队列为空,则取出空值;

一般情况下,非阻塞式队列使用的比较少,一般都用阻塞式的对象比较多;阻塞和非阻塞队列在使用上的最大区别就是阻塞队列提供了以下2个方法:
     出队阻塞方法 : take()
     入队阻塞方法 : put()
 

有界和无界
    有界:有界限,大小长度受限制
    无界:无限大小,其实说是无限大小,其实是有界限的,只不过超过界限时就会进行扩容,就行ArrayList 一样,在内部动态扩容

非阻塞队列

1、ConcurrentLinkedQueue
  单向链表结构的无界并发队列, 非阻塞队列,由CAS实现线程安全,内部基于节点实现

2、ConcurrentLinkedDeque  
  双向链表结构的无界并发队列, 非阻塞队列,由CAS实现线程安全    

3、PriorityQueue
内部基于数组实现,线程不安全的队列

阻塞队列

1,ArrayBlockingQueue:基于数组的阻塞队列实现。在ArrayBlockingQueue内部,维护了一个定长的数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费者不能完全并行。
 长度是需要定义的,可以指定先进先出或者先进后出,因为长度是需要定义的,所以也叫有界队列,在很多场合非常适合使用。

2,LinkedBlockingQueue:基于链表的阻塞队列。同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),LinkedBlockingQueue之所以能够高效地处理并发数据,
 是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作完全并行运行。需要注意一下,它是一个​无界队列​。

3,SynchronousQueue:一种没有缓冲的队列。生产者产生的数据直接会被消费者获取并且立刻消费。

4,PriorityBlockingQueue:基于优先级别的阻塞队列。(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口),
   在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁,需要注意的是它也是一个无界的队列。

5,DelayQueue:带有延迟时间的Queue。其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须先实现Delayed接口,
   DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。

双端队列

ArrayDeque

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable
{
    transient Object[] elements; // non-private to simplify nested class access

    transient int head;

    transient int tail;
    
    ...
}