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

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

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

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

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

ArrayList

数组

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

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

  1. /**
  2. * The array buffer into which the elements of the ArrayList are stored.
  3. * The capacity of the ArrayList is the length of this array buffer. Any
  4. * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  5. * will be expanded to DEFAULT_CAPACITY when the first element is added.
  6. */
  7. transient Object[] elementData; // non-private to simplify nested class access
  8.  
  9. /**
  10. * The size of the ArrayList (the number of elements it contains).
  11. *
  12. * @serial
  13. */
  14. private int size;

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

Fail-Fast 机制

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

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

  1. /**
  2. * The number of times this list has been <i>structurally modified</i>.
  3. * Structural modifications are those that change the size of the
  4. * list, or otherwise perturb it in such a fashion that iterations in
  5. * progress may yield incorrect results.
  6. *
  7. * <p>This field is used by the iterator and list iterator implementation
  8. * returned by the {@code iterator} and {@code listIterator} methods.
  9. * If the value of this field changes unexpectedly, the iterator (or list
  10. * iterator) will throw a {@code ConcurrentModificationException} in
  11. * response to the {@code next}, {@code remove}, {@code previous},
  12. * {@code set} or {@code add} operations. This provides
  13. * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
  14. * the face of concurrent modification during iteration.
  15. *
  16. * <p><b>Use of this field by subclasses is optional.</b> If a subclass
  17. * wishes to provide fail-fast iterators (and list iterators), then it
  18. * merely has to increment this field in its {@code add(int, E)} and
  19. * {@code remove(int)} methods (and any other methods that it overrides
  20. * that result in structural modifications to the list). A single call to
  21. * {@code add(int, E)} or {@code remove(int)} must add no more than
  22. * one to this field, or the iterators (and list iterators) will throw
  23. * bogus {@code ConcurrentModificationExceptions}. If an implementation
  24. * does not wish to provide fail-fast iterators, this field may be
  25. * ignored.
  26. */
  27. protected transient int modCount = 0;

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

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

  1. public class ArrayListRemoveTest {
  2. public static void main(String[] args) {
  3. List<String> lstString = new ArrayList<String>();
  4. lstString.add("hello");
  5.  
  6. Iterator<String> iterator = lstString.iterator();
  7. while (iterator.hasNext()) {
  8. String item = iterator.next();
  9. if (item.equals("hello")) {
  10. lstString.remove(item);
  11. }
  12. }
  13. }
  14. }

运行后会抛出异常:

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

  1. final void checkForComodification() {
  2. if (modCount != expectedModCount)
  3. throw new ConcurrentModificationException();
  4. }

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

LinkedList

链表

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

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

  1. transient int size = 0;
  2. transient Node<E> first; //链表的头指针
  3. transient Node<E> last; //尾指针
  4. //存储对象的结构 Node, LinkedList的内部类
  5. private static class Node<E> {
  6. E item;
  7. Node<E> next; // 指向下一个节点
  8. Node<E> prev; //指向上一个节点
  9.  
  10. Node(Node<E> prev, E element, Node<E> next) {
  11. this.item = element;
  12. this.next = next;
  13. this.prev = prev;
  14. }
  15. }

在新增节点时,只需要创建一个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。

