谈一谈java中的集合

概述

Map

集合框架的第二类接口树。提供了一组键值的映射。其中存储的每个对象都有一个相应的关键字(key),关键字决定了对象在Map中的存储位置。
关键字应该是唯一的,每个key 只能映射一个value。


HashMap

HashMap是基于哈希表实现Map接口的双列集合,存储键值对,key不能重复,但是value可以重复;允许null的键或值;
是无序的(即不能按照添加顺序迭代),线程不安全的,效率高,1.8之后底层采用数组+链表+红黑树实现哈希表:


LinkedHashMap

是HashMap的子类,保证在遍历map元素时,可以按照添加的顺序实现遍历,对于频繁的遍历操作,它的执行效率高于HashMap.
原因:在原有的HashMap底层结构的基础上,添加了一对指针,指向前一个和后一个元素,能维护添加顺序
LinkedHashMap中的内部类:Entry

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

TreeMap

TreeMap是非线程安全的,可以保证按照添加的key-value对进行排序(排序和有序不是一回事,有序指的是添加顺序),实现排序遍历,此时考虑key的自然排序或者定制排序。底层使用红黑树
向TreeMap中添加key-value对,要求key必须是由同一个类创建的对象,因为是按照key进行排序的

        //创建的时候传入自定义比较器,key倒序,默认key升序
        TreeMap<Integer, String> treeMap = new TreeMap<>((x,y)-> y-x);
        treeMap.put(3,"3");
        treeMap.put(4,"4");
        treeMap.put(1,"1");
        treeMap.put(2,"2");
        treeMap.put(6,"6");
        Iterator<Integer> iteratorTreeMap = treeMap.keySet().iterator();
        while (iteratorTreeMap.hasNext()) {
            Integer next = iteratorTreeMap.next();
            System.out.println(next);  // 迭代会按key倒序输出
        }

ConcurrentHashMap

HashMap在并发put操作下会引起死循环,jdk1.7中是因为多线程会导致Entry链表形成环形数据结构,一旦形成,Entry的next节点永远不为空,查找的时候会产生死循环,导致CPU利用率接近100%,所以并发下不能使用HashMap

jdk1.8的HashMap将头插法改成了尾插法,避免了链表行程环形,但是在链表转换树或者对树进行操作的时候也会出现死循环的线程安全的问题。

ConcurrentHashMap是线程安全的哈希表。
1.7中是通过“分段锁”来实现多线程下的安全问题。它将哈希表分成了不同的Segment,段内使用了可重入锁(ReentrantLock ),不同线程只在一个段内存在线程的竞争。它不会对整个哈希表加锁。
ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。
1.8之后参考了JDK8 HashMap的实现,采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作,JDK8中彻底放弃了Segment转而采用的是Node。采用CAS+Synchronized保证线程安全
Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)

ConcurrentSkipListMap

ConcurrentSkipListMap 的key是有序的。是有序的并发map,支持更高的并发。ConcurrentSkipListMap 的存取时间是log(N),和线程数几乎无关。
也就是说在数据量一定的情况下,并发的线程越多,ConcurrentSkipListMap越能体现出他的优势。可以对key进行自定义排序,所以在多线程程序中,如果需要对Map的键值进行排序时,请尽量使用ConcurrentSkipListMap,可能得到更好的并发度。

        // 可传入自定义排序器
        Map<Integer,String> listMap = new ConcurrentSkipListMap<>((x,y)-> y-x);
        listMap.put(3,"3");
        listMap.put(4,"4");
        listMap.put(1,"1");
        listMap.put(2,"2");
        listMap.put(6,"6");
        Iterator<Integer> iterator = listMap.keySet().iterator();
        while (iterator.hasNext()) {
            Integer next = iterator.next();
            System.out.println(next);  // 迭代会按key倒序输出
        }

EnumMap

EnumMap要求map的key是枚举类型,EnumMap的key不允许为null,value可以为null,按照key在enum中的顺序进行保存,所以迭代也会按照枚举中的顺序迭代,非线程安全
EnumMap的性能和速度比HashMap要快。因为EnumMap里面的数据结构是数组,获取的数据的时候特别快。数组在EnumMap实例化的时候就已经初始化好了,要做的就是对原来的oldValue进行覆盖,这样无论获取数据还是添加和删除数据都要快的多。
这里特别提示一下,为什么EnumMap能够用数组,这得益于EnumMap的key是枚举类型,枚举类类型中每一种类型,在声明的时候都有确定的位置,可以通过对应的位置确定的枚举类型。通过该方法获得位置key.ordinal();
Mabatis框架的TypeHandlerRegistry中就用到了EnumMap

        // 构造器指定枚举class类型
        EnumMap enumMap = new EnumMap<JdbcType,String>(JdbcType.class);
        enumMap.put(JdbcType.THREE,"111");
        enumMap.put(JdbcType.ONE,"222");
        enumMap.put(JdbcType.FOUR,"233");
        enumMap.keySet().forEach(key->{
            System.out.println(key);  // 会按照枚举类中顺序进行迭代
        });
    }
    // 枚举类型
    public enum JdbcType {
        ONE("1","1"),    // 顺序
        THREE("1","1"),
        FOUR("1","1");
         ......
    }

HashTable

古老的实现类,线程安全,效率低,不可以存储null的key和value。底层都使用哈希表结构,查询速度快。

Properties

是Hashtable的子类,常用来处理配置文件。key和value都是String类型的。

Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user = pros.getProperty("user");
System.out.println(user);

Collection

List

