list,map,set,queue容器汇总介绍

1.ArrayList(非线程安全)

ArrayList 是一个数组队列,实现了动态数组功能,继承了AbstractList,提供了相关的添加、删除、修改、遍历等功能,可以通过序号随机访问对应数值.对接访问时,效率较高,效率的主要开销在数组数据的预留空间开销.

ArrayList包含了两个重要的对象:elementData 数组和 size。一个用来储存数据,一个用来统计大小.新建一个ArrayList的时候默认数组大小为10,当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”

put方法就是在数组中放入数据,并有上述扩容算法,get方法就是取哪个数组

2.LinkedList(非线程安全)

LinkedList继承了AbstractSequentialList,AbstractSequentialList继承了AbstractList,实现了list接口,实现了Deque 接口,即能将LinkedList当作双端队列使用

 LinkedList双向链表包含两个重要的成员:header 和 size。

  • header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。 
  • size是双向链表中节点的个数。

linkedlist可以作为栈,可以作为数组,调用对应的api即可,但是请不要随机访问的形式去遍历linkedlist,将会很慢.

3.vector(线程安全)

查看源码,vector的实现原理和ArrayList几乎是相同的,vector是java 1的时候产生的接口,官方api提示,不需要线程同步的情况下建议使用arraylist.

那么一定需要线程安全的话改怎么办呢?这种情况也可以通过Collections.synchronizedCollection()获取线程安全列表,其实查看源码可以知道,Collections.synchronizedCollection()只不过是在每个方法上加了synchronized同步代码块,效率其实是和vector一样的,但是出于arraylist是常用列表类,使用简单熟悉,代码可读性高等考据,笔者建议用Collections.synchronizedCollection()方式获取线程安全列表arraylist.

4.HashSet(非线程安全)

实现原理可以参考下边描述的HashMap,因为hashset原码中就是调用的HashMap方法!

5.LinkedHashSet(非线程安全)

LinkedHashSet是set容器中的有序容器,其他的容器(hashset,treeset)都是无序容器:插入顺序和遍历顺序无关.内部使用的是LinkHashMap.

LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但由于是链表结构,所以插入删除,遍历有一定优势

6.TreeSet(非线程安全)

红-黑树的数据结构,原码中实现是用了treeMap,红黑树的特性:

(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

满足这样条件的红黑树放宽了相对于平衡二叉树的限制,添加,删除,查询三次旋转即可完成,时间复杂度均为O(logn),较快.

默认对元素进行自然排序(String)。如果在比较的时候两个对象返回值为0,那么元素重复.返回值小于0,认为新元素小于上个元素,放在树的左边,返回值大于0,新元素大于上一个元素,放在根节点的右边,下面是定义TreeSet的比较规则方法:

比较对象实现Comparable接口,覆盖compareTo 方法:

public class Person implements Comparable<Person> {
    private String name;
    private int age;
    ...
    @Override
    public int compareTo(Person o) {
        int num = this.age - o.age;                //年龄是比较的主要条件
        return num == 0 ? this.name.compareTo(o.name) : num;//姓名是比较的次要条件
    }
}

定义一个类实现Comparator 接口,覆盖compare方法,传递给TreeSet构造函数:

class CompareByLen /*extends Object*/ implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {        //按照字符串的长度比较
        int num = s1.length() - s2.length();        //长度为主要条件
        return num == 0 ? s1.compareTo(s2) : num;    //内容为次要条件
    }
}
//代码中:
TreeSet<String> ts = new TreeSet<>(new CompareByLen());

7.HashMap(非线程安全)

根据散列码(hashcode)查找数据规则:

假如我们有一个数据(散列码76268),而此时的HashSet有128个散列单元,那么这个数据将有可能插入到数组的第108个链表中(76268%128=108)。

Java默认的散列单元大小全部都是2的幂,初始值为16(2的4次幂)。假如16条链表中的75%链接有数据的时候,则认为加载因子达到默认的0.75。HahSet开始重新散列,也就是将原来的散列结构全部抛弃,重新开辟一个散列单元大小为32(2的5次幂)的散列结果,并重新计算各个数据的存储位置。以此类推下去.....

直接根据数据的散列码和散列表的数组大小计算除余后,就得到了所在数组的位置,然后再查找链表中是否有这个数据即可.

这里需要注意:HashSet判断值是否相等会对比hashcode和equals方法,两者都相等才为相等数据.散列值相同的数据会放到一个散列单元中,如果我们设置的多个数据hashcode相同,但equals不相等,HashSet会把这些数据放在同一个散列单元中,这时,数据的查询将会非常缓慢(哈希冲突,链表法解决),禁止这种代码逻辑!

put方法就是tab[]中获得散列单元,散列单元中维持一个链表,插入该链表中,再加上上述扩容问题,

get方法就是获取tab[]中的散列单元,然后查找该key值.

HashSet优点:查找速度很快,是最常用的set容器

8.LinkedHashMap(非线程安全)

LinkedHashMap是HashMap的子类,双向链表结构,能够保证插入元素的顺序。

插入原理基本上和hashmap是一样的,不过在插入完成后,设置好before和after两个属性值和记录header.

迭代的时候主要看一个标志位accessOrder如果accessOrder为true的时候,迭代的时候用的是访问顺序(最近访问的元素放在链表末尾),为false的时候,用插入顺序.

9.TreeMap(非线程安全)

在TreeMap的put()的实现方法中主要分为两个步骤,第一:构建排序二叉树,第二:平衡二叉树(左旋(rotateLeft())、右旋(rotateRight())、着色(setColor()))。

treeMap删除节点(deleteEntry)的步骤是:找到一个替代子节点C来替代P,然后直接删除C,最后调整这棵红黑树。

可以结合上边的TreeSet方法一起来看,两者实现方式和原理是一样的.因为treeSet实际就是调用的TreeMap方法来实现的.

10.ConcurrentHashMap(线程安全)

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

由于细化了锁的粒度,ConcurrentHashMap的执行效率要比HashTable高很多

11.HashTable(线程安全)

HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。

Hashtable是个过时的集合类,如果你使用Java 5或以上的话,请使用ConcurrentHashMap吧

12.queue(线程安全)

 

非阻塞队列:ConcurrentLinkedQueue

它是一个无锁的并发线程安全的队列,适用于高并发场景,性能要比BlockingQueue好,是基于链接节点的无界线程安全队列,先进先出,不允许有null元素.

阻塞队列:BlockingQueue

提供了可阻塞的put和take方法,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞,常用方法:

 可能报异常返回布尔值可能阻塞设定等待时间
入队add(e)offer(e)put(e)offer(e, timeout, unit)
出队remove()poll()take()poll(timeout, unit)
查看element()peek()

ArrayBlockingQueue

在内存中维护一个定长数组,没有实现读写分离,生产和消费不能完全并行,在初始化时需要指定队列大小,可以定义先进先出还是先进后出;

LinkedBlockingQueue

基于链表的阻塞队列,其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能.

作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

DelayQueue

超时取出队列,只有当其指定的延迟时间到了,才能够从队列中获取到该元素。可以用来处理空闲链接的关闭.

PriorityBlockingQueue

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

SynchronousQueue

没有缓冲的队列,生产者数据会直接被消费.

DelayedWorkQueue

队列中放入有延迟的线程队列(一般列有schedule标记),可以根据线程的延时时间获取最靠近当前时间的线程执行,为优先级队列.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值