面试准备(Java部分) Java是一种技术,由四个方面组成:Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)
Java Class 类文件格式看这一篇就够了_java class文件-CSDN博客
JVM JVM(虚拟机):指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的计算机系统,是物理机的软件实现,Java虚拟机是采用虚拟化技术,隔离出一块独立的子操作系统,使Java软件不受任何影响在虚拟机内进行执行。
Java虚拟机阵营:Sun HotSpot VM、BEA JRockit VM(JDK1.8合并)
开发人员编写Java代码,并将Java源代码文件(.java文件)通过Java编译器进行编译后形成java字节码文件(.class文件),通过类加载子系统 加载到运行时数据区(内存空间) ,再通过JVM执行引擎 进行执行。
JVM实现了Java平台的无关性
在Java平台的结构中, 可以看出,Java虚拟机(JVM) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。它的下方是移植接口,移植接口由两部分组成:适配器和Java操作系统, 其中依赖于平台的部分称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在JVM 的上方是Java的基本类库和扩展类库以及它们的API, 利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java 的平台无关性。 JVM在它的生存周期中有一个明确的任务,那就是运行Java程序,因此当Java程序启动的时候,就产生JVM的一个实例;当程序运行结束的时候,该实例也跟着消失了。
JVM类加载机制
JVM系列(四) - JVM类加载机制详解 - 掘金 (juejin.cn)
部分概念 类加载器
简言之,就是用于把.class
文件中的字节码信息转化为具体的java.lang.Class
对象的过程的工具
。
具体过程:
在实际类加载
过程中,JVM会将所有的.class字节码文件
中的二进制数据读入内存
中,导入运行时数据区的方法区中
。
当一个类首次被主动加载或被动加载
时,类加载器会对此类执行类加载的流程 – 加载、连接(验证、准备、解析)、初始化
。
如果类加载成功
,堆内存中会产生一个新的Class对象,Class对象封装了类在方法区内的数据结构
。
加载 、验证 、准备 和初始化 这四个阶段发生的顺序是确定的 ,而解析阶段 可以在初始化阶段之后发生,也称为动态绑定 或晚期绑定 。
加载 查找并加载类的二进制数据 的过程。
具体过程:
通过类的全限定名 定位.class
文件,并获取其二进制字节流 。
把字节流所代表的静态存储结构 转换为方法区 的运行时数据结构 。
在Java
堆 中生成一个此类的java.lang.Class
对象,作为方法区中这些数据的访问入口 。
深入理解Java Class文件格式(三)_类的全限定名-CSDN博客
(全限定名的概念在上面联接的博客部分有)
连接 验证 验证:确保被加载的类的正确性。验证是连接阶段的第一步,用于确保Class字节流中的信息是否符合虚拟机的要求
。
具体验证格式
文件格式验证 :验证字节流是否符合Class
文件格式的规范;例如:是否以0xCAFEBABE
开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
元数据验证 :对字节码描述的信息进行语义分析(注意:对比javac
编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object
之外。
字节码验证 :通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
符号引用验证 :确保解析动作能正确执行。
java 符号引用与直接引用-CSDN博客
准备 准备:为类的静态变量 分配内存,并将其初始化为默认值 。准备过程通常分配一个结构用来存储类信息 ,这个结构中包含了类中定义的成员变量 ,方法 和接口信息 等。
具体行为
这时候进行内存分配的仅包括类变量 (static
),而不包括实例变量 ,实例变量 会在对象实例化 时随着对象一块分配在Java
堆 中。
这里所设置的初始值 通常情况下是数据类型默认的零值 (如0
、0L
、null
、false
等),而不是被在Java
代码中被显式赋值 。
解析 解析:把类中对常量池 内的符号引用 转换为直接引用 。
解析动作主要针对类或接口 、字段 、类方法 、接口方法 、方法类型 、方法句柄 和调用点限定符 等7类符号引用进行。
初始化 初始化:对类静态变量 赋予正确的初始值 (注意和连接时的解析过程 区分开,那个是赋值为默认值,这个是赋值为代码中开发者设置的初始值)。
目标
实现对声明类静态变量 时指定的初始值的初始化;
实现对使用静态代码块 设置的初始值的初始化。
步骤
如果此类没被加载 、连接 ,则先加载 、连接 此类;
如果此类的直接父类 还未被初始化,则先初始化 其直接父类;
如果类中有初始化语句 ,则按照顺序依次执行初始化语句。
初始化时机
创建类的实例(new
关键字);
java.lang.reflect
包中的方法(如:Class.forName(“xxx”)
);
对类的静态变量 进行访问或赋值;
访问调用类的静态方法 ;
初始化一个类的子类 ,父类 本身也会被初始化;
作为程序的启动入口 ,包含main
方法(如:SpringBoot
入口类)。
内存模型 克服焦虑–图解JVM内存模型和JVM线程模型 - 掘金 (juejin.cn)
注:在JVM1.8中,图中的方法区为元数据区
在多线程背景下:
堆和方法区是 线程共享 的
虚拟机栈、本地方法栈、程序计数器是 线程隔离 的
下面展开谈一谈这五个区域的作用。
方法区(JVM1.8为元数据区) 方法区的作用为:存放虚拟机加载的:类型信息,域(Field)信息,方法(Method)信息,常量,静态变量,即时编译器编译后的代码缓存
值得注意的是,无法申请到内存时,将抛出 OutOfMemoryError
方法区中存在运行时常量池,字面量、符号引用等存放入其中。
在Hotspot的演变过程中:
Java6及之前:方法区存在永久代,保存有静态变量
Java7:进行去永久代工作,虽然还保留着,但静态常量池,如字符串常量池,已经移动到堆中
Java8:移除永久代,类型信息、域(Field)信息、方法(Method)信息存放在元数据区;字符串常量池、静态变量存放在堆区
虚拟机栈 虚拟机栈中保存了 每一次 方法调用
的信息。
每个Java线程创建时,都会创建对应的 虚拟机栈
,每一次方法调用,都会往栈中压入一个 栈帧
。
(保存局部变量和计算的结果,返回地址)
而栈帧中,包含:
局部变量表:保存函数 (即方法) 的局部变量
操作数栈:保存计算过程中的结果,即临时变量
动态链接:指向方法区的运行时常量池。字节码中的 方法调用指令
以常量池中指向方法的 符号引用
为参数。
方法的返回地址
本地方法栈 和虚拟机栈功能上类似,它管理了native方法的一些执行细节,而虚拟机栈管理的是Java方法的执行细节。
程序计数器 程序计数器记录线程执行的字节码行号,如果当前线程正在运行native方法则为空 。也有称之为 PC寄存器
字节码解释器在工作时,通过改变计数器的值来选取下一跳需要执行的字节码指令,分支
、 循环
、 跳转
、 异常处理
、线程恢复
等基本功能都需要依赖计数器来完成。
Java虚拟机的多线程实现方式:通过 轮流切换并分配处理器执行时间 实现,所以,在任意确定的时间点,一个处理器只会处理一个线程中的指令。为了正确地处理 线程切换后的任务恢复 ,每一个线程都具有自身的程序计数器
堆 堆提供了类实例和数组的内存,可以按如下方式划分:
新生代,亦可称之年轻代、New Generation
Eden区
Survivor 区,S0和S1中存在互相移动,一些文章中的from、to是指移动上的逻辑关系
老年代 Old Generation
划分和对象创建与GC有关,
新生成的对象在Eden区
触发 Minor GC后,还 “幸存” 的对象移动到S0
再次触发Minor GC后,S0和Eden 中存活的对象被移动到S1中,S0清空
每次移动时,自动递增计数器,超过默认值时 (印象中是16) ,移动到老年代,如果Eden中没有足够内存分配,也将直接在老年代中分配内存(占用内存大的对象)
老年代中依靠Major GC
GC机制 (六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解 - 掘金 (juejin.cn)
Java集合(容器) ArrayList 源码分析 | JavaGuide
ArrayList源码分析 1 2 3 4 public class ArrayList <E> extends AbstractList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable{ }
ArrayList
继承于 AbstractList
,实现了 List
, RandomAccess
, Cloneable
, java.io.Serializable
这些接口。
List
: 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
RandomAccess
:这是一个标志接口,表明实现这个接口的 List
集合是支持 快速随机访问 的。在 ArrayList
中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
Cloneable
:表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。Java深拷贝和浅拷贝区别_java 深拷贝和浅拷贝-CSDN博客
Serializable
: 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
线程不安全,可以添加null值
扩容机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 public class ArrayList <E> extends AbstractList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L ; private static final int DEFAULT_CAPACITY = 10 ; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private int size; public ArrayList (int initialCapacity) { if (initialCapacity > 0 ) { this .elementData = new Object [initialCapacity]; } else if (initialCapacity == 0 ) { this .elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException ("Illegal Capacity: " + initialCapacity); } } public ArrayList () { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList (Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0 ) { if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this .elementData = EMPTY_ELEMENTDATA; } } public void trimToSize () { modCount++; if (size < elementData.length) { elementData = (size == 0 ) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } } public void ensureCapacity (int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private static int calculateCapacity (Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureCapacityInternal (int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity (int minCapacity) { modCount++; if (minCapacity - elementData.length > 0 ) grow(minCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ; private void grow (int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity < 0 ) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity (int minCapacity) { if (minCapacity < 0 ) throw new OutOfMemoryError (); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
当我们要 add
进第 1 个元素到 ArrayList
时,elementData.length
为 0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal()
方法 ,所以 minCapacity
此时为 10。此时,minCapacity - elementData.length > 0
成立,所以会进入 grow(minCapacity)
方法。
当 add
第 2 个元素时,minCapacity
为 2,此时 elementData.length
(容量)在添加第一个元素后扩容成 10
了。此时,minCapacity - elementData.length > 0
不成立,所以不会进入 (执行)grow(minCapacity)
方法。
添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10。
直到添加第 11 个元素,minCapacity
(为 11)比 elementData.length
(为 10)要大。进入 grow
方法进行扩容。
在grow方法中
int newCapacity = oldCapacity + (oldCapacity >> 1)
,所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)! 奇偶不同,比如:10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数.
“>>”(移位运算符):>>1 右移一位相当于除 2,右移 n 位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了 1 位所以相当于 oldCapacity /2。对于大数据的 2 进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源
length属性、length()方法、size()方法
Java 中的 length
属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
Java 中的 length()
方法是针对字符串说的,如果想看这个字符串的长度则用到 length()
这个方法.
Java 中的 size()
方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看
System.arraycopy() 和 Arrays.copyOf()方法 1 2 3 4 5 6 7 8 9 10 11 12 13 public static native void arraycopy (Object src, int srcPos, Object dest, int destPos, int length) ;
1 2 3 4 5 6 7 8 9 public static int [] copyOf(int [] original, int newLength) { int [] copy = new int [newLength]; System.arraycopy(original, 0 , copy, 0 , Math.min(original.length, newLength)); return copy; }
两者联系和区别
联系:
看两者源代码可以发现 copyOf()
内部实际调用了 System.arraycopy()
方法
区别:
arraycopy()
需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf()
是系统自动在内部新建一个数组,并返回该数组。
ensureCapacity方法 源码上面有,这个方法是给外部调用的,理论上来说,最好在向 ArrayList
添加大量元素之前用 ensureCapacity
方法,以减少增量重新分配的次数
LinkedList 源码分析 基于双向链表实现,项目中基本不会用到,用到这个的场景使用ArrayList
HashMap源码分析 底层 HashMap
默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, HashMap
总是使用 2 的幂作为哈希表的大小。
JDK1.8之前
JDK1.8之前HashMap的底层是数组+链表实现的,通过key的hashCode经过扰动函数( HashMap 的 hash 方法,)计算得到hash值,之后通过(n - 1) & hash
判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法(将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。)解决冲突。
JDK1.8之后
当链表长度大于阈值(默认为 8) 时,会首先调用 treeifyBin()
方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize()
方法对数组扩容。
(二叉查找树、二叉搜索树BST:要么为空树,要么满足如下:1.若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。2.若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。3.任意结点的左、右子树也分别为二叉搜索树。)
(平衡二叉树AVL:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,并且平衡二叉树是一个二叉排序树 ,是最早的自平衡二叉查找树)
(红黑树:在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:性质1. 结点是红色或黑色。性质2. 根结点是黑色。性质3. 所有叶子都是黑色。(叶子是NIL结点) 性质4. 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。)
类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class HashMap <K,V> extends AbstractMap <K,V> implements Map <K,V>, Cloneable, Serializable { private static final long serialVersionUID = 362498820763181265L ; static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; static final int MAXIMUM_CAPACITY = 1 << 30 ; static final float DEFAULT_LOAD_FACTOR = 0.75f ; static final int TREEIFY_THRESHOLD = 8 ; static final int UNTREEIFY_THRESHOLD = 6 ; static final int MIN_TREEIFY_CAPACITY = 64 ; transient Node<k,v>[] table; transient Set<map.entry<k,v>> entrySet; transient int size; transient int modCount; int threshold; final float loadFactor; public HashMap () { this .loadFactor = DEFAULT_LOAD_FACTOR; } public HashMap (Map<? extends K, ? extends V> m) { this .loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false ); } public HashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap (int initialCapacity, float loadFactor) { if (initialCapacity < 0 ) throw new IllegalArgumentException ("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException ("Illegal load factor: " + loadFactor); this .loadFactor = loadFactor; this .threshold = tableSizeFor(initialCapacity); } }
hash 方法 JDK1.8的方法如下
1 2 3 4 5 6 7 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
JDK1.7的方法如下
1 2 3 4 5 6 7 8 static int hash (int h) { h ^= (h >>> 20 ) ^ (h >>> 12 ); return h ^ (h >>> 7 ) ^ (h >>> 4 ); }
相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
Node普通链表节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static class Node <K,V> implements Map .Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this .hash = hash; this .key = key; this .value = value; this .next = next; } public final K getKey () { return key; } public final V getValue () { return value; } public final String toString () { return key + "=" + value; } public final int hashCode () { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue (V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals (Object o) { if (o == this ) return true ; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true ; } return false ; } }
红黑树节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static final class TreeNode <K,V> extends LinkedHashMap .Entry<K,V> { TreeNode<K,V> parent; TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super (hash, key, val, next); } final TreeNode<K,V> root () { for (TreeNode<K,V> r = this , p;;) { if ((p = r.parent) == null ) return r; r = p; }
putMapEntries方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 final void putMapEntries (Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0 ) { if (table == null ) { float ft = ((float )s / loadFactor) + 1.0F ; int t = ((ft < (float )MAXIMUM_CAPACITY) ? (int )ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } else if (s > threshold) resize(); for (Map.Entry<? extends K , ? extends V > e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false , evict); } } }
putVal
方法看下面
put 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); } final V putVal (int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0 ) n = (tab = resize()).length; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this , tab, hash, key, value); else { for (int binCount = 0 ; ; ++binCount) { if ((e = p.next) == null ) { p.next = newNode(hash, key, value, null ); if (binCount >= TREEIFY_THRESHOLD - 1 ) treeifyBin(tab, hash); break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null ; }
下面是JDK1.7的put方法
① 如果定位到的数组位置没有元素 就直接插入。
② 如果定位到的数组位置有元素,遍历以这个元素为头结点的链表,依次和插入的 key 比较,如果 key 相同就直接覆盖,不同就采用头插法插入元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public V put (K key, V value) if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null ) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null ; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this ); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null ; }
resize 方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null ) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0 ; if (oldCap > 0 ) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1 ) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1 ; } else if (oldThr > 0 ) newCap = oldThr; else { newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int )(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0 ) { float ft = (float )newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float )MAXIMUM_CAPACITY ? (int )ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node [newCap]; table = newTab; if (oldTab != null ) { for (int j = 0 ; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null ) { oldTab[j] = null ; if (e.next == null ) newTab[e.hash & (newCap - 1 )] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this , newTab, j, oldCap); else { Node<K,V> loHead = null , loTail = null ; Node<K,V> hiHead = null , hiTail = null ; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0 ) { if (loTail == null ) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null ) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null ); if (loTail != null ) { loTail.next = null ; newTab[j] = loHead; } if (hiTail != null ) { hiTail.next = null ; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
get方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public V get (Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode (int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1 ) & hash]) != null ) { if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null ) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null ); } } return null ; }
ConcurrentHashMap 源码分析 LinkedHashMap 源码分析