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源代码 ...
随机推荐
- CSS深入理解流体特性和BFC特性下多栏自适应布局
一.块状元素的流体特性与自适应布局 块状元素像放在容器中的水流一样,内容区域会随着margin, padding, border的出现自动填满剩余空间,这就是块状元素的流体特性. 来一个小实验: di ...
- 从.Net到Java学习第十二篇——SpringBoot+JPA提供跨域接口
从.Net到Java学习系列目录 最近又撸了半个月的前端代码,做app离线存储,然后又花了一周去将过去的wcf项目转webapi,java又被落下了,总感觉我特么像斗地主中的癞子牌,变来变去..... ...
- Redis压缩列表
此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表.本文会介绍1. 压缩列表(ziplist)的使用场景 2.如何达到节约内存的效果?3.压缩列表的存储格式 4. 连锁更新的问题 5 ...
- 南京邮电大学java第三次实验报告
实 验 报 告 ( 2017 / 2018学年 第2学期) 课程名称 JAVA语言程序设计 实验名称 Java集成开发环境的安装与使用. Java变量.表达式与控制结构 实验时间 2018 年 4 月 ...
- MySQL常用字符串函数
字符串函数 是最常用的的一种函数,在一个具体应用中通常会综合几个甚至几类函数来实现相应的应用: 1.LOWER(column|str):将字符串参数值转换为全小写字母后返回 mysql> sel ...
- python3 dict(字典)
clear(清空字典内容) stu = { 'num1':'Tom', 'num2':'Lucy', 'num3':'Sam', } print(stu.clear()) #输出:None copy( ...
- python3 str(字符串)
__add__函数 (在后面追加字符串) s1 ='Hello' s2 = s1.__add__(' boy!') print(s2) #输出:Hello boy! __contains__(判断是否 ...
- MFC界面相关源码
这是这4篇MFC界面的相关源码.建议学习Visual C++的看看这2本微软官方出的教材. [MFC Windows程序设计(第2版,修订版)](美)Jeff Prosise著 [Windows程序设 ...
- Cs231n课堂内容记录-Lecture 5 卷积神经网络介绍
Lecture 5 CNN 课堂笔记参见:https://zhuanlan.zhihu.com/p/22038289?refer=intelligentunit 不错的总结笔记:https://blo ...
- php操作Memcache的一个类库
###php操作Memcache的一个类库 代码如下: <?php /** * Created by PhpStorm. * User: alisleepy * Date: 2019-03-14 ...