Java中的集合

来源:互联网 发布:五线谱打谱软件mac 编辑:程序博客网 时间:2024/05/16 12:00

    Java中的集合分为两大类,分别由两个接口派生出的:Collection和Map。Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。


Collection




    Collection接口下又有三个子接口:Set、Queue、List。Set是无序集合,集合中的元素不可重复;List是有序集合,集合中的元素可以重复;Queue是队列的实现形式,类似于List。


Set集合


    Set是无序集合,并且集合中的元素不可重复。程序依次将元素放入Set集合中,Set集合不会记住元素的顺序;但是Set集合不允许放入相同元素,当程序调用add()方法添加元素时,如果集合中没有该元素,则直接添加成功;如果有元素,则新元素不添加,add()方法返回false。


HashSet


    Set集合常用的实现类是HashSet,也是Set接口的典型实现。HashSet是通过Hash算法来确定元素的存取位置,所以存取和查找的性能比较高,但它依然是无序的。而且HashSet是线程不安全的,多个线程同时访问一个HashSet,可能会出现数据的错乱,必须要通过代码来保证同步。


    HashSet集合判断存入的两个元素是否相等,要求两个对象通过equals()方法比较后为true,并且两个对象的hashCode值也是相等的。如果两个对象的hashCode值不同,但是equals()方法判断两个对象相等,那么这两个对象会被当成不同的对象分别存储在不同位置,这时HashSet集合就存储了相同的对象。但是如果equals()方法比较返回false,但是两个对象的hashCode值相同,HashSet集合根据两个对象的hashCode值经过Hash算法计算得到相同值,则会把两个对象存储在同一个位置,但是一个位置只能存放一个对象,所以就会在该位置以链表形式存储多个对象。HashSet查找数据时是根据hashCode值来查找的,所以同一个位置存储多个元素时会降低查找效率。如果需要把某个类的对象保存到HashSet中,需要重写equals()方法和hashCode()方法,并且尽量保证两个对象通过equals()方法判断相等时,hashCode()方法返回的值也相同。


    HashSet还有一个子类是LinkedHashSet,它也是根据元素的hashCode值来计算存储位置的,但同时也会使用链表维护元素的顺序,使元素看起来是以插入的顺序保存的。


TreeSet


    TreeSet根据名字就可以看出来,它是以数结构存储元素的,并且保证元素是有序的,并不是和添加顺序相同即为有序,而是因为TreeSet是采用红黑树的数据结构存储数据,所以会按照一定的算法对数据进行排序。


EnumSet


    EnumSet是专门为枚举类设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显示或隐式指定。EnumSet中的集合元素也是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序。


    EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑、高效,因此EnumSet对象占用内存很小,而且运行效率很好。


List集合


    List集合是元素有序、可重复的集合,集合中的每个元素都有其对应的顺序索引,所以会有根据索引操作元素的很多方法。List集合不限制元素的个数,可以随意添加,List集合的大小也会自动增加。


ArrayList


    ArrayList是List接口的一个典型实现,它的基本数据结构是数组,根据List集合的特性,这个数组的长度是可以自动增加的。ArrayList是线程不安全的,当多个线程访问同一个ArrayList集合时,程序需要写代码实现同步,但是性能是比较好的。


Queue集合


    Queue集合的基本数据结构是队列,队列具有先进先出的特性,队列中的元素总是从队列的头部离开队列,从队列的尾部进入队列。PriorityQueue是一个比较标准的队列实现类,它并不是队列那样先进先出,而是会对队列中的数据进行排序。Deque接口是Queue接口的子接口,它代表一个双端队列,也可以做为栈来使用。


Map

                               


    Map用于保存具有映射关系的数据,所以Map集合里保存着两组值,一组值用于保存Map中的key,另外一组值用于保存Map中的Value,key和value都可以是任何引用类型的数据。Map中的Key值不可重复。


HashMap


    HashMap是Map接口的典型实现类,它是线程不安全的,所以性能相对较好。HashMap同样是根据Hash算法来计算Key值所应该存储的位置,类似于HashSet,所以作为Key的对象需要保证通过equals()方法判定为相等时,hashCode也要尽量保持相等。HashMap的基本数据结构是数组加链表的形式,当key经过Hash算法计算后,会把Key-Value存储在数组的相应位置,如果Key的hashCode相同,但是equals()方法判定不同时,会在同一个位置以链表形式存储,这样就降低了HashMap的查找性能。


                                                  


Hashtable


    Hashtable是比较老的Map的实现类,从JDK1.0开始就已经出现了。Hashtable的命名并没有像其他的命名一样,每个单词的首字母要大写。Hashtable和HashMap是相似的,但是Hashtable中的key不允许为null。而且Hashtable中的方法是线程安全的,线程同步是靠synchronized关键字实现的,所以相对于HashMap来说性能较差。Hashtable和HashMap一样,存储的顺序是无序的,而且也是通过equals()方法和hashCode()方法同时判定key是否相等的。


    HashSet、HashMap和Hashtable都是通过Hash算法来决定元素的存储位置的,所以他们都有以下几个属性:

        容量:hash表中最多能存储的数据量

        初始化容量:创建hash表时的容量

        尺寸:当前hash表中的数量

        负载因子:hash表中当前数量占hash表容量的百分比


    HashSet、HashMap和Hashtable都有一个负载因子的最大值,超过这个最大值的时候,集合会进行扩容,一般都是倍增,然后会将原有的对象重新分配,这个过程就是rehashing。默认的负载因子的最大值是0.75,即数据存储到容量的四分之三时即会扩容,既不会因为频繁rehashig而占用内存,也不会因为存储空间不足而报错。HashSet和HashMap的线程不安全,可能也会发生在rehashing时,当多个线程同时rehashing时也会出错。


总结


    JDK对于集合的支持,在不同的版本有不同的改进。从Java8完善了Lambda表达式之后,通过Lambda表达式遍历集合也使得代码简便了许多。不同的集合有不同的特点,使用时根据需要区分就好。