Java 集合类实现原理

Collection & Map

  1. Collection 子类有 ListSet

  2. List –> ArrayList / LinkedList / Vector

  3. Set –> HashSet / TreeSet

  4. Map –> HashMap / HashTable / TreeMap

一、ArrayList

ArrayList 是 List 接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外, 此类还提供一些方法来操作内部用来存储列表的数组的大小。

每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长 (每次调用添加操作时,都会调用 ensureCapacity 方法,判断是否需要自增,如果需要则自增数组) 。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造 ArrayList 时指定其容量。在添加大量元素前,应用程序也可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量。

注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)

不管是 ArrayList、 Vector、LinkedList 他们的 set,remove 方法的返回值都是原来该位置的元素,add 方法返回 boolean 值为是否成功插入

1、实现的接口

  1. 继承 AbstractList (实现了 List 接口)

  2. Cloneable 可克隆, Serializable 可序列化,RandomAccess 为 List 提供快速访问功能(RandomAccess 为空接口,只是一个可以快速访问的标识),即通过序号获取元素

2、构造方法

  1. 创建长度为 10 的数组

  2. 创建指定长度数组,小于 0 抛出异常

  3. 根据集合创建数组,创建长度为集合长度的数组并拷贝

3、增删查方法

  1. 每次操作之前都会创建一个新的数组引用指向被操作数组,使用新的引用操作。

  2. set 方法,指定位置赋值,检查 index ,如果不合法则抛出异常

  3. add 方法,末尾位置添加,如果超出,先创建新数组替换旧数组,新数组长度为就数组的 1.5 倍再加 1;

  4. add(int index,Object obj) 指定位置添加,检查 index ,如不合法则抛出异常。指定位置插入时,会将原来的数组以 index 为界,将 index 后的数据后移一位,后移的实现通过 System.arraycopy 方法实现。再在 index 位置插入需要插入的数据。 System.arraycopy 为 Native 层的方法,可以高效复制数组元素。

  5. remove(int index) 根据索引删除,直接操作数组,返回值为被移除的对象。将该对象所在位置之后的数组内容复制到从该位置开始,将末尾置为 null

  6. remove(Object obj) 根据对象删除,遍历数组,如果存在,将该对象所在位置之后的数组内容复制到从该位置开始,将末尾置为 null

  7. 当我们可预知要保存的元素的多少时,要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity 方法来手动增加 ArrayList 实例的容量。

  8. ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。

  9. 在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。

二、Vector

  1. Vector也是基于数组实现的,是一个动态数组,其容量能自动增长。
  2. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,使用 synchronized 修饰,因此是线程安全的(其实也只是相对安全,有些时候还是要加入同步语句来保证线程的安全),可以用于多线程环境。
  3. Vector没有实现 Serializable 接口,因此它不支持序列化,实现了 RandomAccess 可以
  4. 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

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、插入数据

  1. LinkedList 内部的 Link 对象 voidLink ,其 previous 执向链表最后一个对象,next 指向第一个链表第一个对象,初始化 LinkedList 时默认初始化 voidLink 的前后都指向自己。

  2. 注意两个不同的构造方法。无参构造方法直接建立一个仅包含head节点的空链表,包含Collection的构造方法,先调用无参构造方法建立一个空链表,而后将Collection中的数据加入到链表的尾部后面。

  3. 往最后插入,会创建新的 Link 对象,并将 新对象的 previous 赋值为 voidLind 的 previous,将新对象的 next 赋值为 voidLink,最后将 voidLink 的 previous 指向 新对象。

  4. 往非末尾插入,会比较 index 与链表的中间值的大小,缩小检索比例,调用从后往前检索或从前往后检索,如果从前往后,会循环调用 voidLink 的 next 方法
    直到需要插入的位置得到当前位置的元素 link (注意,voidLink的 next 指向第一个元素,所以遍历next之后的位置为需要插入的位置),创建新对象,新对象的 previous 指向原来当前元素 link 的 previous ,新对象的 next 指向 link,link 的 previous 执向新对象,原来 link 的 previous 对象的 next 指向 新元素,这样就准确插入。从后往前的道理相同。

  5. LinkedList 获取非首尾元素时,也会使用与插入时相同的判断位置的加速机制

  6. 在查找和删除某元素时,源码中都划分为该元素为 null 和不为 null 两种情况来处理,LinkedList 支持插入的元素为 null

  7. LinkedList是基于链表实现的,因此不存在容量不足的问题,所以这里没有扩容的方法。

  8. LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)。

  9. 要注意源码中还实现了栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用 push(向顶部插入元素)、pop(删除并返回第一个元素) 等方法。

  10. Iterator 中通过元素索引是否等于“双向链表大小”来判断是否达到最后。

四、HashMap

HashMap 是基于哈希表实现的,每一个元素是一个 key-value 对,其内部通过 单链表

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值