java集合框架总结

来源:互联网 发布:淘宝怎么搜短信轰炸机 编辑:程序博客网 时间:2024/04/29 04:58

      注:以下是常用集合类和使用的方法和用途。

1.Collection接口

       是List、Set集合类接口的根接口。Collection中存储一组对象(即元素)。这些元素有一些是允许有重复,而另一些可能不可以用重复。有一些是可以有序的,而另一些则是无序的。(有序是指存储顺序和输出顺序一致)例如:List集合是重复的、有序的,Set集合是不重复、无序的。该接口中的很多方法底层是使用equals方法作为比较的。类型通常是直接使用Object。

    1.1  List接口

       List接口是继承至Collection接口,该List接口的实现子类皆是有序的,此接口的用户可以对列表中每一个元素的插入的位置进行精准的控制。用户可以根据元素的整数索引进行访问数据。

       1.1.1  ArrayList实现类

        ArrayList实现类是有序的,可重复的,元素可以为null。它也是线程不安全的。如果想实现线程同步可以使用Collections.synchronizedList(new ArrayList());进行包装。该列表底层是使用数组进行顺序维护(在地址上是连续的),如果在使用列表时固定大小,进行存取操作,不进行插入、删除元素操作,建议使用ArrayList。数组进行插入和删除操作时,维护顺序时候会消耗大量资源。

       1.1.2  LinkedList实现类

        LinkedList实现类是有序的,可重复的,元素可以为null。它是线程不安全的。如果想实现线程同步可以使用Collections.synchronizedList(new ArrayList());进行包装。该列表底层是使用链表进行顺序维护(地址上是不连续的,但在逻辑上是连续的),所以LinkedList是对于插入、删除等操作相对消耗资源较小,在进行频繁插入和删除操作时建议使用ArrayList。

       1.1.3  Vector实现类

        Vector是可以实现可增长的对象数组。与数组一样,它包括可以使用整数索引进行访问的组件。但是,Vector的大小可以根据需求增大或者缩小,以适应创建Vector后进行添加或移除项的操作。Vector是线程安全的。由 Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的:如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。Vector 的 elements 方法返回的 Enumeration不是 快速失败的。

       1.1.4  Stack实现类

       Stack 类表示后进先出(LIFO)的对象堆栈。它通过五个操作对类Vector 进行了扩展 ,允许将向量视为堆栈。它提供了通常的pushpop 操作,以及取堆栈顶点的peek 方法、测试堆栈是否为空的empty 方法、在堆栈中查找项并确定到堆栈顶距离的search 方法。

       1.1.5  Vector、ArrayList和LinkedList的比较

  1. Vector是线程同步的,所以它也是线程安全的,而ArrayList和LinkedList是非线程安全的。如果不考虑到线程的安全因素,一般用ArrayList和LinkedList效率比较高。
  2. ArrayList和Vector是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  3. 如果集合中的元素的数目大于目前集合数组的长度时,Vector增长率为目前数组长度的100%,而ArrayList增长率为目前数组长度的50%.如果在集合中使用数据量比较大的数据,用vector有一定的优势;反之,用ArrayList有优势。
  4. 如果查找一个指定位置的数据,Vector和ArrayList使用的时间是相同的,花费时间为O(1),而LinkedList需要遍历查找,花费时间为O(i),效率不如前两者。
  5. 而如果移动、删除一个指定位置的数据花费的时间为0(n-i)n为总长度,这个时候就应该考虑使用LinkedList,因为它移动一个指定位置的数据所花费的时间为0(1)。
  6. 对于在指定位置插入数据,LinedList比较占优势,因为ArrayList要移动数据。

    1.2  Set接口

       Set接口是无序的,不重复的接口。Set内的元素最多可以包含一个null元素。注:如果将可变对象用作 set 元素,那么必须极其小心。如果对象是 set 中某个元素,以一种影响equals 比较的方式改变对象的值,那么 set 的行为就是不确定的。此项禁止的一个特殊情况是不允许某个 set 包含其自身作为元素。

        1.2.1  HashSet实现类

         HashSet类是无序的,不重复的,可以有null元素。HashSet是线程不安全的。如果想实现线程安全可以使用Collections.synchronizedSet(new HashSet()); 进行包装。HashSet底层元素是使用哈希表的散列码进行存储的。