JDK源代码学习-ArrayList、LinkedList、HashMap的更多相关文章

  1. JDK源代码学习系列04----ArrayList

                                                                             JDK源代码学习系列04----ArrayList 1 ...

  2. JDK源代码学习系列05----LinkedList

                                             JDK源代码学习系列05----LinkedList 1.LinkedList简单介绍 LinkedList是基于双向 ...

  3. JDK源代码学习系列07----Stack

                                                                   JDK源代码学习系列07----Stack 1.Stack源代码很easy ...

  4. JDK源代码学习系列03----StringBuffer+StringBuilder

                         JDK源代码学习系列03----StringBuffer+StringBuilder 因为前面学习了StringBuffer和StringBuilder的父类 ...

  5. JDK源代码学习-基础类

    一.概述 1.Java,是一套语言规范,例如规定了变量如何定义.控制语句如何写等,提供基本的语法规范.JDK是java自带的一套调用组件,是对基本java语法规范的进一步封装,jdk中都是使用java ...

  6. arrayList LinkedList HashMap HashTable的区别

    ArrayList 采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,所以最大的缺点就是插入删除时非常麻烦 LinkedList 采用的将对象存放在独立的空间中,而且在每个空间中还保存下一 ...

  7. JDK1.7源码阅读tools包之------ArrayList,LinkedList,HashMap,TreeMap

    1.HashMap 特点:基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Has ...

  8. [Java] LinkedList / Queue - 源代码学习笔记

    简单地画了下 LinkedList 的继承关系,如下图.只是画了关注的部分,并不是完整的关系图.本博文涉及的是 Queue, Deque, LinkedList 的源代码阅读笔记.关于 List 接口 ...

  9. 调试JDK源代码-一步一步看HashMap怎么Hash和扩容

    调试JDK源代码-一步一步看HashMap怎么Hash和扩容 调试JDK源代码-ConcurrentHashMap实现原理 调试JDK源代码-HashSet实现原理 调试JDK源代码-调试JDK源代码 ...

随机推荐

  1. BOM—浏览器对象模型(Browser Object Model)

     1,javascript   组成部分: 1.ECMAscript(核心标准):    定义了基本的语法,比如:if for 数组 字符串 ... 2.BOM  : 浏览器对象模型(Browser ...

  2. pip 使用豆瓣源

    pip 使用豆瓣源 由于pip 默认使用Python的官方源pypi.python.org/pypi,导致我们经常使用pip装包时速度过慢或者无法安装(请求超时)等问题, 所以国内用户建议使用pip ...

  3. Jquery 使用和Jquery选择器

    jQuery中的顶级对象($) jQuery 中最常用的对象即 $ 对象,要想使用 jQuery 的方法必须通过 $ 对象.只有将普通的 Dom 对象封装成 jQuery 对象,然后才能调用 jQue ...

  4. Android各版本特性

    此篇文章可以利用碎片化时间进行消化和了解,针对Android各个版本特性,并没有把所有列出,只是抽出了比较常用重要的特性作为提示,同时在面试中只要牢记重要的几个点即可,其他特性直接查找官方文档即可. ...

  5. Kotlin入门(33)运用扩展属性

    进行App开发的时候,使用震动器要在AndroidManifest.xml中加上如下权限: <!-- 震动 --> <uses-permission android:name=&qu ...

  6. 前端js面向对象编程以及封装组件的思想

    demo-richbase 用来演示怎么使用richbase来制作组件的例子 作为一名前端工程师,写组件的能力至关重要.虽然javascript经常被人嘲笑是个小玩具,但是在一代代大牛的前仆后继的努力 ...

  7. GitHub和75亿美金

    如果你是看到了75亿进来的,还在纳闷前面那个github的是个什么,你可以走人了?如果你进来是想看到微软两个字的,请继续. 微软以75亿美金的股票收购Github这件事情,从周六一早我爬山到香山琉璃塔 ...

  8. java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext

    java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start com ...

  9. c/c++ linux epoll系列1 创建epoll

    linux epoll系列1 创建epoll 据说select和poll的弱点是,随着连接(socket)的增加,性能会直线下降. epoll不会随着连接(socket)的增加,性能直线下降. 知识点 ...

  10. 6.1Python数据处理篇之pandas学习系列(一)认识pandas

    目录 目录 (一)介绍与测试 2.作用: 3.导入的格式 4.小测试 (二)数据类型 1.两种重要的数据类型 2.pandas与numpy的比较 目录 (一)介绍与测试 号称处理数据与分析数据最好的第 ...