Java集合类总结笔记

一、集合类的层次关系

主要容器集合类的特点:

ArrayList 一种可以动态增长和缩减的索引序列

LinkedList 一种可以在任何位置进行高效地插入和删除的有序序列

ArrayDeque 一种用循环数组实现的双端队列

HashSet 一种没有重复元素的无序集合

TreeSet 一种有序集

EnumSet 一种包含枚举类型值的集

LinkedHashSet 一种可以记住元素插入次序的集

PriorityQueue 一种允许高效删除最小元素的集合(参考 堆)

HashMap 一种存储键/值关联的数据结构

TreeMap 一种键值有序排列的映射表

EnumMap 一种键值属于枚举类型的映射表

LinkedHashMap 一种可以记住键/值项添加次序的映射表

WeakHashMap 一种其值没有用处之后可以被GC回收的映射表

IdentityHashMap 一种用==,而不是equals比较键值的映射表

以上这些集合类的继承关系如下图所示:

02aeca62e6a1213da2e578fe782541f6a2e.jpg

如上图所示,实线边框的是实现类,折线边框的是抽象类,而点线边框的是接口。

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口,就是Set和List(Queue也是继承了Collection接口)。Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

Iterator,所有的集合类都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:

1.hasNext()是否还有下一个元素。

2.next()返回下一个元素。

3.remove()删除当前元素

常用的一些集合的框架图

4c5cf1e6e0dd74e9da94d1b5ab6163eb878.jpg

 

二、几种重要的接口和类简介

1、List(有序、可重复)

List里存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。

2、Set(无序、不能重复)

Set里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。

3、Map(键值对、键唯一、值不唯一)

Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。

三、集合类的遍历

在类集中提供了以下四种的常见输出方式:

1)Iterator:迭代输出,是使用最多的输出方式。

2)ListIterator:是Iterator的子接口,专门用于输出List中的内容。

  1. foreach:JDK1.5之后提供的新功能,可以输出数组或集合。

4)for循环。

四、集合类内部源码分析

如果你没看过JDK源码,建议打开IDEA边看源码边看这篇文章,看过的可以把这篇文章当成是知识点备忘录。

JDK容器类中有大量的空指针、数组越界、状态异常等异常处理,这些不是重点,我们关注的应该是它的一些底层的具体实现。

可以参考这个博客,对于集合类写得非常详细:

http://www.cnblogs.com/skywang12345/p/3323085.html

我根据源码以及参考了以上博客,把我认为比较重要的内容记录在这里。

4.1 ArrayList

4.1.1 介绍

ArrayList 是一个数组队列,相当于动态数组。与Java中的普通数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

  1. ArrayList 继承了AbstractList,实现了List接口。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  2. ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。
  3. ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
  4. ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。

4.1.2 源码总结

  1. ArrayList 实际上是通过一个数组去保存数据的。当我们构造ArrayList时;若使用默认构造函数,则ArrayList的默认容量大小是10
  2. 当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”,当数组容量扩增到Integer.MAX_VALUE-8的时候,就会开始限制数组扩充,超过Integer.MAX_VALUE的时候,抛内存溢出异常;
  3. ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
  4. ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
  5. ArrayList 中实现了ListIterator迭代器,它和普通迭代器类似,只是多了双向迭代的功能(不仅有hasNext(),还有hsaPrevious(),可以从后向前遍历)。
  6. 迭代器中的expectedModCount和modCount:迭代器初始化时,会用modCount去初始化expectedModCount,在迭代器的操作中都要检查这两个值是否相等,若不相等则抛出ConcurrentModificationException异常表示当前List正在被修改,原因在于List本身的add和remove操作时都会修改modCount的值,如果在迭代器循环迭代过程中调用了List本身的add和remove方法就会导致两个值不相等而抛出异常,但是迭代器本身的remove则不会有这个问题,它会在remove完成后重新更新一遍expectedModCount保持两个值的等值性。
  7. ArrayList本身的sort其实是调用Arrays.sort传入比较器实现的。

4.1.3 遍历

ArrayList支持3种遍历方式:

