3.1Set和Map
3.1.1HashSet和HashMap
HashMap底层将key-value当做一个整体来处理,这个整体就是一个Entry对象。HashMap底层采用一个Entry[]数组来保存所有的key-value对。
当需要存储一个Entry对象时,首先会根据其key的hashCode()返回值决定该Entry的存储位置:如果两个Entry的key的hashCode()返回值相同,那它们的存储位置相同;如果两个Entry的key通过equals比较返回true,新添加的Entry的value将覆盖原有的Entry的value,但key不会被覆盖;如果这两个Entry的key通过equals比较返回false,新添加的Entry将与原有的Entry形成Entry链,并且新添加的Entry位于Entry链表的头部。
当需要取出一个Entry时,也会根据Hash算法找到其在数组中的位置,然后用equals比较key,知道找到相等的对象并返回,如果没有相等的key,返回null。
HashSet 实现是内部封装了一个HashMap对象来存储所有的集合元素。所有放入HashSet中的集合元素实际上是由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。HashSet的绝大部分方法都是通过调用HashMap的方法实现的,因此HashSet和HashMap两个集合在实现本质上是相同的。
3.1.2TreeMap和TreeSet
类似于HashMap和HashSet之间的关系,HashSet底层依赖于HashMap实现,TreeSet底层采用一个NavaigableMap来保存TreeSet集合的元素。但由于NavaigableMap只是一个接口,因此底层实际上还是由一个TreeMap来保存Set中的元素的。
对于TreeMap而言,它内部采用一种被称为“红黑树”的排序二叉树来保存Map中的每个Entry-----每个Entry都被当成“红黑树”的一个结点对待。
请学习红黑树相关知识
3.2Map和List
3.2.1Map的values()方法
Map集合是一个关联数组,它包含两组值:一组是所有key组成的集合,因为Map集合的key不允许重复,而且Map不会保存Key加入的顺序,因此这些key可以组成一个Set集合;另外一组是values组成的集合,因为Map集合的value完全可以 重复,而且Map可以根据key来获得相应的value,所以这些value可以组成一个list集合;
但实际上,Map的values方法返回的结果并不是一个List集合。该方法返回的是一个内部类Values的对象,而此Values类虽然继承了AbstraceCollection,但是由于并没有实现add(Object e)方法,也就是说这个对象的实例并没有真正的盛装任何Java对象,只是实现了iterator()方法来对map的value集合进行遍历。
HashMap和TreeMap的values()方法并未把Map中的value重新组合成一个包含元素的集合对象,这样可以降低系统的内存开销。
3.3ArrayList和LinkedList
在List集合的实现类中,主要有3个实现类:ArrayList、Vector和LinkedList。其中Vector还有一个Stack的子类,这个子类在父类Vector的基础上添加了5个用以栈操作的方法:
public E push(E item); public synchronized E pop(); public synchronized E peek(); public boolean empty(); public synchronized int search(Object o);
在实际使用中如果需要使用栈结构,推荐使用Deque实现类,例如ArrayDeque。在无需保证线程安全的情况下,程序完全可以使用ArrayDeque来代替Stack类。
Deque接口代表双端队列这种数据结构。它既具有队列的性质先进先出(FIFO),也具有栈的性质(FILO),也就是双端队列既是队列,也是栈。
3.3.2ArrayList和LinkedList的实现差异
List是一种线性表的数据结构,ArrayList则是一种顺序存储的线性表,其底层采用数组来保存集合元素;LinkedList则是一种链式存储的线性表,其本质是一个双向链表,它不仅实现了List接口,还实现了Deque接口。也就是说,LinkedList既可以当成双向链表使用,也可以当成队列使用,还可以当成栈来使用。
对于ArrayList集合而言,当程序向ArrayList添加、删除元素时,ArrayList底层需要对数组进行“整体搬家”,因此性能较;但是如果调用get(int index)方法来取出ArrayList中的元素时,性能和数组几乎相同——非常快。
如果只是单纯的添加某个节点,LinkedList的性能非常好,之需要修改几个引用值即可,可惜如果需要向指定索引出添加节点,LinkedList必须先找到指定索引处的节点——这个搜索过程的开销并不小。因此,LinkedList 的add(int index, E element)方法的性能并不是很好。
当单纯的把LinkedList当成双向链表来使用,通过addFirst(E e)、addLast(E e)、offerFirst(E e)、offerLast(E d)、pollFirst()、pollLast()等方法来操作LinkedList集合元素时,LinkedList的性能非常好——因为此时可以避免搜索的过程
3.4 Iterator迭代器
对于Iterator迭代器而言,它只是一个接口。Java要求各集合都提供一个iterator()方法,该方法可以返回一个Interator用以遍历该集合中的元素,至于返回的是哪种Iterator的实现类,程序并不关心,这就是典型的“迭代器模式”。
Java的Interator和Enumeration两个接口都是迭代器模式的代表之作,它们就是迭代器模式里面的“迭代器接口”。所谓迭代器模式指的是,系统为遍历多种数据列表、集合、容器提供一个标准的“迭代器接口”,这些数据列表、集合、容器就可面向相同的“迭代器接口”编程,通过相同的迭代器接口,访问不同数据列表、集合、容器里的数据。不同的数据列表、集合、容器如何实现这个“迭代器接口”,则交给数据列表、集合和容器自己来实现。