Java集合类小结

本文摘自 https://www.jianshu.com/p/589d58033841

目录

 Collection接口

Set集合

HashSet

LinkedHashSet

TreeSet

EnumSet

List集合

ArrayList

LinkedList

ArrayList与LinkedList性能对比

Queue集合

Map集合


Java的集合类主要由Collection、Map这两个接口派生出的。如图1、2所示:

                                                                                            图1

 

                                                                                            图2

  •  Collection接口

Collection接口是Set,Queue,List的父接口。Collection接口中定义了多种方法可供其子类进行实现,以实现数据操作。

  • Set集合

Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,且新元素不会被加入。

  • HashSet

HashSet按Hash算法来存储集合中的元素。因此具有很好的存取和查找性能。

特点:

  1. 不能保证元素的排列顺序。
  2. 不是线程安全的。
  3. 元素可以是NULL。

HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。

HashSet是根据元素的hashCode值来快速定位的,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降。

  • LinkedHashSet

LinkedHashSet是HashSet对的子类,也是根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使得元素是以插入的顺序来保存的。于要维护元素的插入顺序,在性能上略低与HashSet。但在迭代访问Set里的全部元素时有很好的性能。

判断重复标准与HashSet一致。

HashSet的实质是一个HashMap。HashSet的所有集合元素,构成了HashMap的key,其value为一个静态Object对象。因此HashSet的所有性质,HashMap的key所构成的集合都具备。

  • TreeSet

TreeSet也是非线程安全的。

TreeSet是SortedSet接口的实现类,正如SortedSet名字所暗示的,TreeSet可以确保集合元素处于排序状态。

TreeSet支持两种排序方法:自然排序和定制排序。在默认情况下,TreeSet采用自然排序。

自然排序:

TreeSet会调用集合中元素所属类的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,即把通过compareTo(Object obj)方法比较后比较大的的往后排。这种方式就是自然排序。

一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则就会出现异常。

定制排序:

定制排序是通过Comparator接口的帮助。该接口包含一个int compare(T o1,T o2)方法。如果要实现定制排序,则需要在创建TreeSet时,调用一个带参构造器,传入Comparator对象。并有该Comparator对象负责集合元素的排序逻辑,集合元素可以不必实现Comparable接口。

如果向TreeSet中添加了一个可变对象后,并且后面程序修改了该可变对象的实例变量,这将导致它与其他对象的大小顺序发生了改变,但TreeSet不会再次调整它们。

  • EnumSet

EnumSet是非线程安全的。

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

特点:

1.EnumSet集合不允许加入null元素。EnumSet中的所有元素都必须是指定枚举类型的枚举值。
2.EnumSet类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的类方法来创建EnumSet对象。

EnumSet没有其他额外增加的方法,只是增加了一些创建EnumSet对象的方法。

  • EnumSet性能>HashSet性能>LinkedHashSet>TreeSet性能

EnumSet内部以位向量的形式存储,结构紧凑、高效,且只存储枚举类的枚举值,所以最高效。HashSet以hash算法进行位置存储,特别适合用于添加、查询操作。LinkedHashSet由于要维护链表,性能比HashSet差点,但是有了链表,LinkedHashSet更适合于插入、删除以及遍历操作。而TreeSet需要额外的红黑树算法来维护集合的次序,性能最次。

使用场景:

  1. 当需要一个特定排序的集合时,使用TreeSet集合。
  2. 当需要保存枚举类的枚举值时,使用EnumSet集合。
  3. 经常添加查询,使用HashSet集合。
  4. 经常插入排序或者删除插入及遍历时,使用LinkedHashSet。

 

  • List集合

List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素 。List集合默认按元素的添加顺序设置元素的索引。

List判断两个对象相等只要通过equals()方法比较返回true即可。

  • ArrayList

ArrayList和Vector作为List类的两个典型实现,完全支持之前介绍的List接口的全部功能。

ArrayList和Vector类都是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。ArrayList或Vector对象使用initalCapacity参数来设置

ArrayList是一个动态扩展的数组,Vector也同样如此。
如果开始就知道ArrayList或Vector集合需要保存多少个元素,则可以在创建它们时就指定initalCapacity的大小,这样可以提高性能。

1.ArrayList是线程不安全的,Vector是线程安全的。
2.Vector的性能比ArrayList差

Stack是Vector的子类,用户模拟“栈”这种数据结构,“栈”通常是指“后进先出”。

Stack是线程安全的,但是性能较差。

  • LinkedList

LinkedList也是非线程安全的。

LinkedList类是List接口的实现类——这意味着它是一个List集合,可以根据索引来随机访问集合中的元素。除此之外,LinkedList还实现了Deque接口,可以被当作成双端队列来使用,因此既可以被当成“栈"来使用,也可以当成队列来使用。