List接口继承和扩展了Collection接口,List接口表示具有顺序的集合(放入顺序,不是指大小排序),其中可以包含重复元素。
使用List的实现类时,可以对列表中每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素

ArrayList

ArrayList类以数组为数据结构实现了List接口,能进行快速的随机访问,效率高而且实现了可变大小的数组。
调用new ArrayList()后,会默认初始化一个size=10的数组。
每次add操作都要检查数组容量,如果不够,重新设置一个初始容量为1.5倍大小的新数组,然后再把每个元素复制过去。
在数组中间插入或删除,都要移动后面的所有元素

LinkedList

LinkedList的底层是一种双向循环链表。
链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。
增删结点,只会对链表的指针进行操作,速度快,使用index查找对象时,会以index和size/2比较,从前或从后向中间搜索,所以查询效率低但是增删效率高。

CopyOnWriteArrayList

CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。
CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差。由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc
并且不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;

Vectot

Vectot与ArrayList相似,差别是:Vector是同步(线程安全)的,运行效率低,主要用于在线程环境中;而ArrayList是不同步的,适合在单线程环境中使用

Stack

堆栈是一种“后进先出”的数据结构,只能在一端进行输入或输出数据的操作。
java.util.Stack类继承了Vector类,对应数据结构中以“后进先出”方式存储和操作数据的线性表,即对象栈

Set

CopyOnWriteArraySet

它是线程安全的无序的集合,可以将它理解成线程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet;
但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过“动态数组(CopyOnWriteArrayList)”实现的,并不是散列表。
和CopyOnWriteArrayList类似,其实CopyOnWriteSet底层包含一个CopyOnWriteList,几乎所有操作都是借助CopyOnWriteList,就像HashSet包含HashMap

HashSet

HashSet 是一个没有重复元素的集合。
它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素。
HashSet是非同步的。

TreeSet

TreeSet是通过TreeMap实现的一个有序的、不可重复的集合,底层维护的是红黑树结构。当TreeSet的泛型对象不是java的基本类型的包装类时,
对象需要重写Comparable#compareTo()方法,
TreeSet判断元素是否重复 、以及确定元素的顺序靠的都是这个方法

LinkedHashSet

LinkedHashSet的特点是读取元素的顺序跟存入元素的顺序是一致的,并且元素不能重复。是Set中唯一插入有序的集合,即可以根据index获取元素
底层使用LinkedHashMap来保存所有元素

EnumSet

EnumSet 是一个专为枚举设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式或隐式地指定。
集合元素也是有序的,以枚举值在Enum类内的定义顺序来决定集合元素的顺序。
EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。
EnumSet集合不允许加入null元素,如果试图插入null元素,EnumSet将抛出NullPointerException异常。
EnumSet类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的类方法来创建EnumSet对象。

        //1.创建一个包含Session(枚举类)里所有枚举值的EnumSet集合
        EnumSet e1 = EnumSet.allOf(Session.class);
        System.out.println(e1);//[SPRING, SUMMER, FAIL, WINTER]
 
        //2.创建一个空EnumSet
        EnumSet e2 = EnumSet.noneOf(Session.class);
        System.out.println(e2);//[]
 
        //3. add()空EnumSet集合中添加枚举元素
        e2.add(Session.SPRING);
        e2.add(Session.SUMMER);
        System.out.println(e2);//[SPRING, SUMMER]

EnumSet是一个抽象类,其实现有两个:RegularEnumSet、JumboEnumSet。两个子类都不是public的,所以不能直接使用
什么时候用这两个类呢?EnumSet中的方法会自动选择,选择的依据是enum中定义的常量个数。
如果数量不多于64,那就是RegularEnumSet;反之,则是JumboEnumSet。所以大多时候,我们用的都是RegularEnumSet。

Queue

jdk在并发队列提供了两套实现,一类是ConcurrentLinkedQueue为代表的高性能队列,一类是BlockingQueue为代表的阻塞队列,都继承了Queeu

ConcurrentLinkedQueue

ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全的非阻塞队列,按照先进先出原则对元素进行排序。
新元素从队列尾部插入,而获取队列元素,则需要从队列头部获取。不允许null入列,底层使用列表与cas算法包装入列出列安全

ArrayBlockingQueue

ArrayBlockingQueue是一个基于数组的有界阻塞队列,在队列内部维护了一个定长数组,通过ReentrantLock实现,
因为数组是有界的,所以在数组为空和数组已满两种情况下需要阻塞线程

LinkedBlockingQueue

一个基于链表的无界阻塞队列,是jdk中固定大小线程池(Executors.newFixedThreadPool())底层所使用的阻塞队列,
内部实现采用读写分离锁,从而实现生产者和消费者并行操作

DelayQueue

一个没有大小限制的,带有延迟时间的队列,其中元素只有当指定的延迟时间到了,才能获取到该元素,其中元素必须实现Delayed接口,
应用场景很多,缓存超时处理,任务超时处理,空闲连接关闭等

PriorityBlockingQueue

一个基于优先级的无界阻塞队列,通过构造器传入的Compator对象来决定优先级,即传入对象必须实现Compator接口,内部使用公平锁实现线程同步

LinkedBlockingDeque

一个由链表组成的双向阻塞队列,即可以从队列两端插入获取元素,相比其他阻塞队列,减少了一半的锁竞争,双向阻塞队列可以用在工作窃取模式中

SynchronousQueue

一种没有缓冲的队列,生产者生产的数据直接会被消费者获取并消费,是线程池,newCachedThreadPool使用的任务队列

原文地址:https://www.cnblogs.com/houzheng/p/14434376.html