Collection集合概览

Container taxonomy

Container taxonomy

上图是Java容器分类图,初看这张图可能会有点庞大,但是实际上只有三个组件:Map, List, and Set。
我们解读一下其中的组件:
1.黑色粗线框所代表的是我们常用的容器组件,包括HashMap、HashSet、ArrayList和LinkedList。
2.点框代表的是接口。
3.虚线框代表了抽象类。
4.实线框是常规的实现类。
5.空心箭头代表实现类实现了一个接口或者继承了一个抽象类。
6.实心箭头表示这个类可以生成箭头指向的那个类,例如,任何一个Collection对象可以生成一个Iterator对象,任何一个List对象可以生成一个ListIterator对象。

1.Collection接口

Collection接口是最基本的集合接口,它不提供直接的实现,JDK提供的实现类都是继承自Collection的两个”子接口”,即List和Set
在Java中所有实现了Collection接口的类(抽象类除外)都必须提供两套标准的构造函数:一个是无参构造函数,用于创建一个空的Collection;另一个是带有Collection参数的有参构造函数,用于创建一个新的Collection,这个新的Collection与传入进来的Collection具备相同的元素。

1.1 List接口
List代表了有序的Collection,即它用某种特定的插入顺序来维护元素顺序。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

1.1.1 ArrayList
(1) ArrayList是一个动态数组,它允许插入任何符合规则的元素甚至是null。
(2) 每一个ArrayList的初始容量是10,随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。扩容时会将容器大小设为当前容量的1.5倍。所以,倘若我们明确知道所需插入元素的size,最好指定一个初始容量值,避免过多得进行扩容操作而浪费时间、效率。

int newCapacity = oldCapacity + (oldCapacity >> 1);

(3) add操作的复杂度是O(n),同时ArrayList是非同步的。

1.1.2 LinkedList
(1) LinkedList是一个双向链表。它除了有ArrayList的基本操作方法外,还额外提供了获取、删除、插入到LinkedList首部或尾部的方法等。
(2) 由于实现方式不同,LinkedList不能随机访问,在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。但在List中进行插入和删除操作的代价会比较低。
(3) LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(...));

1.1.3 Vector
与ArrayList相似,它的操作与ArrayList几乎一样。但是Vector是同步的。
另外,Vector扩容是一倍增长,而ArrayList是0.5倍增长。

        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);

1.1.4 Stack
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。push()把元素压入栈顶,pop()移除栈顶元素,peek()得到栈顶的元素,empty()测试堆栈是否为空,search()返回一个元素在堆栈中的位置。

1.2 Set接口
Set是一种不包括重复元素的Collection。它维持它自己的内部排序,所以随机访问没有任何意义。与List一样,它同样允许null的存在但是只能有一个。
Set根据equals方法判断两个对象是否相同。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。
有意思的是,几乎所有的Set实现都是Map,例如TreeSet基于TreeMap实现,HashSet基于HashMap实现。
实现了Set接口的集合有:TreeSet、HashSet、LinkedHashSet。

1.2.1 TreeSet
TreeSet可以确保集合元素处于排序状态,内部以TreeMap来实现。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。

    public TreeSet(Comparator<? super E> comparator) {
        // 定制排序应该使用Comparator接口,实现int compare(T o1,T o2)方法
        this(new TreeMap<>(comparator));
    }

1.2.2 HashSet
HashSet堪称查询速度最快的集合,因为其内部是以HashCode来实现的。它内部元素的顺序是由哈希码来决定的,所以它不保证set的迭代顺序,特别是它不保证该顺序恒久不变。HashSet还提供了可以指定初始容量和负载因子的构造函数。

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

1.2.3 LinkedHashSet
LinkedHashSet同样根据元素的HashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

2.Map接口

Map是由一系列键值对组成的集合,提供了key到Value的映射,它保证了key与value之间的一一对应关系。实现Map接口的有:HashMap、TreeMap、HashTable、LinkedHashMap、WeakHashMap。

2.1 HashMap
(1) HashMap底层是哈希表数据结构,查找对象时通过哈希函数计算其位置。它是为快速查询而设计的,其内部定义了一个hash表数组(table),元素会通过哈希转换函数将元素的哈希地址转换成数组中存放的索引,如果有冲突,则使用散列链表的形式将所有相同哈希地址的元素串起来。通过查看HashMap$Entry的源码它是一个单链表结构。

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

(2) HashMap是非同步的,并且允许null value和null key。由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现HashCode和equals()方法。
(3) 将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

2.2 TreeMap
TreeMap底层是red-black Tree(红黑树),它可用于给Map集合中的键进行排序。同时它不是线程安全的。

    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

2.3 HashTable
与HashMap的不同在于它是线程安全的。但是,HashMap可以实现访问同步,HashMap的功能比Hashtable的功能更多,而且Hashtable基于一个陈旧的类Dictionary的,所以Hashtable基本弃用了。

2.4 LinkedHashMap
(1) 与HashMap相比,LinkedHashMap维护的是一个具有双重链表的HashMap。LinkedHashMap支持2种排序,一种是插入排序,一种是使用排序。
(2) LinkedHashMap内部含有一个header,来记录元素插入的顺序或者是元素被访问的顺序。利用这个线性结构的对象,可以帮助记录entry加入的前后顺序或者记录entry被访问的频率(最少被访问的entry靠前,最近访问的entry靠后)。

private transient Entry<K,V> header;

2.5 WeakHashMap
WeakHashMap是一种改进的HashMap,它对key实行”弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
例如:声明了两个Map对象,一个是HashMap,一个是WeakHashMap,同时向两个map中放入a、b两个对象,当HashMap中的a对象被remove掉并且将a、b都指向null时,WeakHashMap中的a将自动被回收掉。出现这个状况的原因是,对于a对象而言,当HashMap中的a对象被remove掉并且将a指向null后,除了WeakHashMap中还保存a外已经没有指向a的指针了,所以WeakHashMap会自动舍弃掉a,而对于b对象虽然指向了null,但HashMap中还有指向b的指针,所以WeakHashMap将会保留。

3.异同比较

3.1 HashMap vs HashTable
(1) HashTable继承了Dictionary类,而HashMap继承了AbstractMap。Dictionary是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。

(2) HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。

    // 当HashMap遇到为null的key时,它会调用putForNullKey方法来进行处理。对于value没有进行任何处理,只要是对象都可以。
    if (key == null)
        return putForNullKey(value);

    // 当HashTable遇到null时,他会直接抛出NullPointerException异常信息。
    if (value == null) {
        throw new NullPointerException();
    }

(3) Hashtable的方法是同步的,而HashMap的方法不是。有人建议,如果涉及到多线程同步问题时采用Hashtable,没有涉及就采用HashMap,但Collections类提供了一个静态方法:synchronizedMap(),该方法创建了一个线程安全的Map对象,并把它作为一个封装的对象来返回,所以,通过Collections类的synchronizedMap方法是可以同步访问潜在的HashMap的。

    // HashMap的put方法
    public V put(K key, V value) {

    // Hashtable的put方法
    public synchronized V put(K key, V value) {

(4) Hashtable默认初始容量11,HashMap默认初始容量是16。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值