(1)迭代器遍历(Iterator)

(2)随机访问,通过索引值遍历(因为实现了RandomAccess接口)

Integer value = null;

int size = list.size();

for (int i=0; i<size; i++) {

    value = (Integer)list.get(i);        

}

(3)增强for循环遍历

Integer value = null;

for (Integer integ:list) {

    value = integ;

}

用这三种方式遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低。

4.2 LinkedList

4.2.1 介绍

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。

LinkedList 实现了List 接口,能对它进行队列操作。

LinkedList 实现了Deque 接口,即能将LinkedList当作双端队列使用。

LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。

LinkedList 实现了java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList 是线程不安全的。

4.2.2 源码总结

1. LinkedList 实际上是通过双向链表去实现的,它支持链表头增删,链表尾增删,指定节点前或后增删。它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。

2. LinkedList不存在LinkedList容量不足的问题,不像ArrayList一样需要扩容,维护好指针的指向关系即可。

3. LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。

4. LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。

5. LinkedList中的Element元素支持插入空值,和HashMap一样。

6. LinkedList的迭代器中也有expectedModCount检测链表的修改状态,迭代器循环迭代时只能用迭代器的remove方法。

7. 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、删除和获取元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。总结起来如下表格:

 

针对第一个元素(头部)

针对第一个元素(头部)

针对最后一个元素(尾部)

针对最后一个元素(尾部)

 

抛异常

返回特殊值

抛异常

返回特殊值

插入

addFirst(e)

offerFirst(e)

addLast(e)

offerLast(e)

删除

removeFirst()

pollFirst()

removeLast()

pollLast()

获取

getFirst()

peekFirst()

getLast()

peekLast()

8. LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:

队列中的方法

LinkedList中的等效方法

add(e)

addLast()

offer(e)

offerLast()

remove()

removeFirst()

poll()

pollFirst()

element()

getFirst()

peek()

peekFirst()

9. LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:

栈中的方法

LinkedList中的等效方法

push(e)

addFirst()

pop()

removeFirst()

peek()

peekFirst()

4.2.3 遍历

LinkedList支持以下几种方式遍历:

1.通过迭代器遍历,即通过Iterator去遍历。

2.通过快速随机访问遍历LinkedList

int size = list.size();

for (int i=0; i<size; i++) {

     list.get(i);        

}

3.通过增强for循环来遍历

4.通过pollFirst()、pollLast()、removeFirst()、removeLast()遍历。如:

while(list.pollFirst() != null) {}

使用removeFirst()或removeLast()效率最高。但用它们遍历时,会删除原始数据;若单纯只读取,而不删除,应该使用增强for循环遍历方式。无论如何,千万不要通过随机访问去遍历,效率很低

4.3 Vector

4.3.1 介绍

Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。

Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。

Vector 实现了RandmoAccess接口,即提供了随机访问功能。

Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。

和ArrayList不同,Vector中的操作是线程安全的

4.3.2 源码总结

  1. Vector实际上是通过一个数组去保存数据的,当我们构造Vecotr时;若使用默认构造函数,则Vector的默认容量大小是10。Vector的数据结构和ArrayList差不多,它包含了3个成员变量:elementData、elementCount和capacityIncrement。elementData 是"Object[]类型的数组",它保存了添加到Vector中的元素。随着Vector中元素的增加,Vector的容量也会动态增长,capacityIncrement是与容量增长相关的增长系数。elementCount 是动态数组的实际大小。
  2. 当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数n>0,则将容量的值增加n;否则,将容量大小增加一倍。
  3. Vector的克隆函数,即是将全部元素克隆到一个数组中。
  4. 它的特点和ArrayList都一样,但是提供了方法粒度级别的同步机制,大量的synchronized方法。

4.3.3 遍历

Vector支持4种遍历方式:

  1. 通过迭代器遍历,即通过Iterator去遍历。
  2. 随机访问,通过索引值去遍历(实现了RandomAccess接口)。
  3. 增强for循环
  4. Enumeration遍历,如:
Integer value = null;

Enumeration enu = vec.elements();

