java集合框架主要分为实现了Collection接口的List和Set、映射接口Map。
|-- List 有序,元素都有索引,可重复。
|-- Set 无序,不可以存储重复的元素。
|-- Map中的每个元素包含一个Key和一个对应的Value。
下图是java集合框架的体系结构
Collection接口
Collection接口是最基本的集合接口,他不提供直接的实现。
Collection所表示的是一种规则,它所包含的元素都必须包含一条或多条原则.eg:有些允许重复,有些不允许重复;有些要求按照顺序插入而有些则是散列;有些支持排序有些不支持。
所有实现了Collection接口的类都必须提供两套标准的构造函数,一个是无参,用于创建一个空的Collection;一个是带有Collection参数的有参构造函数,用于创造一个新的Collection,这个心得Collection与传进来的Collection具有相同的元素。
Collection接口的一些公有方法
主要就是增、删、判断、获取、取交集、变数组等。
List
List接口为Collection的直接接口。List代表的是有序的Collection,它用某种特定的顺序来维护元素的插入顺序。用户可对每个元素的精确的插入位置进行精确的控制,同时根据元素的索引获取元素。实现了List接口的集合主要有:ArrayList、LinkedList、Vector、Stack.
|--Vector 线程同步,增删查询都很慢。
|-- ArrayList 线程不同步,动态数组,ArrayList代替了Vector,允许插入null值,初始大小为10,擅长随机访问。
|-- LinkedList 线程不同步,双向链表,增删元素非常快。
|--Stack 继承自Vector,实现一个后进先出的堆栈。Stack在Vector的基础上增加了5个方法,使得其变为栈。Push、Pop、peek(获取栈顶)、empty、search(检测一个元素是否在堆中).
set
Set是一种不包括重复元素的Collection,他维持他的内部排序,允许null但仅能有一个,传入Set的元素必须不同。实现Set接口的集合有:EnumSet、HashSet、TreeSet.
|--EnumSet 枚举专用的Set,所有元素都是枚举类型。
|-- HashSet 底层是哈希表,线程不同步,无序,高效。HashSet堪称查询最快的集合,因为其内部是以HashCode实现的。它内部的顺序有哈希码来决定,所以不保证Set的迭代顺序。
|-- LinkedHashSet 有序 ,HashSet的子类。
|-- TreeSet 底层是二叉树, 线程不同步。 基于TreeMap,生成一个总是处于排序状态的Set,内部以TreeMap来实现,利用元素的自然顺序对元素进行排序,或者根据创建时提供的Comparator进行排序
Map
是由一系列键值对组成的集合,提供Key到Value的映射,不能存在相同的Key,实现Map的有:HashMap、TreeMap、HashTable、Properties、EnumMap . Map要保证键的唯一性。
|-- HashTable 底层哈希表,线程同步,不能存在Null键,Null值。线程同步,以哈希表的数据结构实现,解决冲突时与HashMap一样也是采用散列表的形式,不过性能比HashMap要低。
|--HashMap 底层哈希表,线程不同步,不能存在Null键,Null值。 以哈希表数据结构实现,查找对象时通过hash值计算其位置,它是为快速查询而设计的,内部定义了一个Hash表数组(Entry[] table),元素会通过hash转化函数转为在数组中的索引,如果有冲突的,则用散列链表的形式串起来。
|--LinkedHashMap Map接口的哈希表和链接列表实现,具有预知的迭代顺序。
|-- TreeMap 底层二叉树,可以对Map集合中的键进行制定顺序的排序。键以某种排序规则排序,内部以Red-balck(红-黑树)数据结构实现。实现了SortedMap接口。
(Queue
队列,主要分为两大类,一类是阻塞式队列,队列满了以后在插入会出现异常,ArrayBlockQueue、PriorityBlockingQueue、LinkedBlockingQueue。另一种队列则是双端队列,支持在头、尾两端插入和移除元素,主要包括:ArrayDeque、LinkedBlockingDeque、LinkedList。)
下面按顺序对每一个容器进行详细讲解:
List --> Vector :
可以使用整数索引访问,可以看成数组的访问方式,但是与数组不同的是Vector大小会自动增长。
由 Vector 的 iterator 和 listIterator 所返回的迭代器是快速失败的:如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。
Vector 的 elements 方法返回的 Enumeration 不是 快速失败的。
点击查看ConcurrentModificationException和Fail-Fast机制
List --> ArrayList :
实现了List接口,底层采用数组保存所有元素;动态数组,ArrayList是List接口的可变数组的实现;允许null值;不是同步的;实现了Cloneable()接口,级覆盖了clone()方法,能够被克隆。
1.set(int index, E element):该方法首先调用rangeCheck(index) 来校验 index 变量是否超出数组范围,超出则抛出异常。而后,取出原 index 位置的值,并且将新的 element 放入 Index 位置,返回 oldValue。
2.add(E e):该方法是将指定的元素添加到列表的尾部。当容量不足时,会调用 grow 方法增长容量。
3.add(int index, E element):在 index 位置插入 element。
4.addAll(Collection<? extends E> c) 和addAll(int index, Collection<? extends E> c) :将特定 Collection中的元素添加到 Arraylist 末尾。
数组扩容有两个方法,1:通过一个 public 的方法ensureCapacity(int minCapacity) 来增加 ArrayList 的容量。2:而在存储元素等操作过程中,如果遇到容量不足,会调用priavte方法private void ensureCapacityInternal(int minCapacity) 实现。
数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的 1.5 倍.
List --> LinkedList:
LinkedList 与 ArrayList一样,都实现了List接口,但内部的数据结构有本质的不同,LinkedList 是基于链表实现的,所以在类中包含了first和last两个指针,所以插入删除效率高,但随机访问效率低。
Set --> HashSet :
HashSet是基于HashMap实现的,底层是采用HahsMap来保存数据的,HashSet中会有一个HashMap成员,在构造函数中对其初始化。因为HashSet是基于HashMap实现的,所以关于HashSet的操作基本都是调用HashMap的方法来实现的。但是我们需要覆盖HashCode()和Equals()方法(用来保证放入的对象的唯一性)。
Set --> HashSet -->LinkedHashSet:
LinkedHashSet 继承HashSet、又基于LinkedHashMap来实现。
LinkedHashSet 是一个Set的实现,存的不是键值对,而是值。LinkedHashSet是不同步的。
LinkedHashSet 是Set的一个具体实现,其维护着一个运行于所有条目的双重链接列表,此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或访问顺序。
Set --> TreeSet :
不同步,当至少有一个线程修改了改set,则必须对外保持同步,一般是通过对自然封装该set的对象执行同步操作来完成,如果不存在这样的对象,则应该使用Collections.synchronizedSortedSet 方法来“包装”该 set。此操作最好在创建时进行,以防止对 set 的意外非同步访问:
SortedSet s=Collections.synchronizedSortedSet(new TreeSet(...)); 此类的iterator方法返回的迭代器是 快速失败 的。
Map --> HashTable:
任何非null的对象都可以做键或值,为了成功的在哈希表中存储和获取对象,用作键的对象必须实现hashCode()和equals()方法。
Map --> HashMap:
基于哈希表的Map实现,HashMap实际上是一个链表散列的数据结构,即数组和链表的结合体。底层是一个数组结构,而每个存储空间又是一个链表,当创建HashMap的时候会初始化一个数组。
允许存放Null Key和Null Value,当我们保存时,如果Key已经存在,则心得Value会覆盖旧的Value ,当我们执行put(此映射中关联指定值与指定键)操作时,先根据key的hashCode()计算出hash值,根据hash值得到这个元素在数组中的下标,如果数组中这个位置已经有元素了,则以链表的形式存放,新加入的放在链头,否则直接放到该位置。
确定存储Key-Value时,不考虑Value值,仅仅根据Key决定位置。
总结:HashMap在底层将Key-Value当成一个整体进行处理,这个整体是一个Entry对象,HashMap底层采用一个Entry[]数组来保存所有的Key-Value对。
当HashMap中的元素越来越多时,Hash冲突的机率越来越高。为了提高查询效率,就要对HashMap扩容,但是HashMap扩容是很消耗性能的:原数组中的数据必须重新计算其在新数组中的位置,并放进去。数组自动扩容(扩展两倍)时发生在:元素个数 > 数组长度(默认是16) * 负载因子(默认是0.75),当然,默认值可在初始化时像构造中传递参数。
负载因子是衡量一个散列表空间的散列程度的,负载因子越大表示填充的越满。(对使用链表法的散列表来说:负载因子越大,空间利用越充分,但查询效率就会降低)。
HashMap的遍历方式:
第一种:
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue(); }
}
第二种:
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
第一种效率比第二种高。
Map --> HashMap --> LinkedHashMap:
此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedMap
方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射的意外的非同步访问:
Map m=Colections.synchronizedMap(new LinkedHashMap(...));
Map --> TreeMap:
基于红-黑树(Ren-Black tree)实现。该映射根据其键的自然映射展开,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。
不同步,当至少有一个线程修改了改set,则必须对外保持同步,一般是通过对自然封装该set的对象执行同步操作来完成,如果不存在这样的对象,则应该使用Collections.synchronizedSortedSet 方法来“包装”该 set。此操作最好在创建时进行,以防止对 set 的意外非同步访问:
SortedMap m=Collections.synchronizedSortedMap(new TreeMap(...)); 此类的iterator方法返回的迭代器是 快速失败 的。