ArrayList内部是以数组的形式来保存集合中的元素的,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色。

  • ArrayList与LinkedList性能对比

ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。ArrayList应使用随机访问(即,通过索引序号访问)遍历集合元素。
LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。LinkedList应使用采用逐个遍历的方式遍历集合元素。
如果涉及到“动态数组”、“栈”、“队列”、“链表”等结构,应该考虑用List,具体的选择哪个List,根据下面的标准来取舍。
(01) 对于需要快速插入,删除元素,应该使用LinkedList。
(02) 对于需要快速随机访问元素,应该使用ArrayList。
(03) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。

  • Queue集合

Queue用户模拟队列这种数据结构,队列通常是指“先进先出”(FIFO,first-in-first-out)的容器。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。

  • Map集合

Map用户保存具有映射关系的数据。Map的key不允许重复。key和value之间存在单向一对一关系,即通过指定的key,总能找到唯一的、确定的value。

1.与Set集合的关系
如果 把Map里的所有key放在一起看,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不能重复),实际上Map确实包含了一个keySet()方法,用户返回Map里所有key组成的Set集合。
2.与List集合的关系
如果把Map里的所有value放在一起来看,它们又非常类似于一个List:元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中索引不再使用整数值,而是以另外一个对象作为索引。

  • HashMap

HashMap是一个散列表,存储键值对。

 

HashMap与Hashtable:都是Map接口的实现类,关系类似于ArrayList和Vector的关系。Hashtable可以看出比较古老,因为命名都不是Java命名规范(t没大写),并且不符合Map接口的规范,但是两个各有优点。

区别:

Hashtable是一个线程安全的Map实现,但是HashMap是线程不安全的实现,所以HshMap比Hashtable的性能好一点,但若多个线程访问同一个Map用hashtable更好。

Hashtable不允许使用null作为key和value,如果用null放入hashtable将会引发空指针异常;但是hashmap可以null为key或者value。

HashMap中key所组成的集合元素不能重复,value所组成的集合元素可以重复。

HashMap分析:

构造:

从构造方法中可以看出:两个元素:容量大小capacity以及加载要素loadFactor 。

capacity是哈希表容量,默认的容量是:

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

loadFactor是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表的条目数目超出了loadFactor✖capacity时候,则药对哈希表进行resize操作(重建内部数据结构),从而哈希表将具有大约两倍的桶数。默认loadFactor是0.75。这是一种对时间和空间成本上的选择。loadFactor过高虽然能减少空间开销,但是同时增加了查询成本。在设置容量时候需要考虑到条目数和loadFactory,以便最大限度的较少resize操作次数。如果容量大于最大条目数除以loadFactor,则不会发生rehash操作。

HashMap的数据结构:数组和链表

数组:数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难

链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易

哈希表具有较快(常量级)的查询速度,及相对较快的增刪速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”,我們可以理解为“链表的数组”,如图

 

 可以发现哈希表是数组加链表组成,一个长度16的书中中每个元素存储的是一个链表的头节点。

如何存储到数组?一般通过hash(key)%len,即元素key的哈希值对数组长度取余。如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。

线性的数组怎么实现按键值对来存取数据呢?

一般通过hash(key)获得,即哈希值,如果值相等则存入该hash值所对应的链表中。内部是一个Node数组

 /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    transient Node<K,V>[] table;
    /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

 HashMap遍历请参考我的另一篇文章Java中HashMap遍历的几种方式

  • LinkedHashMap实现类

非线程安全,HshMap子类

使用双向链表来维护key-value对的次序。

因为其需要维护元素的插入顺序所以性能略低HashMap但是因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时候有较好的性能,因为迭代输出LinkedHashMap元素时候会按照添加key-value对的顺序输出。

本质:散列表 + 循环双向链表。

    /**
     * The head (eldest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * The tail (youngest) of the doubly linked list.
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
    final boolean accessOrder;

定义了双向列表的头节点和尾巴节点以及accessOrder访问顺序为真,插入顺序为假。

    /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    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);
        }
    }

before、After是用于维护Entry插入的先后顺序。

  • TreeMap

TreeMap是SortedMap接口的实现类。是一个有序的key-value集合,通过红黑树实现的,每个key-value对即作为红黑树的一个节点。

排序方式:

自然排序,TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则抛出ClassCast异常

自定义排序,创建TreeMap时,传入一个Comparator对象。

TreeMap中判断两个key相等的标准是:两个key通过compareTo()方法返回0,TreeMap即认为这两个key是相等的。

TreeMap中判断两个value相等的标准是:两个value通过equals()方法比较返回true。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值