此类的 iterator 方法返回的迭代器是快速失败 的:在创建迭代器之后,如果对 set 进行修改,除非通过迭代器自身的remove 方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来在某个不确定时间发生任意不确定行为的风险。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器在尽最大努力抛出ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法:迭代器的快速失败行为应该仅用于检测 bug。

       1.2.2  LinkedHashSet实现类

        LinkedHashSet类是有序的、不重复的、可以有null元素的,线程不安全的。LinkedHashSet具有可预知迭代顺序的Set 接口的哈希表和链接列表实现。此实现与HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。注意,插入顺序 受在 set 中重新插入的 元素的影响。(如果在s.contains(e) 返回true 后立即调用 s.add(e),则元素e 会被重新插入到 sets 中。)

        LinkedHashSet 有两个影响其性能的参数:初始容量加载因子。它们与HashSet 中的定义极其相同。注意,为初始容量选择非常高的值对此类的影响比对HashSet 要小,因为此类的迭代时间不受容量的影响。

       注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何强有力的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

       1.2.3  TreeSet实现类

        TreeSet是有顺序的(即按照自然顺序排列,也可自定义)、不重复的(即有多个一样的值只存储一个)、不可以有null元素、线程不安全的。TreeSet基于TreeMapNavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的Comparator 进行排序,具体取决于使用的构造方法。

        此实现为基本操作(addremovecontains)提供受保证的 log(n) 时间开销。

        注意,如果要正确实现 Set 接口,则 set 维护的顺序(无论是否提供了显式比较器)必须与 equals 一致。(关于与 equals 一致 的精确定义,请参阅ComparableComparator。)这是因为Set 接口是按照 equals 操作定义的,但 TreeSet 实例使用它的 compareTo(或 compare)方法对所有元素进行比较,因此从 set 的观点来看,此方法认为相等的两个元素就是相等的。即使 set 的顺序与 equals 不一致,其行为也 定义良好的;它只是违背了Set 接口的常规协定。

       1.2.4  Set总结

       在所有Set子类中多线程中不可使用Iterator进行遍历,只可用于测试BUG。

2.  Map接口

        Map接口没有继承Collection接口,是一个单独的接口。Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value,不同的key对应的value可以相同。Map 接口提供三种collection 视图,允许以键集(map.keySet())、值集(map.values())或键-值映射关系集(map.entrySet())的形式查看某个映射的内容。映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如HashMap 类。  

       2.1  HashMap实现类

       HashMap是哈希表基于Map接口的实现。使用键-值对进行存储数据(键不可以重复,值可以重复)。可以有null键和null值。HashMap是线程不安全的。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

      此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(getput)提供稳定的性能。迭代 collection 视图所需的时间与HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

    HashMap 的实例有两个参数影响其性能:初始容量加载因子容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

      在Java编程语言中,加载因子默认值为0.75,默认哈希表元为101。通常,默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数HashMap 类的操作中,包括getput 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

       如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedMap 方法来“包装”该映射。

       2.2  LinkedHashMap实现类

       LinkedHashMap是有序的(即输入顺序和遍历顺序一致)、线程是不安全的。

    Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。注意,如果在映射中重新插入 键,则插入顺序不受影响。(如果在调用m.put(k, v)m.containsKey(k) 返回了true,则调用时会将键 k 重新插入到映射m 中。)

package com.mypractice.third;import java.util.LinkedHashMap;import java.util.Map;import java.util.Set;import java.util.Map.Entry;public class LinkedHashMapDemo {/** * @param args */public static void main(String[] args) {Map<Integer,Integer> maps = new LinkedHashMap<Integer,Integer>();//创建一个LinkedHashMap对象for(int i=1;i<5;i++){//赋值maps.put(i,i*i);}System.out.println("插入前:");Set<Entry<Integer,Integer>> sets1 = maps.entrySet();                //获取键值对for(Entry<Integer,Integer> set1:sets1){//遍历键值对System.out.println(set1.getKey()+"    "+set1.getValue());}if(maps.containsKey(1)){//判断时候有键为1的,如果并为其加入值maps.put(1, 10);}System.out.println("插入后:");Set<Entry<Integer,Integer>> sets2 = maps.entrySet();                //获取修改值后的键值对for(Entry<Integer,Integer> set1:sets2){//遍历键值对System.out.println(set1.getKey()+"    "+set1.getValue());}}}
运行结果:


总结:

    1、键值对的插入顺序和遍历输出顺序一致。

    2、如果为一个已经存在的键值对进行重新存放,将会把原有键值对的值重新赋值,并且顺序还是原有顺序。



0 0
原创粉丝点击