Java集合Collection和Map分析解读

来源:互联网 发布:居民社保退休工资算法 编辑:程序博客网 时间:2024/05/22 13:49

Collection是一个集合接口,结构图如下:

Collection

解释一下上面这张图。带o-的是接口,实线是继承关系。是继承箭头所指的接口。虚线是实现关系,实现了箭头所指的接口。


常用集合类的继承结构如下:
Collection(接口)<–List(接口)<–Vector(实现类)
Collection(接口)<–List(接口)<–ArrayList (实现类)
Collection(接口)<–List(接口)<–LinkedList (实现类)
Collection(接口)<–Queue(接口)<–LinkedList(实现类)
Collection(接口)<–Queue(接口)<–DeQue(接口)<–LinkedList(实现类)
Collection(接口)<–Set(接口)<–HashSet (实现类)
Collection(接口)<–Set(接口)<–HashSet<–LinkedHashSet(实现类)
Collection(接口)<–Set(接口)<–SortedSet<–TreeSet (实现类)
Map(接口)<–SortedMap(接口)<–TreeMap (实现类)
Map(接口)<–HashMap (实现类)

注意:现在Java不推荐使用Stack(栈),而是采取Deque接口来实现栈


依次解释Collection的继承接口和实现类


List:

List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下 标)来访问List中的元素,这类似于Java的数组。


实现类:ArrayList,LinkedList,Vector


Vector:(动态数组,数据结构的中线性表)
基于数组(Array)的List,其实就是封装了数组所不具备的一些功能方便我们使用,所以它难易避免数组的限制,同时性能也不可能超越数组。所以,在可能的情况下,我们要多运用数组。另外很重要的一点就是Vector是线程同步的(sychronized)的,这也是Vector和ArrayList 的一个的重要区别。


ArrayList: (动态数组,数据结构的中线性表)
同Vector一样是一个数组,但是不同的是ArrayList不是同步的。所以在性能上要比Vector好一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。

