JDK源代码学习-ArrayList、LinkedList、HashMap
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的更多相关文章
- JDK源代码学习系列04----ArrayList
JDK源代码学习系列04----ArrayList 1 ...
- JDK源代码学习系列05----LinkedList
JDK源代码学习系列05----LinkedList 1.LinkedList简单介绍 LinkedList是基于双向 ...
- JDK源代码学习系列07----Stack
JDK源代码学习系列07----Stack 1.Stack源代码很easy ...
- JDK源代码学习系列03----StringBuffer+StringBuilder
JDK源代码学习系列03----StringBuffer+StringBuilder 因为前面学习了StringBuffer和StringBuilder的父类 ...
- JDK源代码学习-基础类
一.概述 1.Java,是一套语言规范,例如规定了变量如何定义.控制语句如何写等,提供基本的语法规范.JDK是java自带的一套调用组件,是对基本java语法规范的进一步封装,jdk中都是使用java ...
- arrayList LinkedList HashMap HashTable的区别
ArrayList 采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,所以最大的缺点就是插入删除时非常麻烦 LinkedList 采用的将对象存放在独立的空间中,而且在每个空间中还保存下一 ...
- JDK1.7源码阅读tools包之------ArrayList,LinkedList,HashMap,TreeMap
1.HashMap 特点:基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Has ...
- [Java] LinkedList / Queue - 源代码学习笔记
简单地画了下 LinkedList 的继承关系,如下图.只是画了关注的部分,并不是完整的关系图.本博文涉及的是 Queue, Deque, LinkedList 的源代码阅读笔记.关于 List 接口 ...
- 调试JDK源代码-一步一步看HashMap怎么Hash和扩容
调试JDK源代码-一步一步看HashMap怎么Hash和扩容 调试JDK源代码-ConcurrentHashMap实现原理 调试JDK源代码-HashSet实现原理 调试JDK源代码-调试JDK源代码 ...
随机推荐
- BOM—浏览器对象模型(Browser Object Model)
1,javascript 组成部分: 1.ECMAscript(核心标准): 定义了基本的语法,比如:if for 数组 字符串 ... 2.BOM : 浏览器对象模型(Browser ...
- pip 使用豆瓣源
pip 使用豆瓣源 由于pip 默认使用Python的官方源pypi.python.org/pypi,导致我们经常使用pip装包时速度过慢或者无法安装(请求超时)等问题, 所以国内用户建议使用pip ...
- Jquery 使用和Jquery选择器
jQuery中的顶级对象($) jQuery 中最常用的对象即 $ 对象,要想使用 jQuery 的方法必须通过 $ 对象.只有将普通的 Dom 对象封装成 jQuery 对象,然后才能调用 jQue ...
- Android各版本特性
此篇文章可以利用碎片化时间进行消化和了解,针对Android各个版本特性,并没有把所有列出,只是抽出了比较常用重要的特性作为提示,同时在面试中只要牢记重要的几个点即可,其他特性直接查找官方文档即可. ...
- Kotlin入门(33)运用扩展属性
进行App开发的时候,使用震动器要在AndroidManifest.xml中加上如下权限: <!-- 震动 --> <uses-permission android:name=&qu ...
- 前端js面向对象编程以及封装组件的思想
demo-richbase 用来演示怎么使用richbase来制作组件的例子 作为一名前端工程师,写组件的能力至关重要.虽然javascript经常被人嘲笑是个小玩具,但是在一代代大牛的前仆后继的努力 ...
- GitHub和75亿美金
如果你是看到了75亿进来的,还在纳闷前面那个github的是个什么,你可以走人了?如果你进来是想看到微软两个字的,请继续. 微软以75亿美金的股票收购Github这件事情,从周六一早我爬山到香山琉璃塔 ...
- 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 ...
- c/c++ linux epoll系列1 创建epoll
linux epoll系列1 创建epoll 据说select和poll的弱点是,随着连接(socket)的增加,性能会直线下降. epoll不会随着连接(socket)的增加,性能直线下降. 知识点 ...
- 6.1Python数据处理篇之pandas学习系列(一)认识pandas
目录 目录 (一)介绍与测试 2.作用: 3.导入的格式 4.小测试 (二)数据类型 1.两种重要的数据类型 2.pandas与numpy的比较 目录 (一)介绍与测试 号称处理数据与分析数据最好的第 ...