Collection & Map
Collection 子类有 List 和 Set
List –> ArrayList / LinkedList / Vector
Set –> HashSet / TreeSet
Map –> HashMap / HashTable / TreeMap
一、ArrayList
ArrayList 是 List 接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外, 此类还提供一些方法来操作内部用来存储列表的数组的大小。
每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长 (每次调用添加操作时,都会调用 ensureCapacity 方法,判断是否需要自增,如果需要则自增数组) 。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造 ArrayList 时指定其容量。在添加大量元素前,应用程序也可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)
不管是 ArrayList、 Vector、LinkedList 他们的 set,remove 方法的返回值都是原来该位置的元素,add 方法返回 boolean 值为是否成功插入
1、实现的接口
继承 AbstractList (实现了 List 接口)
Cloneable 可克隆, Serializable 可序列化,RandomAccess 为 List 提供快速访问功能(RandomAccess 为空接口,只是一个可以快速访问的标识),即通过序号获取元素
2、构造方法
创建长度为 10 的数组
创建指定长度数组,小于 0 抛出异常
根据集合创建数组,创建长度为集合长度的数组并拷贝
3、增删查方法
每次操作之前都会创建一个新的数组引用指向被操作数组,使用新的引用操作。
set 方法,指定位置赋值,检查 index ,如果不合法则抛出异常
add 方法,末尾位置添加,如果超出,先创建新数组替换旧数组,新数组长度为就数组的 1.5 倍再加 1;
add(int index,Object obj) 指定位置添加,检查 index ,如不合法则抛出异常。指定位置插入时,会将原来的数组以 index 为界,将 index 后的数据后移一位,后移的实现通过 System.arraycopy 方法实现。再在 index 位置插入需要插入的数据。 System.arraycopy 为 Native 层的方法,可以高效复制数组元素。
remove(int index) 根据索引删除,直接操作数组,返回值为被移除的对象。将该对象所在位置之后的数组内容复制到从该位置开始,将末尾置为 null
remove(Object obj) 根据对象删除,遍历数组,如果存在,将该对象所在位置之后的数组内容复制到从该位置开始,将末尾置为 null
当我们可预知要保存的元素的多少时,要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity 方法来手动增加 ArrayList 实例的容量。
ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。
二、Vector
- Vector也是基于数组实现的,是一个动态数组,其容量能自动增长。
- Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,使用 synchronized 修饰,因此是线程安全的(其实也只是相对安全,有些时候还是要加入同步语句来保证线程的安全),可以用于多线程环境。
- Vector没有实现 Serializable 接口,因此它不支持序列化,实现了 RandomAccess 可以
- Vector 的构造函数中可以指定容量增长系数,如果不指定增长系数,增加时为增加一倍,这点有别于 ArrayList。
Vector的源码实现总体与ArrayList类似,关于Vector的源码,给出如下几点总结:
1、Vector有四个不同的构造方法。无参构造方法的容量为默认值10,仅包含容量的构造方法则将容量增长量(从源码中可以看出容量增长量的作用,第二点也会对容量增长量详细说)明置为0。
2、注意扩充容量的方法ensureCapacityHelper。与ArrayList相同,Vector在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就先看构造方法中传入的容量增长量参数CapacityIncrement是否为0,如果不为0,就设置新的容量为就容量加上容量增长量,如果为0,就设置新的容量为旧的容量的2倍,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后同样用Arrays.copyof()方法将元素拷贝到新的数组。
3、很多方法都加入了synchronized同步语句,来保证线程安全。
4、同样在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,Vector中也允许元素为null
5、其他很多地方都与ArrayList实现大同小异,Vector现在已经基本不再使用。
三、LinkedList
LinkedList 和 ArrayList 一样,实现了 List 接口,但其内部的数据结构有本质不同。LinkedList 是基于双向循环链表实现的,所以它的插入和删除操作比 ArrayList 更高效,不过由于是基于链表的,随机访问的效率要比 ArrayList 差。
实现了 Searializable 接口,支持序列化,实现了 Cloneable 接口,可被克隆
是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。
1、数据结构
LinkedList 是基于链表结构的实现,每一个节点的类都包含了 previous 和 next 两个 Link 指针对象,由 Link 保存,Link 中包含了上一个节点和下一个节点的引用,这样就构成了双向的链表,每个 Link 只能知道自己的前一个和后一个节点。
注意:不同版本类名不同,但是原理一样,有的版本类名是 Node
Link
private static final class Link<ET> {
ET data;
Link<ET> previous, next;
Link(ET o, Link<ET> p, Link<ET> n) {
data = o;
previous = p;
next = n;
}
}
2、插入数据
LinkedList 内部的 Link 对象 voidLink ,其 previous 执向链表最后一个对象,next 指向第一个链表第一个对象,初始化 LinkedList 时默认初始化 voidLink 的前后都指向自己。
注意两个不同的构造方法。无参构造方法直接建立一个仅包含head节点的空链表,包含Collection的构造方法,先调用无参构造方法建立一个空链表,而后将Collection中的数据加入到链表的尾部后面。
往最后插入,会创建新的 Link 对象,并将 新对象的 previous 赋值为 voidLind 的 previous,将新对象的 next 赋值为 voidLink,最后将 voidLink 的 previous 指向 新对象。
往非末尾插入,会比较 index 与链表的中间值的大小,缩小检索比例,调用从后往前检索或从前往后检索,如果从前往后,会循环调用 voidLink 的 next 方法
直到需要插入的位置得到当前位置的元素 link (注意,voidLink的 next 指向第一个元素,所以遍历next之后的位置为需要插入的位置),创建新对象,新对象的 previous 指向原来当前元素 link 的 previous ,新对象的 next 指向 link,link 的 previous 执向新对象,原来 link 的 previous 对象的 next 指向 新元素,这样就准确插入。从后往前的道理相同。LinkedList 获取非首尾元素时,也会使用与插入时相同的判断位置的加速机制
在查找和删除某元素时,源码中都划分为该元素为 null 和不为 null 两种情况来处理,LinkedList 支持插入的元素为 null
LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。
LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。
要注意源码中还实现了栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用 push(向顶部插入元素)、pop(删除并返回第一个元素) 等方法。
Iterator 中通过元素索引是否等于“双向链表大小”来判断是否达到最后。
四、HashMap
HashMap 是基于哈希表实现的,每一个元素是一个 key-value 对,其内部通过 单链表