注意:ArrayList和vector在内部其实都是由数组实现的。只不过它封装了一些方法使得避免了数组存在的扩容缩容插入等操作的复杂性,只需要调用类的方法就可以快速的操作的数组。
可以看看ArrayList的其中一个构造方法源码就知道了
源码:

  public ArrayList(int initialCapacity) {//这里的initialCapacity是指初始容量        super();        if (initialCapacity < 0)//如果初始容量小于零,则抛出异常            throw new IllegalArgumentException("Illegal Capacity: "+                                               initialCapacity);        this.elementData = new Object[initialCapacity];//构建这个初始容量的数组给ArrayList    }

LinkedList: (数据结构中的双向链表,也是栈和队列)
由节点链成
它每一个节点(Node)(LinkedList的内部类)都包含三方面的内容:
1.上一个节点的信息(Node prev)
1.节点本身的数据(E item);
2.下一个节点的信息(nextNode)。
源码:

public class LinkedList<E>    extends AbstractSequentialList<E>    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{    transient int size = 0;//容量    transient Node<E> first;//头结点    transient Node<E> last;//尾结点    public LinkedList() {    }    public LinkedList(Collection<? extends E> c) {        this();        addAll(c);    }    .......     private static class Node<E> {//内部类(结点)        E item;//内容        Node<E> next;//下一个结点        Node<E> prev;//上一个结点        Node(Node<E> prev, E element, Node<E> next) {            this.item = element;            this.next = next;            this.prev = prev;        }    }    ......    }

所以当对LinkedList做添加,删除动作的时候就不用像基于数组的ArrayList一样,必须进行大量的数据移动。只要更改next和prev的相关信息就可以实现了,这是LinkedList的优势。


Queue:(数据结构中的队列)
因为队列经常的操作是入队(在尾节点添加)出队(删除头结点)
这种增删操作,所以它的实现类直接利用的LinkedList,只对LinkedList做入队和出队的操作就成了队列(双向队列)


Deque:(数据结构中的栈)
因为栈经常的操作是入栈(在尾节点添加)出栈(删除尾结点)
这种增删操作,所以它的实现类直接利用的LinkedList,只对LinkedList做入栈和出栈的操作就成了队列(双向栈)


List总结:
所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ]

所有的List中可以有null元素,例如[ tom,null,1 ]

基于Array的List(Vector,ArrayList)适合查询,而LinkedList 适合添加,删除操作


Set:
Set是一种不包含重复的元素的无序Collection。


HashSet:
虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是在 HashMap的基础上来实现的。看HashMap源码的构造方法一目了然。

public HashSet() {        map = new HashMap<>();    }

HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。
看HashMap源码的add方法一目了然。

 public boolean add(E e) {        return map.put(e, PRESENT)==null;    }

这个也是为什么在Set中不能像在List中一样有重复的项的根本原因,因为HashMap的key是不能有重复的。


LinkedHashSet:
HashSet的一个子类,一个链表。


TreeSet:
SortedSet的子类,它不同于HashSet的根本就是TreeSet是有序的。它是通过SortedMap来实现的。


Set总结:
Set实现的基础是Map(HashMap)

Set中的元素是不能重复的,如果使用add(Object obj)方法添加已经存在的对象,则会覆盖前面的对象


Map:


Map结构图


Map 是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。对于键对象来说,像Set一样,一个 Map容器中的键对象不允许重复,这是为了保持查找结果的一致性;如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。当然在使用过程中,某个键所对应的值对象可能会发生变化,这时会按照最后一次修改的值对象与键对应。对于值对象则没有唯一性的要求,你可以将任意多个键都映射到一个值对象上,这不会发生任何问题(不过对你的使用却可能会造成不便,你不知道你得到的到底是那一个键所对应的值对象)。


Map有两种比较常用的实现:HashMap和TreeMap。


HashMap:

HashMap提供了三个构造函数:

HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。

HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。

HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。

在这里提到了两个参数:初始容量,加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况下我们是无需修改的。
Map数据结构

从上图我们可以看出HashMap底层实现还是数组,只是数组的每一项都是一条链。其中参数initialCapacity就代表了该数组的长度。下面为HashMap构造函数的源码:

public HashMap(int initialCapacity, float loadFactor) {        //初始容量不能<0        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal initial capacity: "                    + initialCapacity);        //初始容量不能 > 最大容量值,HashMap的最大容量值为2^30        if (initialCapacity > MAXIMUM_CAPACITY)            initialCapacity = MAXIMUM_CAPACITY;        //负载因子不能 < 0        if (loadFactor <= 0 || Float.isNaN(loadFactor))            throw new IllegalArgumentException("Illegal load factor: "                    + loadFactor);        // 计算出大于 initialCapacity 的最小的 2 的 n 次方值。        int capacity = 1;        while (capacity < initialCapacity)            capacity <<= 1;        this.loadFactor = loadFactor;        //设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作        threshold = (int) (capacity * loadFactor);        //初始化table数组        table = new Entry[capacity];        init();    }

从源码中可以看出,每次新建一个HashMap时,都会初始化一个table数组。table数组的元素为Entry节点。

static class Entry<K,V> implements Map.Entry<K,V> {        final K key;        V value;        Entry<K,V> next;        final int hash;        /**         * Creates new entry.         */        Entry(int h, K k, V v, Entry<K,V> n) {            value = v;            next = n;            key = k;            hash = h;        }        .......    }

其中Entry为HashMap的内部类,它包含了键key、值value、下一个节点next,以及hash值,这是非常重要的,正是由于Entry才构成了table数组的项为链表。


其它:
一、几个常用类的区别
1.ArrayList: 元素单个,效率高,多用于查询
2.Vector: 元素单个,线程安全,多用于查询
3.LinkedList:元素单个,多用于插入和删除
4.HashMap: 元素成对,元素可为空
5.HashTable: 元素成对,线程安全,元素不可为空

二、Vector、ArrayList和LinkedList
大多数情况下,从性能上来说ArrayList最好,但是当集合内的元素需要频繁插入、删除时LinkedList会有比较好的表现,但是它们三个性能都比不上数组,另外Vector是线程同步的。所以:
如果能用数组的时候(元素类型固定,数组长度固定),请尽量使用数组来代替List;
如果没有频繁的删除插入操作,又不用考虑多线程问题,优先选择ArrayList;
如果在多线程条件下使用,可以考虑Vector;
如果需要频繁地删除插入,LinkedList就有了用武之地;
如果你什么都不知道,用ArrayList没错。