Java集合知识汇总
一、List(列表)——线程不安全的数据结构
1.1、List数组(ArrayList)
底层:一个可动态扩容的数组,与普通数组的区别就是它是没有固定大小的限制
特点:顺序存储,读速度、更新快,增删慢;内存相邻,根据Index读取的时间复杂度是O(1);可以存储重复元素,但线程不安全
扩容机制:如果当前元素放不下,则扩容至1.5倍,且扩容空间大于等于1
1.2、List链表(LinkedList)
底层:节点,存下数据的引用对象(非数据),节点之间使用引用相关联
特点:顺序存储,实现了Queue、Deque接口,可作为队列使用;查找慢,增删快,可以存储重复元素,但线程不安全
扩容机制:无固定大小,不需要扩容
Api:
public E getLast() //获取最后一个元素
public E removeFirst() // 移除第一个元素,并返回
public E removeLast() // 移除最后一个元素,并返回
public void addFirst(E e) //加入头部
public void addLast(E e) //加入尾部
public void add(E e) //加入尾部
public boolean contains(Object o) //是否包含 元素 o
public E peek() //获取头部第一个元素
public E element() // 获取头部第一个元素,不存在则报错
public E poll() //获取头部第一个元素,并移除
public boolean offer(E e) // 调用 add(E e)
public boolean offerFirst(E e) // 调用 addFirst
public boolean offerLast(E e) // 调用 addLast
public void push(E e) //在头部压入一个元素
public E pop() //弹出第一个元素,并移除。不存在则报错
1.3、List总结
ArrayList和LinkedList:
共同点:顺序存储,可以存储重复元素,线程不安全
不同点:ArrayList底层数组,内存相邻,因此擅长读改,LinkedList底层节点之间的使用引用相关联,因此擅长增删
Iterator 和 fast-fail、fail-safe机制:
-
Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 List 和 Set 等集合,主要有hashNext(),next(),remove()三种方法
-
fail-fast 是Java集合(Collection)的一种错误机制。当多个线程对同一个集合进行修改结构操作,使用集合的迭代器iterator,会首先检测是否有对集合的并发修改,进而产生ConcurrentModificationException 异常提示
-
fail-safe:保证在对任何集合结构的修改操作都基于先复制再修改 进行的,即先copy一个新的集合对象,然后对新的集合对象进行修改,最后将新的集合对象替换掉老的集合对象(老的集合对象的地址指向新的集合对象)。java.util.concurrent包下采用的是fail-safe机制
-
- 缺点1:对集合的复制copy会产生大量的对象,造成内存空间的浪费
- 缺点2:无法保证集合迭代过程中获取的集合数据是最新的内容
如何保证线程安全呢?
- plan_A:使用 Vector
- plan_B:使用 Collections.synchronized() 返回线程安全的 List
- plan_C:使用 CopyOnWriteArrayList
Collections.synchronized()举例::
List<String> list = Collections.synchronizedList(new ArrayList<String>());
list.add("1");
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext()) {
System.out.println(i.next());
}
}
Collections.synchronizedList()添加数据不用锁(底层代码已经实现),遍历数据用锁
CopyOnWriteArrayList 的线程安全:
CopyOnWriteArrayList 在写的时候会加锁,为了保证写安全,会在写操作时复制一个新数组来操作,然后覆盖旧的数组,不会影响读的性能
CopyOnWriteArrayList 的缺点:
- CopyOnWrite 在进行写操作的时候,内存里会同时驻扎两个对象的内存,导致内存的浪费
- CopyOnWrite 容器只能保证数据的最终一致性,不能保证数据的实时一致性。如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器,没有阻塞等待的概念
CopyOnWriteArrayList 和 Collections.synchronizedList 区别:
-
CopyOnWriteArrayList 的写操作性能较差,而多线程的读操作性能较好
-
Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了 synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList
List的Api:
boolean contains(Object o) // 是否包含 o
boolean isEmpty(); // 是否为空
int size(); //集合元素
Iterator<E> iterator(); // 返回迭代器
Object[] toArray(); // 转为 Object数组
<T> T[] toArray(T[] a); // 转为具体类型数组
boolean add(E e); // 加入尾部
boolean remove(Object o); // 移除 o
boolean containsAll(Collection<?> c); //是否报考 集合 c
boolean addAll(Collection<? extends E> c);// 合并 c
boolean retainAll(Collection<?> c);//保留只存在集合 c 的元素
void clear(); // 清除集合元素
void sort(Comparator<? super E> c) //根据 Comparator 排序
E get(int index); // 根据下标获取 元素
E set(int index, E element); // 设置第 index 的元素
E remove(int index); // 移除 第 index 的元素
<E> List<E> of(E e1.....) // jdk 9
List<E> copyOf(Collection<? extends E> coll) // 复制
二、Vector(向量)——ArrayList 线程安全的翻版
底层:数组
特点:查询快,线程安全,线程安全
Api:
boolean synchronized contains(Object o);
boolean synchronized isEmpty();
boolean synchronized containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
public synchronized boolean add(E e)
public synchronized E get(int index);
public synchronized E set(int index, E element);
public synchronized E firstElement()
public synchronized void removeElementAt(int index)
public synchronized E lastElement()
public synchronized void setElementAt(E obj, int index)
public synchronized E remove(int index)
public void clear()
Iterator<E> iterator();
三、 Stack(栈)——后进先出的线性表
底层:数组
特点:用于模拟"栈"这种数据结构(LIFO后进先出),线程安全,允许 null 值
Api:
public E push(E item) //推入栈顶
public synchronized E pop() // 弹出栈顶元素,不存在则报错
public synchronized E peek() // 获取栈顶元素,不移除
public boolean empty() // 栈是否为空
public synchronized int search(Object o) // 搜索元素
四、Map——不属于Collection的映射关系
底层:key到value的映射
特点:可以使任何引用类型的数据,但key不能重复,无法遍历( 没有继承 Collection 接口)
4.1、TreeMap(1.8JDK)
底层:红黑树
特点:不允许出现重复的key,可以插入null键,null值,可以对元素进行排序,无序集合(插入和遍历顺序不一致)。
4.2、HashMap
底层:数组+链表+红黑树,空参的HashMap初始容量是16,默认加载因子为0.75。
特点:具有很快的访问速度,最多允许一条记录的键为 null,线程不安全,存储无序。
HashMap如何处理hash冲突:
- 链地址法:如果存在 hash 碰撞,则创建一链表存储相同的元素
- 开放定址法:就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
- 再哈希:同时构造多个不同的哈希函数,当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。
- 建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
4.3、HashTable(基本被淘汰)
特点:HashTable的操作几乎和HashMap一致,主要的区别在于HashTable为了实现多线程安全,在几乎所有的方法上都加上了synchronized锁,而加锁的结果就是HashTable操作的效率十分低下。
HashMap和HashTable区别:
- HashMap允许有一个键为null,允许多个值为null,但HashTable不允许键或值为null。
- Hash映射:HashMap的hash算法通过非常规设计,将底层table长度设计为2的幂,使用位与运算代替取模运算,减少运算消耗;而HashTable的hash算法首先使得hash值小于整型数最大值,再通过取模进行散射运算
4.4、LinkedHashMap
底层:HashMap和双向链表合二为一
特点:LinkedHashMap的元素存取过程基本与HashMap基本类似,只是在细节实现上稍有不同。当然,这是由LinkedHashMap本身的特性所决定的,因为它额外维护了一个双向链表用于保持迭代顺序。此外,LinkedHashMap可以很好的支持LRU算法。
4.5、WeakHashMap
底层:散列表,它存储的内容也是键值对(key-value)映射
特点:键和值都可以是 null,WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个 key 不再被强引用使用时,会被从WeakHashMap中被 JVM 自动移除,然后它对应的键值对也会被从WeakHashMap中移除。
4.6、ConcurrentHashMap(1.8JDK)
底层:底层数据结构是 数组 + 链表/红黑树
特点:ConcurrentHashMap 是 HashMap 的多线程安全版本。它使用了细粒度锁 和 cas 提高了在多线程环境的安全性和高并发
悲观锁与乐观锁:
synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。
CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
4.7、ConcurrentSkipListMap
底层:基于跳跃链表的实现的 map
特点:使用了 cas 技术实现线程安全性,高并发
ConcurrentSkipListMap 相比 ConcurrentHashMap 的优点:
-
ConcurrentSkipListMap 的key是有序的
-
ConcurrentSkipListMap 支持更高的并发。ConcurrentSkipListMap的存取时间是log(N),和线程数几乎无关。也就是说在数据量一定的情况下,并发的线程越多,ConcurrentSkipListMap 越能体现出它的优势
4.8、NavigableMap 和 ConcurrentNavigableMap 操作 key 值的范围区间
原理:
- TreeMap 实现了 NavigableMap 。ConcurrentNavigableMap 高并发线程安全版的 TreeMap
- NavigableMap 提供了针对给定搜索目标返回最接近匹配项的导航方法。
Api:
K lowerKey(K key) // 找到第一个比指定的key小的值
K floorKey(K key) // 找到第一个比指定的key小于或等于的key
K ceilingKey(K key) // 找到第一个大于或等于指定key的值
K higherKey(K key) // 找到第一个大于指定key的值
Map.Entry<K,V> firstEntry() // 获取最小值
Map.Entry<K,V> lastEntry() // 获取最大值
Map.Entry<K,V> pollFirstEntry() // 删除最小的元素
Map.Entry<K,V> pollLastEntry() // 删除最大的元素
NavigableMap<K,V> descendingMap() //返回一个倒序的Map
// 返回值小于 toKey 的 NavigableMap
NavigableMap<K,V> headMap(K toKey, boolean inclusive)
// 返回值大于 fromKey 的 NavigableMap
NavigableMap<K,V> tailMap(K fromKey, boolean inclusive)
// 返回值小于 toKey 大于 的 fromKey NavigableMap
NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
五、Set(集合)——元素唯一的无下标数组
特点:存储顺序无关,元素不可重复,如果加入重复元素,会保留最先加入的对象。存取速度快。但与List不同的是,Set还提供了equals(Object o)和hashCode(),供其子类重写,以实现对集合中插入重复元素的处理。
Api:
方法 | 内容 |
---|---|
boolean add(E o) | 如果 set 中尚未存在指定的元素,则添加此元素(可选操作) |
boolean addAll(Collection<? extends E> c) | 如果 set 中没有指定 collection 中的所有元素,则将其添加到此 set 中(可选操作) |
void clear() | 移除 set 中的所有元素(可选操作) |
boolean contains(Object o) | 如果 set 包含指定的元素,则返回 true |
boolean containsAll(Collection<?> c) | 如果此 set 包含指定 collection 的所有元素,则返回 true |
boolean equals(Object o) | 比较指定对象与此 set 的相等性 |
int hashCode() | 返回 set 的哈希代码值 |
boolean isEmpty() | 如果 set 不包含元素,则返回 true |
Iterator iterator() | 返回在此 set 中的元素上进行迭代的迭代器 |
boolean remove(Object o) | 如果 set 中存在指定的元素,则将其移除(可选操作) |
boolean removeAll(Collection<?> c) | 移除 set 中那些包含在指定 collection 中的元素(可选操作)。 |
boolean retainAll(Collection<?> c) | 仅保留 set 中那些包含在指定 collection 中的元素(可选操作) |
int size() | 返回 set 中的元素数(其容量) |
Object[] toArray() | 返回一个包含 set 中所有元素的数组 |
T[] toArray(T[] a) | 返回一个包含 set 中所有元素的数组;返回数组的运行时类型是指定数组的类型 |
5.1、TreeSet
底层:红黑树
特点:具有排序功能(自然排序(123456)和自定义排序,默认是自然排序),允许插入Null值,不允许插入重复元素,线程不安全。
红黑树: 一种自平衡二叉查找树
5.2、HashSet
底层:哈希表(HashMap)
特点:不允许出现重复因素,允许插入Null值,元素无序(添加顺序和遍历顺序不一致),线程不安全。
基本操作:插入的元素被当做是HashMap的key,根据hashCode值来确定集合中的位置,由于Set集合中并没有角标的概念,所以并没有像List一样提供get()方法。当获取HashSet中某个元素时,只能通过遍历集合的方式进行equals()比较来实现。
5.3、LinkedHashSet
底层:链表和哈希表共同实现
特点:链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性,效率高,但线程不安全。
5.4、ConcurrentSkipListSet
底层:基于 ConcurrentSkipListMap 实现
5.5、CopyOnWriteArraySet
底层:基于 CopyOnWriteArrayList 实现
5.6、BitSet
底层:long数组
特点:BitSet是位操作的对象,值只有 0 或 1 即false和true,内部维护了一个long数组,初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充,最终内部是由N个long来存储。
Api:
void and(BitSet set) // 两个BitSet 做与操作,结果并存入当前 BitSet
void andNot(BitSet set) // 两个BitSet 与非操作
void flip(int index) // 反转某一个指定 index
boolean intersects(BitSet bitSet) // 是否有交集
int cardinality() //返回 true/1 的个数
void clear() // 重置
void clear(int startIndex, int endIndex) // startIndex~endIndex 重置
int nextSetBit(int startIndex) //检索在startIndex之后出现为1的第一位的索引
int nextClearBit(int startIndex) //检索在startIndex之后出现为0的第一位的索引
5.7、TreeSet和HashSet对比
共同点:允许插入Null值,不允许插入重复元素,线程不安全,可以被克隆,可以被序列化。
不同点:TreeSet对插入的元素进行排序,是一个有序的集合,HashSet无序。TreeSet底层使用红黑树,HashSet底层使用HashMap。
六、Queue(队列)——一端进一端出的线性表
底层:基于数组,链表实现
特点:存储有序
Api:
boolean add(E e); //加入队列尾部
boolean offer(E e); // 加入队列尾部,并返回结果
E remove(); //移除头部元素
E poll(); // 获取头部元素,并移除
E element(); // 获取头部元素,不存在则报错
E peek(); // 获取头部元素,不移除
6.1、PriorityQueue
底层:PriorityQueue是按优先级排序的队列,也就是说 vip 可以插队。优先队列要求使用 Java Comparable 和 Comparator 接口给对象排序,并且在排序时会按照优先级处理其中的元素。
6.2、PriorityBlockingQueue
底层:线程安全的PriorityQueue。
6.3、BlockingQueue
底层:BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。常用于线程的任务队列
6.4、DelayQueue
DelayQueue是一个没有边界BlockingQueue实现,加入元素必须实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发 Delayed 接口中的compareTo方法进行排序。消费者线程查看队列头部的元素,注意是查看不是取出。然后调用元素的getDelay方法,如果此方法返回的值小0或者等于0,则消费者线程会从队列中取出此元素,并进行处理。如果getDelay方法返回的值大于0,则消费者线程阻塞到第一元素过期。
七、Deque(双向队列)——两端均可进出的线性表
特点:Deque接口代表一个"双端队列",双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用
**Deque 的子类 **:LinkedList,ArrayDeque,LinkedBlockingDeque
Api:
void addFirst(E e); //加入头部
void addLast(E e); //加入尾部
boolean offerFirst(E e); //加入头部,并返回结果
boolean offerLast(E e); //加入尾部,并返回结果
E removeFirst(); // 移除第一个元素
E removeLast(); // 移除最后一个元素
E getFirst(); //获取第一个元素,不存在则报错
E getLast(); //获取最后一个元素,不存在则报错
E pollFirst(); //获取第一个元素,并移除
E pollLast(); //获取最后一个元素,并移除
E peekFirst(); //获取第一个元素
E peekLast(); // 获取最后一个元素
void push(E e); //加入头部
E pop(); //弹出头部元素
八、总结
增删改查的通用方法:
操作方法 | 抛出异常 | 阻塞线程 | 返回特殊值 | 超时退出 |
---|---|---|---|---|
插入元素 | add(e) | put(e) | offer(e) | offer(e, timeout, unit) |
移除元素 | remove() | take() | poll() | pull(timeout, unit) |
检查 | element() | peek() | 无 | 无 |