学习应该由简到繁,先把这些简单一点的向Queue队列的集合框架学会,再去学Collection以及Map。
首先看一下队列的实现,因为集合是存储元素的东西,队列也可以存储元素,所以队列也是集合的一种实现,Java中的队列有两种实现方式,一种是Array,一种是List,也不能说只有队列的实现方式有这两种,在计算机存储元素的时候,要么是数组存储,要么是链表存储,所以队列只是一个例子而已,就算学到后边的Collection以及Map,涉及到底层也逃不出数组和链表的影子,在这里就不详细介绍数组和链表了,我们看一下在java中实现Queue的API。
在java中,队列通常有两种实现方式,一种是使用循环数组(ArrayDeque),一种是使用链表(LinkedList)。
通常情况下,数组的实现效率要高于链表,但是数组有一个尤为致命的缺点,就是要提前知道数组的大小。所以如果在容量已知的前提条件下,可以优先考虑使用ArrayDeque的数组实现队列,否则还是老老实实的使用链表(LinkedList)实现的队列。至于他们的API方法,在这里就不详细的展开了。无非就是增删改查操作。
下面详细介绍一下Collection和Map集合框架。
首先上图:
这个图是拷贝的网上的,是说明了Collection以及Map之间的继承关系,以及各自的实现类,这些类的继承关系搞懂,不然之后也会学模糊。
由简到繁。先说一下Iterator迭代器吧,迭代器就是遍历集合元素的一种方式,遍历集合有好多种方式,迭代器是为我们提供的其中一种遍历方式而已,Iterator包含了4个方法:
E next();
boolean hasNext();
void remove();
default void forEachRemaining(Consumer<? super E> action);
至于我们常用的也就前三个方法,通过反复调用next方法,可以逐个访问集合中的元素。但是如果到了集合的末端,next方法就会抛出异常,所以在next调用之前先用hasNext方法判断一下是否含有下一个元素。
Collection<String> c = ...;
Iterator<String> iter = c.iterator();
while(iter.hasNext()){
String element = iter.next();
//doSomething
}
大致的使用就是上面的了,可以在doSomething处填写你的逻辑代码。
迭代器的位置有很多人事搞不清楚的,只知道代码是这样写,但是不知道每次next后,迭代器的位置在哪里。我说一下,应该将Java迭代器认为是位于两个元素之间。当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用,remove的元素是返回的元素。
需要注意的是,如果调用remove之前没有调用next方法将是不合理的,会抛异常。所以在remove方法之前必须先next。
List
:有序可重复。可以含有null元素。
List的实现类:ArrayList
、LinkedList
、Vector
ArrayList
:底层数据结构使数组结构array,查询速度快,增删改慢,因为是一种类似数组的形式进行存储,因此它的随机访问速度极快;线程不安全。扩容操作:扩容当前数组大小的一半。
Vector
:底层是数组结构array,与ArrayList相同,查询速度快,增删改慢,直接在方法上加Synchronized保证线程安全。扩容操作:如果在构造方法中没有传入扩容的大小,默认扩容当前数组的一倍。
LinkedList
:底层使用链表结构,增删速度快,查询稍慢;线程不安全
为了保证List的线程安全,可以有三种做法:
1,使用Vector
,直接在方法上加Synchronized,效率较低
2,使用集合类工具Collections的方法:static <T> List<T> synchronizedList(List<T> list)
3,写时复制类CopyOnWriteArrayList
,底层使用Lock锁。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将容器Object[]进行拷贝,复制出一个新的容器Object[] newElements,然后新的容器里添加元素,添加完之后,再将原容器的引用指向新的容器,这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁。所以CopyOnWrite容器也是一种读写分离的思想。推荐使用这种方法。
Set
:无序不可重复。相当于Map中的Key
HashSet
:最常用的实现类,底层是用了哈希表(散列/hash)算法。底层其实也是一个数组,存在的意义是提供查询速度,插入的速度也是比较快,但是适用于少量数据的插入操作。线程不安全。
LinkedHashSet
(可在LRU算法中使用):继承了HashSet类,底层用的也是哈希表的数据结构,但因为保持数据的先后添加顺序,所以又加了双向链表结构,效率较较低。线程不安全。
TreeSet
:TreeSet是通过TreeMap实现的一个有序的、不可重复的集合,底层维护的是红黑树结构,是可以进行排序的Set集合,如果需要排序的元素是自定义的,那么需要我们自己定义排序方法(重写Comparable#compareTo()方法)。要注意的是在TreeSet集合中只能存储相同类型对象的引用。线程不安全。
解决线程不安全:
使用Collections工具类提供的方法: static Set synchronizedSet(Set s)。
Map
:java的Map是一种把键对象和值对象进行映射的集合,通过key取value,其中每一个元素都包含了键对象和值对象,Map中的键是唯一的,但值可以不唯一。
Map集合有两种实现,一种是利用哈希表来完成的叫做HashMap,它和HashSet都是利用哈希表来完成的,区别其实就是在哈希表的每个桶中,HashSet只有key,而HashMap在每个key上挂了一个value;另一种就是TreeMap,它实现了SortMap接口,也就是使用了红黑树的数据结构,和TreeSet一样也能实现自然排序和客户化排序两种排序方式,而哈希表不提供排序。
Map常用方法:
void clear():删除该Map对象中所有键值对;
boolean containsKey(Object key):查询Map中是否包含指定的key值;
boolean containsValue(Object value):查询Map中是否包含一个或多个value;
Set entrySet():返回map中包含的键值对所组成的Set集合,每个集合都是Map.Entry对象。
Object get():返回指定key对应的value,如果不包含key则返回null;
boolean isEmpty():查询该Map是否为空;
Set keySet():返回Map中所有key组成的集合;
Collection values():返回该Map里所有value组成的Collection。
Object put(Object key,Object value):添加一个键值对,如果集合中的key重复,则覆盖原来的键值对;
void putAll(Map m):将Map中的键值对复制到本Map中;
Object remove(Object key):删除指定的key对应的键值对,并返回被删除键值对的value,如果不存在,则返回null;
boolean remove(Object key,Object value):删除指定键值对,删除成功返回true;
int size():返回该Map里的键值对个数;
HashMap
:哈希表的实现原理中,先采用一个数组表示位桶,每个位桶的实现在1.8之前都是使用链表,但当每个位桶的数据较多的时候,链表查询的效率就会不高,因此在1.8之后,当位桶的数据超过阈值(8)的时候,就会采用红黑树来存储该位桶的数据(在阈值之前还是使用链表来进行存储),所以,哈希表的实现包括数组+链表+红黑树,在使用哈希表的集合中我们都认为他们的增删改查操作的时间复杂度都是O(1)的,不过常数项很大,因为哈希函数在进行计算的代价比较高,HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap 的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。线程不安全。
TreeMap
:TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。TreeMap 实现了Cloneable接口,意味着它能被克隆。TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。线程不安全。TreeMap有以下几个特点:
不允许出现重复的key;
可以插入null键,null值;
可以对元素进行排序;
无序集合(插入和遍历顺序不一致)
TreeMap
基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。线程不安全
HashTable
:Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value,线程安全。
解决线程不安全:
1.使用HashTable
,底层在方法上直接使用Synchronizes,效率低
2.使用并发工具类:ConcurrentHashMap
。
先写到这里,各个实现类的细节,另写一篇文章进行分析。