while (enu.hasMoreElements()) {

    value = (Integer)enu.nextElement();

}

遍历Vector,使用索引的随机访问方式最快,使用迭代器最慢。

4.4 Stack

4.4.1 介绍

Stack是栈。它的特性是:先进后出(FILO, First In Last Out)。

Java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,这就意味着,Stack也是通过数组实现的,而非链表。当然,我们也可以将LinkedList当作栈来使用。

4.4.2 源码总结

1. Stack实际上也是通过数组去实现的。

  • 执行push时(即,将元素推入栈中),是通过将元素追加的数组的末尾中。
  • 执行peek时(即,取出栈顶元素,不执行删除),是返回数组末尾的元素。
  • 执行pop时(即,取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。

2. Stack继承于Vector,意味着Vector拥有的属性和功能,Stack都拥有。

3. Stack提供整栈元素搜索功能,查找元素在栈中的位置(由栈底向栈顶数),具体获取元素索引的elementAt()实现也是在Vector.java中。

4.5 HashMap

4.5.1 介绍

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。

HashMap 的实现不是同步的,这意味着它不是线程安全的。

HashMap 的key、value都可以为null。

HashMap中的映射不是有序的。

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量(默认是16,且必须是2的幂次方,最大是2的30次方)。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷(更深层原因是与泊松分布有关)。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

4.5.2 源码总结

1. HashMap中的key-value都是存储在Entry数组中的,Entry 实际上就是一个单向链表。

2. clear() 的作用是清空HashMap。它是通过将所有的元素设为null来实现的。

3. containsKey() 的作用是判断HashMap是否包含key。containsKey() 首先通过getEntry(key)获取key对应的Entry,然后判断该Entry是否为null。getEntry() 的作用就是返回“键为key”的键值对。注意:HashMap将“key为null”的元素都放在table的位置0处,即table[0]中;“key不为null”的放在table的其余位置!

4. containsValue() 的作用是判断HashMap是否包含“值为value”的元素。containsValue()分为两步进行处理:第一,若“value为null”,则调用containsNullValue()。第二,若“value不为null”,则查找HashMap中是否有值为value的节点。

5. get()方法:计算key的hashCode,该hash值对应的链表”上查找“键值等于key”的元素。(JDK1.8中引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,如果有红黑树就按照红黑树的查找方式进行)。

6. put()方法:

  1. hashCode()计算Key的哈希值,从而找到数组中的元素位置,具体的定位方法见:https://www.cnblogs.com/wupa/p/7171143.html
  2. 该位置中没元素,直接放入;
  3. 若该位置中有元素(hash冲突),则调用equals,对比Key是否真的相等,若相等,用新的Value替代旧的Value;
  4. 若key不相等,则把这个元素插入到原来的元素后面(链表)

7. resize():重新调整数组大小,新旧数组复制,较耗时;

8. remove()方法:通过hashCode找到数组位置,对比第一个节点,再对比红黑树或链表,找到节点之后删除节点并返回;

9. HashMap的增删方法也和List一样有ConcurrentModificationException异常,使用迭代器需注意;

10. JDK1.8对HashMap的改进:除了引入红黑树,还改进了扩容机制,利用位计算非常巧妙的回避了再扩容时每个元素重新计算hash,在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。

PS:JDK1.8对HashMap的改进强烈建议看一下这篇博文:

http://blog.csdn.net/zdsicecoco/article/details/51775545

 

11. HashMap实现了Cloneable接口,即实现了clone()方法。clone()方法的作用很简单,就是克隆一个HashMap对象并返回。

12. HashMap实现java.io.Serializable,分别实现了串行读取、写入功能。

4.6 LinkedHashMap

4.6.1 介绍

1. HashMap的子类;与HashMap区别是,在HashMap基础上LinkedHashMap内部多了一个双向循环链表的维护,该链表是有序的,可以按元素插入顺序或元素最近访问顺序(LRU)排列;

2. 存储方式和HashMap都一样,都是直接继承的,只是在put元素的时候顺带存储了元素的存入先后顺序,在迭代器遍历的时候,输出的结果顺序是和插入元素时的插入先后顺序一致的;

3. 线程不安全(有ConcurrentModificationException);

4. 可以设置插入元素时自动删除最旧的元素。

4.7 Hashtable

4.7.1 介绍

1. 和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。

2. Hashtable继承于Dictionary(因为遍历该集合要用到Enumeration迭代器),实现了Map、Cloneable、java.io.Serializable接口。

3. Hashtable的函数都是同步的,这意味着它是线程安全的它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。

4. Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。

5. 遍历迭代器使用Enumeration方式;(Iterator是Enumeration的升级版,在其中加入了ConcurrentModificationException保证Fail-Fast,但是遍历性能却下降了。在HashTable、Vector中已经用synchronized保证了线程安全,故仅需使用更高效的Enumeration遍历即可)

4.8 TreeMap

4.8.1 介绍

1. TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。

2. TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。

3. TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。

4. TreeMap 实现了Cloneable接口,意味着它能被克隆。

5. TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。

6. TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

7. TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。

8. TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。

9. TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量:root、size、 comparator。root 是红黑数的根节点。它是Entry类型,Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。Entry节点根据key进行排序,Entry节点包含的内容为value。 红黑数排序时,根据Entry中的key进行排序;Entry中的key比较大小是根据比较器comparator来进行判断的。size是红黑数中节点的个数。

4.8.2 源码总结

TreeMap的源码大致可以分成几个部分来解读:

  • TreeMap的核心,即红黑树相关部分。
  • TreeMap的主要函数。
  • TreeMap实现的几个接口。
  • TreeMap的其它内容。

1. 红黑树相关内容:

(1)数据结构:

红黑树节点的颜色——红色;

红黑树节点的颜色——黑色;

红黑树节点对应的类——Entry,Entry包含了6个部分内容:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)

