Collecttion和Map之间不存在任何关系。
1,HashMap
HashMap不安全的原因, JDK7HashMap不安全原因 常见的HashMap面试题
- JDK1.7:Hash数组+链表
- JDK1.8:Hsah数组+链表+红黑树
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.7和1.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。
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
ConcurrentHashMap初始化操作
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
红黑树的特点:
1. 节点分为红色或者黑色;
2. 根节点必为黑色;
3. 叶子节点都为黑色,且为null;
4. 连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点);
5. 从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点;
6. 新加入到红黑树的节点为红色节点。
TreeMap的底层时红黑树的结构。
TreeMap比HashMap多了一个排序的功能。
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)
}
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) + 1, 16));
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
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
队列常用方法:
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;
...
}