集合
1. ArrayList源码分析
1.1 继承关系
注意:这里的AbstarctList实现了List,而AbstarctList的子类ArrayList也实现了List,子类实现了List不用看,是一个mistake。
分析:
1)为什么要先继承AbstarctList,而让AbstarctList继承List,而不是直接继承List?
这里有一个思想,接口中的都是抽象方法,而抽象类中可以有抽象方法也可以有具体实现的方法,我们可以让AbstractList实现接口中的一些通用方法,而具体的类如ArrayList就来继承这个AbstractList,就可以拿到通用方法,然后直接在实现一些自己的特有方法,这样可以让代码更简洁,把继承结构中最底层的通用方法都抽取出来一起实现,减少重复代码,所以一般看到一个类上面还有一个抽象类,应该就是这个作用。
2)RandomAccess接口,这是一个标记性接口,它的作用就是快速随机存取,有关效率的问题,如果实现了该接口,那么使用普通的for循环类遍历,性能会更高,例如ArrayList。没有使用该接口的话,使用iterator迭代器来迭代,这样性能更高,例如linkedlist,所以这个标记知识为了让我们直到用什么样的方式获取数据性能更好。
3)Cloneable接口,实现了该接口,就可以使用Object.Clone()方法了。
4)Serializable接口,实现类该序列化接口,表面该类可以被序列化,及可以从类变成字节流传输,然后还可以从字节流变成原来类。
1.2 类中的属性
//版本号
private static final long serialVersionUID = 8683452581122892189L;
//如果使用了无参构造创建对象,第一次put时会将容量扩为10
//如果用有参构造方法创建了一个容量为1 的,当添加下一个元素时,会扩容为2,
//而不是10,所以为了提高效率,不知道具体存储数据的大小,一般给定初始容量为10
private static final int DEFAULT_CAPACITY = 10;
//空对象数组,当使用有参构造器时,若参数为0则将此数组付给真实存储数据的数组
private static final Object[] EMPTY_ELEMENTDATA = {};
/*
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);
}
}
*/
//默认空对象数组,创建无参对象,第一次扩容会用到
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//元素数组
transient Object[] elementData;
//实际存储的元素大小,默认为0
private int size;
//最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
1.3 构造方法
ArrayList有三个构造方法:
1)无参构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
将空的数组赋给真实存储数据的数组,等到第一次put时会扩容为10。
2)有参构造方法(参数为数字)
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {//当给定容量大于0,直接创建该容量的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//若为0,将空数组付给实际存数据的数组
this.elementData = EMPTY_ELEMENTDATA;
} else {//若小于0,抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
3)有参构造方法(参数为collection)
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray(); //将集合转为数组赋给真实数组
if ((size = elementData.length) != 0) {//如果不为空
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//每个集合的toarray()的实现方法不一样,如果不是Object[].class类型,转型赋值
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {//如果为空,则赋一个空数组给他
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
1.4 核心方法
1.4.1 add()方法(有四个)
1)public boolean add(E e),默认直接在list末尾添加元素
public boolean add(E e) {
//确定实际容量是否可以容纳size+1的大小,若不能则会扩容为原来的1.5倍(若初始容量为0,则扩容为10)
ensureCapacityInternal(size + 1); // 增加modCount!
//容量够后,给数组赋值
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity),给空数组的minCapacity赋初始值
private void ensureCapacityInternal(int minCapacity) {
//若初始数组为空,则将DEFAULT_CAPACITY(10)赋值给minCapacity
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确认实际的容量,上面只是将minCapacity=10,这个方法就是真正的判断elementData是否够用
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity),真正的判断实际容量是否足够
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//对数组操作时候,会记录次数
// overflow-conscious code
if (minCapacity - elementData.length > 0)//判断实际容量是否足够所需最小容量minCapacity
grow(minCapacity);
}
private void grow(int minCapacity) ,扩容方法,前面的所有方法都是在堆容量大小进行操作,这个方法才进行真实的扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//记录原数组容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容后大小为原来1.5倍
if (newCapacity - minCapacity < 0)//这里就是为0容量数组写的,
newCapacity = minCapacity;//将所需容量10赋值给新容量
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果所需容量超过了最大能容量的限制
newCapacity = hugeCapacity(minCapacity);//将最大容量给它
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) ,用来赋最大值
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
/*如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之将MAX_ARRAY_SIZE返回。因为maxCapacity是三倍的minCapacity,可能扩充的太大了,就用minCapacity来判断了。
Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是说最大也就能给到第一个数值。还是超过了这个限制,就要溢出了。相当于arraylist给了两层防护。*/
return (minCapacity > MAX_ARRAY_SIZE) ?//将所需容量进行比较,若小于最大值,则赋一个较小的"最大值"
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2)public void add(int index, E element) 在特定位置插入元素
public void add(int index, E element) {
rangeCheckForAdd(index);
//和add()里的一样,确定容量是否够用
ensureCapacityInternal(size + 1); // Increments modCount!!
//这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位,
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) 检查给的索引是否超过最大范围
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
总结:一般情况下会扩容1.5倍,特殊情况下:如扩容两倍后的大小已经超过了能有容量大小的最大值,则只能将数组扩容为最大值;初始化容量为0的数组,在第一次进行put时,直接扩容为10
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8agU8HTP-1622365268044)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1622345399843.png)]
1.4.2 删除方法
1)public E remove(int index) 根据索引删除值,并返回该值
public E remove(int index) {
rangeCheck(index);//判断索引是否越界
modCount++;//记录操作次数
E oldValue = elementData(index);//记录要删除的数,方便后面返回
int numMoved = size - index - 1;//记录有多少个值要前移
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);//进行前移
//后面的值赋给前面,那么最后还会有一个值没用
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
2)remove(Object):这个方法可以看出来,arrayList是可以存放null值得。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index)
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
3)clear():将elementData中每个元素都赋值为null,等待垃圾回收将这个给回收掉,所以叫clear
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
4)removeAll(collection c):
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//判断是否为空
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement)
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
1.4.3 set()方法
public E set(int index, E element) 修改指定索引的元素,并返回原元素
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
1.4.4 indexOf()方法
public int indexOf(Object o) ,返回指定元素第一次出现的索引,若没有找到返回-1;与此函数对应的lastIndexOf,表示从尾部开始查找。
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
1.4.5 get()方法
public E get(int index) {
// 检验索引是否合法
rangeCheck(index);
return elementData(index);
}
说明:get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的是,在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下
E elementData(int index) {
return (E) elementData[index];
}
说明:返回的值都经过了向下转型(Object -> E),这些是对我们应用程序屏蔽的小细节
1.5 ArrayList总结
1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是grow()方法。
4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。
2. LinkedList源码分析
2.1 继承关系
分析
我们可以看到,LinkedList在最底层,说明他功能最为强大,同时我们发现ArrayList只有四层,而这个有五层,多了一层AbstractSequentialList抽象类,为什么呢?
- 减少实现顺序存取(例如LinkedList)这种类的工作,抽象出类似LinkedList这种类的一些共同的方法
- 那么以后如果自己想实现顺序存取这种特性的类(就是链表形式),那么就继承这个AbstractSequentialList抽象类
- 这样分层,就很符合我们抽象类的概念,越在高处的类,就越抽象,越往底层的类,就越有自己独特的个性。
- LinkedList的类继承结构中,我们着重要看的是Deque接口,这个接口表示一个双端队列,那么也意味着LinkedList是双端队列的一种实现,所以,给予双端队列的操作在LinkedList中全部有效。
- List接口:列表,add,set等一些对列表进行操作的方法(需要注意的是,LinkedList间接继承的AbstractList也实现了List)
- Deque接口:有队列的各种特性
- Cloneable接口:能够复制,使用copy方法
- Serializable接口:能够序列化
- 注意到没有RandomAccess,那么久推荐使用迭代器遍历,这里可以使用foreach。
2.2 类的属性
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// 实际元素个数
transient int size = 0;
// 头结点
transient Node<E> first;
// 尾结点
transient Node<E> last;
}
2.3 构造方法
有两个构造方法
1)空参构造
public LinkedList() {
}
2)有参构造
//将集合c中的各个元素构建成LinkedList链表。
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
说明:会调用无参构造方法,将集合中所有元素添加到LinkedList中
2.4 内部类(Node)
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;
}
}
2.5 核心方法
2.5.1 add方法
1)add(E) 会直接添加到末尾
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) Links e as last element.
void linkLast(E e) {
final Node<E> l = last;//记录添加前last指向的元素
//创建要添加的结点,该节点pre指向前面的last,next为空
final Node<E> newNode = new Node<>(l, e, null);
//将last指向最后一个
last = newNode;
if (l == null)//如果原来的链表的last为空,及链表为空
first = newNode;//那就将first指向newNode
else
l.next = newNode;//将原last元素指向新结点
size++;//链表长度增加
modCount++;//操作次数增加
}
2.5.2 addAll()方法
addAll有两个重载方法,public boolean addAll(Collection<? extends E> c) 和 public boolean addAll(int index, Collection<? extends E> c),我们平时习惯调用前者会转化为后者类型。
1)addAll©
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
2)addAll(size,c): 将指定 collection 中的所有元素从指定位置开始插入此列表。
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index);
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)//如果插入数组长度为0,则返回
return false;
Node<E> pred, succ;//pred指向index的前一个,succ指向index处
//当index为最后一个数的后一个时。两种情况:1:原链表为空,index为0。 2:插入的是最后一个
if (index == size) {
succ = null;//插入位置由于是最后一个,该索引出没有元素
pred = last;//让pred指向最后一个
} else {//插入索引位置不是最后一个
succ = node(index);//指向index出索引的元素
pred = succ.prev;//指向index处元素的前一个
}
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)//如果为null,说明要么只有一个元素,要么没有元素
first = newNode;//添加新结点
else
pred.next = newNode;将新节点与最后一个结点连接起来
pred = newNode;//指针后移
}
if (succ == null) {//如果索引处没有元素
last = pred;//last指向最后添加的元素
} else {
pred.next = succ;//让最后一个添加的元素与原index索引处元素相连接
succ.prev = pred;//让原index索引处元素的pre指向新添加的元素
}
size += numNew;//集合大小扩大collection大小
modCount++;//操作次数加一
return true;
}
Node node(int index) 用于获取该索引下的结点
Node<E> node(int index) {
// 判断插入的位置在链表前半段或者是后半段
if (index < (size >> 1)) { // 插入位置在前半段
Node<E> x = first;
for (int i = 0; i < index; i++) // 从头结点开始正向遍历
x = x.next;
return x; // 返回该结点
} else { // 插入位置在后半段
Node<E> x = last;
for (int i = size - 1; i > index; i--) // 从尾结点开始反向遍历
x = x.prev;
return x; // 返回该结点
}
}
说明:在根据索引查找结点时,会有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。
2.5.3 remove(Object o)方法
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
unlink(xxxx)
/**
* Unlinks non-null node x.
*/
//不能传一个null值过,注意,看之前要注意之前的next、prev这些都是谁。
E unlink(Node<E> x) {
// assert x != null;
//拿到节点x的三个属性
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
//这里开始往下就进行移除该元素之后的操作,也就是把指向哪个节点搞定。
if (prev == null) {
//说明移除的节点是头节点,则first头节点应该指向下一个节点
first = next;
} else {
//不是头节点,prev.next=next:有1、2、3,将1.next指向3
prev.next = next;
//然后解除x节点的前指向。
x.prev = null;
}
if (next == null) {
//说明移除的节点是尾节点
last = prev;
} else {
//不是尾节点,有1、2、3,将3.prev指向1. 然后将2.next=解除指向。
next.prev = prev;
x.next = null;
}
//x的前后指向都为null了,也把item为null,让gc回收它
x.item = null;
size--; //移除一个节点,size自减
modCount++;
return element; //由于一开始已经保存了x的值到element,所以返回。
}
2.5.4 get(index) 查询方法
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
//这里没有什么,重点还是在node(index)中
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
/**
* Returns the (non-null) Node at the specified element index.
*/
//这里查询使用的是先从中间分一半查找
Node<E> node(int index) {
// assert isElementIndex(index);
//"<<":*2的几次方 “>>”:/2的几次方,例如:size<<1:size*2的1次方,
//这个if中就是查询前半部分
if (index < (size >> 1)) {//index<size/2
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//前半部分没找到,所以找后半部分
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
2.5.5 indexOf(Object o)第一次出现的位置
//这个很简单,就是通过实体元素来查找到该元素在链表中的位置。跟remove中的代码类似,只是返回类型不一样。
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
2.6 LinkedList总结
1)linkedList本质上是一个双向链表,通过一个Node内部类实现的这种链表结构。
2)能存储null值
3)跟arrayList相比较,就真正的知道了,LinkedList在删除和增加等操作上性能好,而ArrayList在查询的性能上好
4)从源码中看,它不存在容量不足的情况
5)linkedList不光能够向前迭代,还能像后迭代,并且在迭代的过程中,可以修改值、添加值、还能移除值。
6)linkedList不光能当链表,还能当队列使用,这个就是因为实现了Deque接口。
3. HashMap源码分析
3.1 继承关系
1)继承结构
2)实现接口
Map<K,V>:在AbstractMap抽象类中已经实现过的接口,这里又实现,实际上是多余的。但每个集合都有这样的错误,也没过大影响
Cloneable:能够使用Clone()方法,在HashMap中,实现的是浅层次拷贝,即对拷贝对象的改变会影响被拷贝的对象。
Serializable:能够使之序列化,即可以将HashMap对象保存至本地,之后可以恢复状态。
3.2 HashMap类的属性
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// 序列号
private static final long serialVersionUID = 362498820763181265L;
// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认的填充因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 当桶(bucket)上的结点数大于这个值时会转成红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 桶中结构转化为红黑树对应的table的最小大小
static final int MIN_TREEIFY_CAPACITY = 64;
// 存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table;
// 存放具体元素的集
transient Set<map.entry<k,v>> entrySet;
// 存放元素的个数,注意这个不等于数组的长度。
transient int size;
// 每次扩容和更改map结构的计数器
transient int modCount;
// 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容
int threshold;
// 填充因子
final float loadFactor;
}
3.3 HashMap构造方法
1)public HashMap() 无参构造方法,默认负载因子 0.75,默认初始容量 64
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
2)public HashMap(int initialCapacity) 参数为初始容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
3)public HashMap(int initialCapacity, float loadFactor) 参数为初始容量与初始加载因子
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)//初始容量不能小于0
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)//初始容量最大为 MAXIMUM_CAPACITY
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//判断是否小于0,或是否为数字
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;//属性合法则赋值
this.threshold = tableSizeFor(initialCapacity);
}
HashMap的容量必须为2的n次幂,tableSizeFor(int cap)方法会将初始容量转为大于此容量的,最小的2的n次幂。
/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
public HashMap(Map<? extends K, ? extends V> m) 参数为一个map,
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // table为该map的结点数组,如果该map为空
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);//将t变成2的n次幂
}
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);
}
}
}
3.4 put方法
1)put(K key,V value)
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)
HashMap并没有直接提供putVal接口给用户调用,而是提供的put函数,而put函数就是通过putVal来插入元素的。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict)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) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
3.5 get方法
1) get(Object key)
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
2)getNode(int hash,Pbject key),通过key的hash值和key获取value值
HashMap并没有直接提供getNode接口给用户调用,而是提供的get函数,而get函数就是通过getNode来取得元素的。
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// table已经初始化,长度大于0,根据hash寻找table中的项也不为空
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 桶中第一项(数组元素)相等
if (first.hash == hash && // always check first node
((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;
}
3.6 resize方法
进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,要尽量避免resize。
final Node<K,V>[] resize() {
// 当前table保存
Node<K,V>[] oldTab = table;
// 保存table大小
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 保存当前阈值
int oldThr = threshold;
int newCap, newThr = 0;
// 之前table大小大于0
if (oldCap > 0) {
// 之前table大于最大容量
if (oldCap >= MAXIMUM_CAPACITY) {
// 阈值为最大整形
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 容量翻倍,使用左移,效率更高
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 阈值翻倍
newThr = oldThr << 1; // double threshold
}
// 之前阈值大于0
else if (oldThr > 0)
newCap = oldThr;
// oldCap = 0并且oldThr = 0,使用缺省值(如使用HashMap()构造函数,之后再插入一个元素会调用resize函数,会进入这一步)
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 新阈值为0
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"})
// 初始化table
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 之前的table已经初始化过
if (oldTab != null) {
// 复制元素,重新进行hash
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 { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
// 将同一桶中的元素根据(e.hash & oldCap)是否为0进行分割,分成两个不同的链表,完成rehash
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;
}
resize()
3.7 总结
从putVal源代码中我们可以知道,当插入一个元素的时候size就加1,若size大于threshold的时候,就会进行扩容。假设我们的capacity大小为32,loadFator为0.75,则threshold为24 = 32 * 0.75,
此时,插入了25个元素,并且插入的这25个元素都在同一个桶中,桶中的数据结构为红黑树,则还有31个桶是空的,也会进行扩容处理,其实,此时,还有31个桶是空的,好像似乎不需要进行扩容处理,
但是是需要扩容处理的,因为此时我们的capacity大小可能不适当。我们前面知道,扩容处理会遍历所有的元素,时间复杂度很高;前面我们还知道,经过一次扩容处理后,元素会更加均匀的分布在各个桶中,
会提升访问效率。所以,说尽量避免进行扩容处理,也就意味着,遍历元素所带来的坏处大于元素在桶中均匀分布所带来的好处。