Java 集合入门

一,Java集合

Java的集合类只要又两个接口派生而出:Collection和Tree,Collection和Tree是Java集合框架的根接口,这两个接口又包含了一些子接口的实现类。
这里写图片描述

这里写图片描述

Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。

Iterator接口也是Java集合接口框架的成员,但它与Collection系列、Tree系列的集合不一样:Collection系列集合、Tree系列集合只要用于盛装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也称为迭代器。


二,各Set实现类的性能分析

HashSet 和 TreeSet 是Set的两个典型实现,到底如何选择HashSet和TreeSet呢?HashSet 的性能总是比TreeSet好(特别是最常用的添加,查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。

HashSet还有一个子类:linkedHashSet,对于普通的插入、删除操作,linkedHashSet比HashSet要略微慢一点,这时由于维护链表所带来的额外开销造成的,但由于有了链表,遍历linkedHashSet会更快。

HashSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。

必须指出的是,Set的三个实现类HashSet、TreeSet和HashSet都是线程不安全的。如果有多个线程同时访问一个Set集合,并且有超过一个线程修改了该Set集合,则必须手动保证该Set集合的同步性。通常可以通过collection工具类的synchronizedSortedSet方法来包装该Set集合。此操作最好在创建时进行,以防止对Set集合的意外非同步访问。例如

SortedSet Set = Collections.synchronizedNavigableSet(new TreeSet<>());

三,各种线性表的性能分析

ArrayList和vector是List类的两个典型实现,都是基于数据实现的List类。
vector类是一个古老的集合,具有很多缺点,通常尽量少用vector实现类。
ArrayList和vector的显著区别是:ArrayList是线程不安全的,当多个线程访问同一个ArrayList集合时,如果有超过一个线程修改了ArrayList集合,则程序必须手动保证集合的同步性,但vector集合则是线程安全的,无须保证该集合的同步性。因为vector是线程安全的,所以vector的性能比ArrayList的性能要低。

PriorityQueue是一个比较标准的队列实现类,而不是绝对标准的队列实现,因为PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序的。

Java提供的list就是一个线性表接口,而ArrayList、linkedlist又是线性表的两种典型实现,基于数组的线性表和基于链表的线性表。queue代表了队列,deque代表了双端队列(既可作为队列使用,也可作为栈使用),接下来对各种实现类的性能进行分析。

一般来说,由于数组以一块连续内存区来保存所有的数组元素,所以数组在随机访问时性能最好,所有的内部以数组作为底层实现的集合在随机访问时性能都比较好,而内部以链表作为底层实现的集合在执行插入、删除操作时有较好的性能。但总体来说,ArrayList的性能比linkedlist的性能要好,因此大部分时候都应该考虑使用ArrayList。

关于使用list集合的建议:
1,如果需要遍历list集合,对于ArrayList、vector集合,应该使用随机访问法(get)来遍历集合元素,这样性能更好,对于linkedlist集合,则应该采用迭代器(iterator)来遍历集合元素。
2,如果需要经常执行插入、删除操作来改变包含大量数据的list集合的大小,可考虑使用linkedlist集合。使用ArrayList、vector集合可能需要经常分配内部数组的大小,效果可能比较差。
3,如果多个线程需要同时访问list集合的元素,开发者可考虑使用Collections将集合包装成线程安全的集合。


四,各Tree实现类的性能分析

从Java源码来看,Java是先实现了Tree,然后通过包装一个所有value都为null的Tree就实现了Set集合。

Properties类是Hashtable类的子类,正如它的名字所暗示的,该对象在处理属性文件时特别方便。

WeekHashTree与HashTree的用法基本类似。与HashTree的区别在于,HashTree的key保留了对实际对象的强引用,这意味着只要该HashTree对象不被销毁,该HashTree的所有key所引用的对象就不会被垃圾回收,HashTree也不会自动删除这些key所对应的kev-value对,但WeekHashTree的key只保留了对实际对象的弱引用,这意味着如果WeekHashTree对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,WeekHashTree也可能自动删除这些key所对应的key-value对。

HashTree实现类是一个与枚举一起使用的Tree实现,HashTree中的所有key都必须是单个枚举类的枚举值。

对于Tree的常用实现类而言,虽然HashTree与Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的、线程安全的集合,因此HashTree通常比Hashtable要快。

treeTree通常比HashTree、Hashtable要慢(尤其在插入、删除key-value对时更慢),因为treeTree底层采用红黑树来管理key-value对(红黑树的每个节点就是一个key-value对)。

使用treeTree有一个好处:treeTree中的key-value对总是处于有序状态,无须专门进行排序操作。当treeTree被填充之后,就可以调用keySet(),取得由key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。

对于一般的应用场景,程序应该多考虑使用HashTree,因为HashTree正是为快速查询设计的(HashTree底层其实也是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Tree时,则可以考虑使用treeTree。

linkedHashTree比HashTree慢一点,因为它需要维护链表来保持Tree中key-value的添加顺序。IdentityHashTree性能没有特别出色之处,因为它采用与HashTree基本相似的实现,只是它使用==而不是equals()方法来判断元素相等。HashTree的性能最好,但它只能使用同一个枚举类的枚举值作为key。


五,collections工具类同步控制

collections类中提供了多个synchronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。

Java中常用的集合框架中的实现类HashSet、treeSet、ArrayList、ArrayDeque、linkedlist、HashTree和treeTree都是线程不安全的。如果有多个线程访问它们,而且有超过一个的线程试图修改它们,则存在线程安全问题。collections提供了多个类方法可以把它们包装成线程安全的集合。

public class SynchronizedTest
{
    public static void main(String[] args)
    {
        // 下面程序创建了四个线程安全的集合对象
        Collection c = Collections
            .synchronizedCollection(new ArrayList());
        List list = Collections.synchronizedList(new ArrayList());
        Set s = Collections.synchronizedSet(new HashSet());
        Tree m = Collections.synchronizedTree(new HashTree());
    }
}

在上面示例程序中,直接将新创建的集合对象传给了collections的synchronizedXxx()方法,这样就可以直接获取list、Set和Map的线程安全实现版本。


六,线程安全的集合类

在java.util.concurrent包下提供了大量支持并发访问的集合接口和实现类。
这里写图片描述

这些线程安全的集合类可分为如下两类:
1,以Concurrent开头的集合类,如ConcurrentHashMap。Concurrentskiplistmap、Concurrentskiplistset、Concurrentlinkedqueue和Concurrentlinkeddeque。
2,以CopyOnWrite开头的集合类,如CopyOnWriteArrayList、CopyOnWritearrayset。

其中以Concurrent开头的集合类代表了支持并发访问的集合,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。以Concurrent开头的集合类采用了更复杂的算法来保证永远不会锁住整个集合,因此在并发写入时有较好的性能。

当多个线程共享一个公共结合时,ConcurrentLinkedQueue时一个恰当的选择。ConcurrentLinkedQueue不允许使用null元素。ConcurrentLinkedQueue实现了多线程的高效访问,多个线程访问ConcurrentLinkedQueue集合时无须等待。

在默认情况下,ConcurrentHashMap支持16个线程并发写入,当超过16个线程并发向该map中写入数据时,可能有一些线程需要等待。实际上,程序通过设置concurrentLevel构造参数(默认值16)来支持更多的并发写入线程。

与前面介绍的HashMap和普通集合不同的是,因为Concurrentlinkedqueue和ConcurrentHashMap支持多线程并发访问,所以当使用迭代器来遍历元素时,该迭代器可能不能反映出迭代器之后所做的修改,但程序不会抛出异常。

使用非concurrent包下的collection作为集合对象时,如果该集合对象创建迭代器后集合元素发生改变,则会引发ConcurrentModificationException异常。

由于CopyOnWritearrayset的底层封装了CopyOnWriteArrayList,因此它的实现机制完全类似于CopyOnWriteArrayList集合。

对于CopyOnWriteArrayList集合,正如它的名字所暗示的,它采用复制底层数组的方式来实现写操作。

当线程对CopyOnWriteArrayList集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对CopyOnWriteArrayList集合执行写入操作时(包括调用add()、remove()、set()等方法),该集合会在底层复制一份新的数组,接下来对新的数组执行写入操作。由于对CopyOnWriteArrayList集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。

需要指出的是,由于CopyOnWriteArrayList执行写入操作时需要频繁地复制数组,性能比较差,但由于读操作与写操作不是操作同一个数组,而且读操作也不需要加锁,因此读操作就很快、很安全。由此可见,CopyOnWriteArrayList适合用在读取操作远远大于写入操作的场景中,例如缓存等。


七,参考资料

《Java疯狂讲义》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值