JDK源代码学习-ArrayList、LinkedList、HashMap

237 篇文章 4 订阅
237 篇文章 1 订阅

ArrayList、LinkedList、HashMap是Java开发中非常常见的数据类型。它们的区别也非常明显的,在Java中也非常具有代表性。在Java中,常见的数据结构是:数组、链表,其他数据结构基本就是这两者的组合。

复习一下数组、链表的特征。

数组:在内存中连续的地址块,查找按照下标来寻址,查找快速。但是插入元素和删除元素慢,需要移动元素。

链表:内存中逻辑上可以连接到一起的一组节点。每个节点除了存储本身,还存储了下一个元素的地址。查找元素需要依次找找各个元素,查找慢,插入和删除元素只需要修改元素指向即可。

结合这两种数据结构的特征,就不难理解ArrayList、LinkedList、HashMap的各种操作了。

ArrayList

数组

ArrayList的底层实现就是数组,根据数组的特征就很好理解ArrayList的各个特性了。

下面是ArrayList中最基本的两个变量:存储对象的数组和数组大小。

/**

  • The array buffer into which the elements of the ArrayList are
    stored.

  • The capacity of the ArrayList is the length of this array buffer.
    Any

  • empty ArrayList with elementData ==
    DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  • will be expanded to DEFAULT_CAPACITY when the first element is
    added.

*/

transientObject[] elementData; // non-private to simplify nested class
access

/**

  • The size of the ArrayList (the number of elements it contains).

  • @serial

*/

privateintsize;

在执行add操作前,会首先检查数组大小是否足以容纳新的元素,如果不够,就进行扩容,扩容的公式是:新的数组大小=(老的数组大小*3)/2 + 1,例如初始时数组大小为10,第一次扩容后,数组大小就为16,再扩容一次变为25。

Fail-Fast 机制

在操作元素的方法中,例如add方法和remove方法中,会看到modCount++操作。这个modCount变量是记录什么的?

查看modCount的定义,modCount是在AbstractList中定义的,其说明如下:

/**

  • The number of times this list has been structurally
    modified
    .

  • Structural modifications are those that change the size of the

  • list, or otherwise perturb it in such a fashion that iterations in

  • progress may yield incorrect results.

  • This field is used by the iterator and list iterator

implementation

  • returned by the {@code iterator} and {@code listIterator} methods.

  • If the value of this field changes unexpectedly, the iterator (or
    list

  • iterator) will throw a {@code ConcurrentModificationException} in

  • response to the {@code next}, {@code remove}, {@code previous},

  • {@code set} or {@code add} operations. This provides

  • fail-fast behavior, rather than non-deterministic behavior
    in

  • the face of concurrent modification during iteration.

  • Use of this field by subclasses is optional. If a

subclass

  • wishes to provide fail-fast iterators (and list iterators), then it

  • merely has to increment this field in its {@code add(int, E)} and

  • {@code remove(int)} methods (and any other methods that it
    overrides

  • that result in structural modifications to the list). A single
    call to

  • {@code add(int, E)} or {@code remove(int)} must add no more than

  • one to this field, or the iterators (and list iterators) will throw

  • bogus {@code ConcurrentModificationExceptions}. If an
    implementation

  • does not wish to provide fail-fast iterators, this field may be

  • ignored.

*/

protectedtransientintmodCount = 0;

modCount记录是List的结构变化次数,就是List大小变化的次数,如果在遍历List的时候,发现modCount发生变化,则抛出异常ConcurrentModificationException。

例如下面的代码,定义了一个Array List,向其中增加元素,然后遍历元素,在遍历元素过程中,删除了一个元素。

publicclassArrayListRemoveTest {

publicstaticvoidmain(String[] args) {

List lstString = newArrayList<String>();

lstString.add("hello");



Iterator<String> iterator = lstString.iterator();

while(iterator.hasNext()) {

  String item = iterator.next();

  if(item.equals("hello")) {

    lstString.remove(item);

  }

}

}

}

运行后会抛出异常:
在这里插入图片描述

根据报错堆栈,next方法会调用checkForComodification方法,在checkForComodification方法中抛出异常。

finalvoidcheckForComodification() {

if(modCount != expectedModCount)

    thrownewConcurrentModificationException();

}

代码中会比较当前的modCount和expectedModCount的值,expectedModCount的值是在执行Iterator iterator = lstString.iterator();时,在Itr的构造函数中赋值的,是原始的List结构变化次数。在执行remove方法后,List的大小发生了变化,则modCount发生了变化,两次modCount不同,抛出异常。做这个检查的原因,是要保持单线程的唯一操作。这就是Fail-Fast机制。

LinkedList

链表

LinkedList的底层实现就是链表,插入和删除只需要改变节点指向,效率高。随机访问需要依次找到各个节点,慢。

LinkedList在类中包含了 first 和 last 两个指针(Node)。Node 中包含了上一个节点和下一个节点的引用,这样就构成了双向的链表。

transientintsize = 0;

transientNode first; //链表的头指针

transientNode last; //尾指针

//存储对象的结构 Node, LinkedList的内部类

privatestaticclassNode {

E item;

Node next; // 指向下一个节点

Node prev; //指向上一个节点



Node(Node<E> prev, E element, Node<E> next) {

    this.item = element;

    this.next = next;

    this.prev = prev;

}

}

在新增节点时,只需要创建一个Node,指向这个Node即可。删除节点,修改上一个节点的prev指向即可。

HashMap

数组+链表

HashMap是Java数据结构中两大结构数组和链表的组合。其结构图如下:
在这里插入图片描述

可以看出,HashMap底层是数组,数组中的每一项又是一个链表。

当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 在数组中的存储位置,即数组下标。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同(即碰撞)。再调用equals,如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖,就是value替换。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部。

简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 hash 算法来决定其在数组中的存储位置,再根据 equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry 时,也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该Entry。

欢迎工作一到五年的Java工程师朋友们加入Java架构开发: 854393687

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值