Entry节点根据key进行排序,Entry节点包含的内容为value。

(2)相关操作:

包括 左旋、右旋、插入、插入修正、删除、删除修正。

关于红黑树部分的详细内容,可以参考:

https://my.oschina.net/u/3342874/blog/1833893

2. 主要函数:

(1)构造函数

(2)Entry相关函数,包括firstEntry()、lastEntry()、lowerEntry()、higherEntry()、floorEntry()、ceilingEntry()、 pollFirstEntry()、pollLastEntry()等。

(3)key相关函数,包括firstKey()、lastKey()、lowerKey()、higherKey()、floorKey()、ceilingKey()等。

(4)values函数,返回“TreeMap中值的集合”。

(5)entrySet()函数,entrySet()返回“键值对集合”。顾名思义,它返回的是一个集合,集合的元素是“键值对”。

3. 实现的几个接口:

(1)Cloneable接口,支持浅拷贝。

(2)Serializable接口,支持序列化。

(3)NavigableMap接口,支持一系列的导航方法。比如返回有序的key集合。

4. TreeMap的其它内容:

(1)顺序遍历,从第一个元素开始,逐个向后遍历。通过 keyIterator()方法来实现,在这个方法中,先通过getFirstEntry()得到第一个元素作为起始元素,再通过next()不断获取下一个元素。

(2)逆序遍历,从最后一个元素开始,逐个往前遍历。通过descendingKeyIterator()方法来实现,在这个方法中,先通过getLastEntry()得到最后一个元素作为起始元素,再通过next()不断获取下一个元素。

4.8.3 遍历

1.遍历TreeMap的键值对

第一步:根据entrySet()获取TreeMap的“键值对”的Set集合。

第二步:通过Iterator迭代器遍历“第一步”得到的集合。

2.遍历TreeMap的键

第一步:根据keySet()获取TreeMap的“键”的Set集合。

第二步:通过Iterator迭代器遍历“第一步”得到的集合。

3.遍历TreeMap的值

第一步:根据value()获取TreeMap的“值”的集合。

第二步:通过Iterator迭代器遍历“第一步”得到的集合。

4.9 WeakHashMap

4.9.1 介绍

1. WeakHashMap 继承于AbstractMap,实现了Map接口。

