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.
*/
transient Object[] elementData; // non-private to simplify nested class access /**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;

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

Fail-Fast 机制

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

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

    /**
* The number of times this list has been <i>structurally modified</i>.
* 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.
*
* <p>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
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> 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.
*/
protected transient int modCount = 0;

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

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

public class ArrayListRemoveTest {
public static void main(String[] args) {
List<String> lstString = new ArrayList<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方法中抛出异常。

        final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

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

LinkedList

链表

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

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

    transient int size = 0;
transient Node<E> first; //链表的头指针
transient Node<E> last; //尾指针
//存储对象的结构 Node, LinkedList的内部类
private static class Node<E> {
E item;
Node<E> next; // 指向下一个节点
Node<E> 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。

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. JavaScript字符串排序localeCompare()笔记

    今天遇到了一个没有接触过的方法,是localeCompare().查了一些相关的文档,给出的定义很模糊,所以就把自己的理解写下来. 首先由例子入手! 已知一串学生姓名的字符串,根据学生姓名的长度按照降 ...

  2. 5.HotSpot的算法实现

    1.枚举根节点 在可达性分析中,可以作为GC Roots的节点有很多,但是现在很多应用仅仅方法区就有上百MB,如果逐个检查的话,效率就会变得不可接受. 而且,可达性分析必须在一个一致性的快照中进行-即 ...

  3. atitit.元编程总结 o99

    atitit.元编程总结 o99.doc 1. 元编程(Metaprogramming) 1 2. 元编程的历史and发展 1 3. 元类型and元数据 1 4. 元编程实现方式 2 4.1. 代码生 ...

  4. HDU 1043 &amp; POJ 1077 Eight(康托展开+BFS+预处理)

    Eight Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 30176   Accepted: 13119   Special ...

  5. centos 下mysql操作

    MySQL名字的来历MySQL是一个小型关系型数据库管理系统,MySQL被广泛地应用在Internet上的中小型网站中.由于其体积小.速度 快.总体拥有成本低,尤其是开放源码这一特点,许多中小型网站为 ...

  6. Step one : 熟悉HTML

    //H1 1 <html> <head> <title>BeiJing</title> </head> <body> <h ...

  7. Linux服务器操作系统

    Linux服务器操作系统 今日大纲 ●    服务器操作系统的系列.Linux的主流产品.虚拟机软件 ●    安装linux ●    linux基本命令 ●    用户管理及权限(多用户) ●   ...

  8. JS基础四

    1.函数是一组可以随时随地运行的语句. 函数是 ECMAScript 的核心. 函数是由这样的方式进行声明的:关键字 function.函数名.一组参数,以及置于括号中的待执行代码. 2.闭包,指的是 ...

  9. dimens.xml详解

    本文联合两篇博文和自身理解写下 其中一篇:http://blog.csdn.net/hnzcdy/article/details/50628993 另一篇:暂不知原作者 Android中官方建议的屏幕 ...

  10. 纯JS编写打地鼠游戏

    这个游戏是本人前不久刚入门编写的游戏,感觉里面代码很LOW但是对于新手来说很一般般吧~ 没有上传音乐文件了 运行效果: 上代码(HTML+JS): <!DOCTYPE html> < ...