1.java集合类图
1.1
上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,而点线边框的是接口,比如Collection,Iterator,List等。
发现一个特点,上述所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。
还有一个特点就是抽象类的使用。如果要自己实现一个集合类,去实现那些抽象的接口会非常麻烦,工作量很大。这个时候就可以使用抽象类,这些抽象类中给我们提供了许多现成的实现,我们只需要根据自己的需求重写一些方法或者添加一些方法就可以实现自己需要的集合类,工作流昂大大降低。
2.详解
2.1ArrayList
ArrayList实现了List接口,是顺序容器,允许放入null元素,底层通过数组实现。集合中元素被访问的顺序取决于集合的类型。如果对ArrayList进行访问,迭代器将从索引0开始,每迭代一次,索引值加1。每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。
add()
跟C++ 的vector不同,ArrayList没有bush_back()方法,对应的方法是add(E e),ArrayList也没有insert()方法,对应的方法是add(int index, E e)。这两个方法都是向容器中添加新元素,这可能会导致capacity不足,因此在添加元素之前,都需要进行剩余空间检查,如果需要则自动扩容。扩容操作最终是通过grow()方法完成的。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//原来的3倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//扩展空间并复制
}
2.2HashMap
HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对
HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。
2.3HashSet
对于HashSet 而言,它是基于HashMap 实现的,HashSet底层使用HashMap 来保存所有元素,因此HashSet 的实现比较简单,相关HashSet 的操作,基本上都是直接调用底层HashMap 的相关方法来完成。
HashSet保持数据唯一:
HashMap 的 put() 方法添加key-value 对时,当新放入 HashMap 的 Entry 中 key 与集合中原有 Entry 的 key 相同(hashCode()返回值相等,通过equals 比较也返回 true),新添加的 Entry 的 value 会将覆盖原来 Entry 的 value(HashSet 中的 value 都是PRESENT),但 key 不会有任何改变,因此如果向 HashSet 中添加一个已经存在的元素时,新添加的集合元素将不会被放入 HashMap中,原来的元素也不会有任何改变,这也就满足了 Set 中元素不重复的特性。
2.4LinkedList
LinkedList 和 ArrayList 一样,都实现了 List 接口,但其内部的数据结构有本质的不同。LinkedList 是基于链表实现的(通过名字也能区分开来),所以它的插入和删除操作比 ArrayList 更加高效。但也是由于其为基于链表的,所以随机访问的效率要比 ArrayList 差。
LinkLedist实现:
1. LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
2. LinkedList 实现 List 接口,能对它进行队列操作。
3. LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
4. LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
5. LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
6. LinkedList 是非同步的。
2.5TreeMap
传送门TreeMap详解
2.6TreeSet
TreeSet的底层是通过TreeMap实现的。TreeSet并不是根据插入的顺序来排序,而是根据实际的值的大小来排序。TreeSet()是使用二叉树的原理对新add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
Integer和String对象都可以进行默认的TreeSet排序,而自定义类的对象是不可以的,自己定义的类必须实现Comparable接口,并且覆写相应的compareTo()函数,才可以正常使用。
3.比较
3.1HashMap和Hashtable的区别
1.Hashtable是基于陈旧的Dictionary类的,HashMap是java 1.2引进的Map接口的一个实现。
2.Hashtable是线程同步的。这个类中的一些方法保证了Hashtable中的对象是线程安全的。而HashMap则是线程异步的,因此HashMap中的对象并不是线程安全的。
3.HashMap可以让你将空值作为一个表的条目的key或value但是Hashtable是不能放入空值的(null)。
3.2Vector和ArrayList的区别
1.Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。
2.ArrayList和Vector的扩展数组的大小不同。ArrayList在内存不够时默认是扩展50%+ 1个,Vector是默认扩展1倍。
3.3HashTable和ConcurrentHashMap的区别
1.HashTable使用的对整个Map的锁,只能支持单线程操作Map。而ConcurrentHashMap使用的是分段锁,同时支持16个线程读写Map容器。
参考HashTable和ConcurrentHashMap的区别
总结
Java中的List/Set和Map的区别:
List按对象进入的顺序保存对象,不做排序和编辑操作。
Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set而不关心它的顺序--否则使用List)。
Map同样对每个元素保存一份,但这是基于"键"(key)的,Map也有内置的排序,因而不关心元素添加的顺序。
如果添加元素的顺序对程序设计很重要,应该使用LinkedHashSet或者LinkedHashMap。
List的功能方法
实际上有两种List:一种是基本的ArrayList其优点在于随机访问元素,另一种是更强大的LinkedList它并不是为快速随机访问设计的,而是具有一套更通用的方法。
List:次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用)一个List可以生成Listlterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。
ArrayList:由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速率很慢。Listlterator只应该用来由后向前遍历ArrayList。而不是用来插入和移除元素。因为那比LinkedList开销要大很多。
LinkedList:对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替)还具有下列方法:addFirst(),addLast(),getFirst(),getLast(),removeFirst()和removeLast()这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
Set的功能方法
Set具有与Collection完全一样的接口,因此没有任何额外的功能,不象前面有两个不同的List。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素(至于如何判断元素相同则较为负责)
Set:存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必需定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
TreeSet:保存次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
Map的功能方法
方法put(Objectkey,Object value)添加一个"值"(想要得东西)和与"值"相关的"键"(key)(使用它来查找)。方法get(Object key)返回与给定"键"相关联的"值"。可以用containsKey()和containsValue()测试Map中是否包含某个"键"或"值"。标准的java类库中包含了几种不同的Map:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ldentityHashMap。它们都有同样的基本接口Map,但是行为、效率、排序策略、保存对象的生命周期和判定"键"等价的策略等各不相同。
执行效率是Map的一个大问题。看看get()要做哪些事,就会明白为什么在ArrayList中搜索"键"是相当慢的。这正是HashMap提高速度的地方。HashMap使用了特殊的值,称为"散列码"(hash code),来取代对键的缓慢搜索。"散列码"是"相对唯一"用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。所有java对象都能产生散列码,因为hashCode()是定义在基类Object中的方法。
HashMap就是使用对象的hashCode()进行快速查询的。此方法能够显著提高性能。
Map:维护"键值对"的关联性,使你可通过"键"查找"值"
HashMap: Map基于散列表的实现。插入和查询"键值对"的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得"键值对"的顺序是其插入次序,或者是最近最少使(LRU)的次序。只能HashMap慢一点。而在迭代访问时发而更快,因为它使用键表维护内部次序。
TreeMap:基于红黑树数据结果的实现。查看"键"或"键值对"时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。