2. 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。

3. WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的回收,某个键被终止时,它对应的键值对也就从映射中有效地移除了。

PS:“弱键”是怎么实现的?

通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。

具体实现步骤是:

(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。

(02) 当某“弱键”不再被其它对象引用,并被GC回收时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。

(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。

4. 和HashMap一样,WeakHashMap是不同步的。

5. 主要应用场景:做缓存。

6. WeakHashMap和HashMap都是通过"拉链法"实现的散列表。它们的源码绝大部分内容都一样。

4.10 HashSet

4.10.1 介绍

1. HashSet 是一个没有重复元素的集合。

2. HashSet允许使用 null 元素。

3. HashSet是非同步的。

4. HashSet底层是由HashMap提供支持的,其实就是定义一个私有的HashMap变量,但HashSet中只需要用到key,而HashMap是key-value键值对,因此向map中添加键值对时,值固定用Object类型的叫PRESENT的常量代替。

5. HashSet其实就是利用了HashMap的key的唯一性。

4.11 TreeSet

4.11.1 介绍

1. TreeSet 是一个有序的集合,它的作用是提供有序的Set集合,比如提取比某个值大的子集合,提取在某一范围内的子集合等有序才具有的操作。

2. TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。

3. TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。

4. TreeSet 实现了Cloneable接口,意味着它能被克隆。

5. TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。

6. TreeSet的底层是基于TreeMap实现的。Set中的元素其实就是Map的Key;value用一个Object类型的叫做PRESENT的常量代替。

7. TreeSet中的元素支持2种排序方式:自然排序或者实现Comparable接口(要求compareTo和equals的返回结果一致)。

8. TreeSet为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。

9. TreeSet是非同步的,它的iterator方法返回的迭代器是fail-fast的。

4.12 Queue

单向队列接口,队头取出,队尾加入。实现子类是LinkedList

4.13 Deque

双向队列接口,双向添加删除,分别有两个方向的迭代器。继承自Queue,实现子类也是LinkedList

五、一些集合类的比较

5.1 List总结

1.ArrayList, LinkedList, Vector, Stack是List的4个实现类。

(1)ArrayList 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。

(2)LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率低。

(3)Vector 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。

(4)Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。

2.使用场景

如果涉及到“栈”、“队列”、“链表”等操作,应该考虑用List,具体的选择哪个List,根据下面的标准来取舍:

(01) 对于需要快速插入、删除元素,应该使用LinkedList。

(02) 对于需要快速随机访问元素,应该使用ArrayList。

(03) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。

(04) 对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。

3.Vector和ArrayList比较

相同之处:

  1. 它们都是List。
  2. 它们都实现了RandomAccess(支持快速随机访问)和Cloneable接口。
  3. 它们都是通过数组实现的,本质上都是动态数组。
  4. 它们的默认数组容量是10。
  5. 它们都支持Iterator和listIterator遍历。

不同之处:

  1. 线程安全性不一样,Vector安全,ArrayList不安全。
  2. 对序列化支持不同,Vector不支持,ArrayList支持(实现Serializable接口)。
  3. 构造函数个数不同,ArrayList有3个,Vector有4个(指定容量增加系数)。
  4. 容量增加方式不同:
    • ArrayList容量不足时,新的容量 = (原始容量x 3) / 2 + 1
    • Vector的容量增长与“增长系数有关”,若指定了“增长系数”,且“增长系数有效(即,大于0)”,那么,每次容量不足时,新的容量 = 原始容量 + 增长系数。若增长系数无效(即,小于/等于0),则 新的容量 = 原始容量 x 2
  5. 对Enumeration的支持不同。Vector支持通过Enumeration去遍历,而ArrayList不支持。

5.2 Map总结

1.HashMap, Hashtable, TreeMap, WeakHashMap这4个类是“键值对”映射的实现类。

(1)HashMap 是基于“拉链法”实现的散列表。一般用于单线程场景。

(2)Hashtable 也是基于“拉链法”实现的散列表。它一般用于多线程场景。

(3)WeakHashMap 也是基于“拉链法”实现的散列表,它一般也用于单线程场景。相比HashMap,WeakHashMap中的键是“弱键”,当“弱键”被GC回收时,它对应的键值对也会被从WeakHashMap中删除;而HashMap中的键是强键。

(4)TreeMap 是有序的散列表,它是通过红黑树实现的。它一般用于单线程中存储有序的映射。

2.HashMap和Hashtable比较

相同之处:

(1)存储方式

HashMap和Hashtable都是存储“键值对(key-value)”的散列表,而且都采用拉链法实现。具体思想都是:通过table数组存储,数组的每一个元素都是一个Entry;而一个Entry就是一个单向链表,Entry链表中的每一个节点就保存了key-value键值对数据。

(2)添加key-value键值对

首先,根据key值计算出哈希值,再计算出数组索引(即,该key-value在table中的索引)。然后,根据数组索引找到Entry(即,单向链表),再遍历单向链表,将key和链表中的每一个节点的key进行对比。若key已经存在Entry链表中,则用该value值取代旧的value值;若key不存在Entry链表中,则新建一个key-value节点,并将该节点插入Entry链表的表头位置。

(3)删除key-value键值对

删除键值对,相比于“添加键值对”来说,简单很多。首先,还是根据key计算出哈希值,再计算出数组索引(即,该key-value在table中的索引)。然后,根据索引找出Entry(即,单向链表)。若节点key-value存在与链表Entry中,则删除链表中的节点即可。

不同之处:

(1)继承和实现方式不同

HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。

Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。

Dictionary一般是通过Enumeration(枚举类)去遍历,Map则是通过Iterator(迭代器)去遍历。 然而由于Hashtable也实现了Map接口,所以,它即支持Enumeration遍历,也支持Iterator遍历,而HashMap 仅支持Iterator遍历。

(2)线程安全不同

Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。

而HashMap的函数则是非同步的,它不是线程安全的。若要在多线程中使用HashMap,需要我们进行额外的同步处理。 对HashMap的同步处理可以使用Collections类提供的synchronizedMap静态方法,或者直接使用JDK 5.0之后提供的java.util.concurrent包里的ConcurrentHashMap类。

(3)对null值的处理不同

Hashtable的key、value都不可以为null。否则,会抛出异常NullPointerException。

HashMap的key、value都可以为null。当HashMap的key为null时,HashMap会将其固定的插入table[0]位置(即HashMap散列表的第一个位置);而且table[0]处只会容纳一个key为null的值,当有多个key为null的值插入的时候,table[0]会保留最后插入的value。

(4)支持的遍历种类不同

HashMap只支持Iterator(迭代器)遍历。

Hashtable支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历。

(5)通过Iterator迭代器遍历时,遍历的顺序不同

HashMap是“从前向后”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。

Hashtable是“从后往前”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。

(6)容量的初始值 和 增加方式都不一样

HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。

Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”。

PS:何时增加容量?实际容量 >= 阈值(阈值 = 总的容量 * 加载因子)

(7)添加key-value时的hash值算法不同

HashMap添加元素时,是使用自定义的哈希算法。

Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。

(8)部分API不同

Hashtable支持contains(Object value)方法,而且重写了toString()方法;

HashMap不支持contains(Object value)方法,没有重写toString()方法。

3.HashMap和WeakHashMap比较

相同之处:

(1)它们都是散列表,存储的是“键值对”映射。

(2)它们都继承于AbstractMap,并且实现Map基础。

(3)它们的构造函数都一样,都包括4个构造函数,而且函数的参数都一样。

(4)默认的容量大小是16,默认的加载因子是0.75。

(5)它们的“键”和“值”都允许为null。

(6)它们都是“非同步的”。

不同之处:

(1)实现接口不同

HashMap实现了Cloneable和Serializable接口,它能通过clone()克隆自己、支持序列化。而WeakHashMap没有实现这两个接口。

(2)HashMap的键是强引用(StrongReference),而WeakHashMap的键是弱引用(WeakReference)。

 

 

转载于:https://my.oschina.net/edwardge/blog/1834517

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值