集合:集合是一种容器,可以用来存储多个元素
集合和数组的区别:
数组的长度是固定的,而集合是可变的
数组中存储的是同一类型的元素,可以存储任意的类型数据,而集合的都是引用数据类型。如果需要存储基本类型数据则需要存储基本数据类型的包装类型
集合主要分两种:一种是collection单列集合,一种是map双列集合,这里主要讲解单例集合
集合的架构图
Collection:
是单列集合的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是java.util.List和java.util.Set
List:
有序集合(也叫序列),该集合可以精确控制列表中每个元素的插入位置,用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。同时list集合里面可以允许有重复的元素,也可以添加空值元素。
Set:
不可包含重复的元素,set集合也是无序的,比如在存放数据的时候并不是按照元素添加的顺序进行排序的,同时set集合也不能插入空值
Collection相关的API
public class CollectionMethod {
private static Collection<String> list = new ArrayList<String>();
static {
list.add("hello");
list.add("world");
list.add("java");
list.add("mysql");
}
public static void main(String[] args) {
list.add("redis"); // 新增一个元素
list.remove(2); // 在删除的时候可以根据集合中的内容删除,也可以根据下标删除
System.out.println(list.contains("hello")); // 查找集合中时候有这个元素
System.out.println(list.size()); // 获取集合中有多少元素
System.out.println(list.isEmpty());// 判断集合是否为空
list.clear();// 清空集合中的元素
list.addAll(new ArrayList<String>());// 添加多个元素
list.containsAll(new ArrayList<String>());// 判断多个元素是否都存在
list.removeAll(new ArrayList<String>());// 删除多个元素
Object[] array = list.toArray();// 将集合变成一个数组
list.forEach(System.err::println);
// 使用迭代器循环
// iterator方法,表示在一个集合中指针往下移一位 next方法则是取出当前位置的元素,如果在使用next之前没有使用hasNext方法,那么会抛出异常
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String string = it.next();
System.out.println(string);
}
String next = it.next(); // 抛出异常java.util.NoSuchElementException
// 使用增强for循环
for (String string : list) {
}
}
}
List相关的API
public class ListMethod {
private static List<String> list = new ArrayList<String>();
private static List<String> list2 = new ArrayList<String>();
static {
list.add("java");
list.add("mysql");
list.add("redis");
list.add("html");
list2.add("vue");
list2.add("element-ui");
}
public static void main(String[] args) {
list.add("nginx"); // 将指定的元素追加到此列表的末尾(可选操作)。
list.add(2, "axios"); // 将指定的元素插入此列表中的指定位置(可选操作)。
// list.forEach(System.err::println);
list.addAll(list2); // 按指定集合的迭代器(可选操作)返回的顺序将指定集合中的所有元素附加到此列表的末尾。
list.addAll(3, list2); // 将指定集合中的所有元素插入到此列表中的指定位置(可选操作)。
list.clear(); // 从此列表中删除所有元素(可选操作)。
list.contains("hello"); // 如果此列表包含指定的元素,则返回 true 。
list.containsAll(list2); // 如果此列表包含指定 集合的所有元素,则返回true。
boolean b = list.equals(new ArrayList<String>()); // 将指定的对象与此列表进行比较以获得相等性。false
list.get(0); // 返回此列表中指定位置的元素。
list.hashCode();// 返回此列表的哈希码值。
list.indexOf("java"); // 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
list.isEmpty();// 如果此列表不包含元素,则返回 true 。
list.iterator();// 以正确的顺序返回该列表中的元素的迭代器。
list.lastIndexOf("mysql");// 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
ListIterator<String> iterator = list.listIterator();// 返回列表中的列表迭代器(按适当的顺序)。
ListIterator<String> iterator2 = list.listIterator(2);// 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
list.remove(0); // 删除该列表中指定位置的元素(可选操作)。
list.remove("html");// 从列表中删除指定元素的第一个出现(如果存在)(可选操作)。
list.removeAll(list2);// 从此列表中删除包含在指定集合中的所有元素(可选操作)。
// list.replaceAll();//将该列表的每个元素替换为将该运算符应用于该元素的结果。注:这个我还没使用过
list.retainAll(list2);// 仅保留此列表中包含在指定集合中的元素(可选操作)。
list.set(1, "mybatis");// 用指定的元素(可选操作)替换此列表中指定位置的元素。
list.size();// 返回此列表中的元素数。
list.subList(0, 5);// 返回此列表中指定的 fromIndex (含)和 toIndex之间的视图。
list.toArray();// 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
list.toArray(new String[] { "java", "mysql" });// 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。
System.out.println(b);
list.forEach(System.err::println);
}
}
ArrayList:
ArrayList底层实现和扩容机制
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 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;
DEFAULT_CAPACITY:表示默认初始化的长度
EMPTY_ELEMENTDATA:表示有参构造器的空数组实例
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:表示无参构造器的空数组实例
elementData:表示当前集合底层我们所使用的数组,注意:transient修饰符表示不会被序列化
size:记录总数组的大小
无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
上图是arraylist的空参构造器,也就是说当我们在new ArrayList()的时候,集合会自动会创建一个长度为0的空数组,在我们不会集合进行新增操作的时候,集合都不会扩展数组长度
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在我们执行新增元素的时候,由于我们的集合是空的,所以会先给我们集合进行扩容,size是int类型的,而int类型的初始化值是0,所以这里就传递了一个最小容量1
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
这里判断了当前数组是否是空的数组,Math.max该接口表示两个参数当中比较出最大值,而minCapacity容量是1,DEFAULT_CAPACITY上面默认了长度是10,所以这里取出最大值10,然后赋值给minCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
注意:这里的这个modCount++这个属性来自父类AbstractList当中的一个不需要被序列化的值: protected transient int modCount = 0;作用就是表示记录当前集合被修改的次数,防止有多个线程同时去修改它,如果有同时修改则会抛出一个异常
这个时候我们minCapacity容量是10,而这个时候elementData这个数组的长度是0,所以表示当前容量已经不够用了,每次集合进行新增的时候都会来到这个接口,如果minCapacity大于当前数组的长度,表示容量够用不需要进行扩容,所以也不用执行grow接口了,如果小于则表示容量不够用了然后进行扩容,所以grow接口才是真正的为这个集合进行扩容的方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
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);
}
oldCapacity长度是0,newCapacity长度是当前oldCapacity长度的1.5倍,由于我们oldCapacity容量长度是0,而0的1.5倍结果还是0,所以这里进行了判断,最后才将minCapacity的长度10赋值给了newCapacity,
继续往下执行,这里MAX_ARRAY_SIZE是一个最大值减8 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;也就是如果这个集合的长度比这个最大值还大,那么就要去hugeCapacity执行,
最后就用Arrays.copyOf接口,该接口表示将一个数组复制到另外一个数组里面,其中数组中的值也同时要复制到新的数组里面,最后elementData就是一个全新的长度的数组,至此集合的扩容操作就完成了,最后返回到最开始的add方法里面了,最后执行elementData[size++] = e;而这个size初始化值是0,所以在这个全新的数组的第一个位置,下标为0的位置里面添加了传递进来的元素,最后arraylist集合的add操作就完成了
走完了空参的构造器,看看有参的构造器吧
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);
}
}
也就是说我们在创建集合的时候,直接给集合进行长度的初始化new ArrayList(8);表示创建了一个list集合长度是8,这里判断了传递进来的参数的有效性,如果大于0则创建了一个object数组长度就是传递进来的8,如果传递进来的是0,那么就创建一个长度为空的objcet数组,如果是负数那么则抛出一个异常;最后如果执行新增的时候,执行的顺序和空参进行 add的顺序是一样的,唯一区别的是,当长度够用的时候不会去执行grow这个接口,也就是说不用去扩容,当长度不够用的时候又继续进行扩容然后在新增。
总结:
无论是有参还是无参扩容的长度都是当前数组长度的1.5倍。如果长度为0,那么长度默认为10!
Vector:
Vector的底层实现和扩容机制
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* The array buffer into which the components of the vector are
* stored. The capacity of the vector is the length of this array buffer,
* and is at least large enough to contain all the vector's elements.
*
* <p>Any array elements following the last element in the Vector are null.
*
* @serial
*/
protected Object[] elementData;
/**
* The number of valid components in this {@code Vector} object.
* Components {@code elementData[0]} through
* {@code elementData[elementCount-1]} are the actual items.
*
* @serial
*/
protected int elementCount;
/**
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
*
* @serial
*/
protected int capacityIncrement;
elementData:表示当前集合的长度
elementCount:表示当前集合存在的元素个数
capacityIncrement:表示容量扩充的数量
无参构造器
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
/**
* Constructs an empty vector with the specified initial capacity and
* with its capacity increment equal to zero.
*
* @param initialCapacity the initial capacity of the vector
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
/**
* Constructs an empty vector so that its internal data array
* has size {@code 10} and its standard capacity increment is
* zero.
*/
public Vector() {
this(10);
}
当我们使用无参构造器的时候,集合会默认调用有参构造器,然后传递了一个当前集合默认长度为10个元素的数值,而一个参数的构造器又调用了两个参数的构造器,将长度为10的数值和默认容量扩充的数值传递给了两个参数的构造器,而如果根据一个参数的构造器创建对象,使用的长度为负数的话,则会抛出异常,如果都符合要求,则创建了一个Object数组,长度正好是传递过去的数值。使用的就是默认的容量扩充的数量,至此集合的创建初始化就完成。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
注意:这里使用了同步锁synchronized,表示这个接口是线程安全的,modCount表示当前集合被修改的次数,和ArrayList一样,然后进入下一个方法,这里elementCount如果是第一次新增int类型的默认值是0,所以这里传递了一个参数1过去,
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这里做了一个判断,表示如果传递过来的参数大于当前数组的长度,那么表示该数组的长度已经不够用了,则进入grow接口里面进行扩容,反之则退出该方法,然后去执行add,
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
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) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
这里是vector的扩容机制,minCapacity也就是当添加记录元素个数,vector的扩容机制是获取到当前的数组的长度,然后判断当前集合对象的扩容数量是否是默认的0,还是输入的扩容数量,如果大于0则表示扩容数量是外部创建集合的时候给的,就使用给的扩容数量和当前集合的长度进行相加。如果是默认的0,则是让当前的数组长度加自己的长度也就是说是自己的2倍。
newCapacity表示新的数组长度,当新的数组长度还小于当前集合元素个数的时候,就直接将当前元素个数的数值赋值给新的数组长度,
MAX_ARRAY_SIZE表示int的最大值,也就是说当集合元素超过了int类型的长度的时候,那么进入下一个接口做处理,
最后就开始做数组的复制了,和ArrayList一样使用的是Arrays.copyOf接口,至此Vector的扩容就完成。
LinkedList:
看看linkedLisst的底层实现吧
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
size:表示当前集合的元素个数大小和顺序
first:表示头部
last:表示尾部
空参构造器
public LinkedList() {
}
可以看到这里也知识创建了一个LinkedList的对象而已,并没有做任何的操作
public boolean add(E e) {
linkLast(e);
return true;
}
当我们执行新增操作的时候,集合会进入到下一个接口,
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
这里就是LinkedList在做新增元素的底层实现了,
Node,是linkedList里面类里面维护了一个静态的内部类,此类有三个属性,一个item,next,prey,因为linkedList底层使用的是双向链表,所以linkedList没有扩容的说法,如果要想明白linkedList的底层实现,必须得对双向链表有一个基础的认识,
在执行新增的时候,如果是第一次进来我们的last为null,然后创建了一个Node对象,由于last对象是null,所以,创建的newNode对象他的prey和next也都是空,只有item是我们传递进来的一个e对象的。最后我们的尾部last指向了这个newNode,由于我们的l对象是null,所以又将我们的first头部指向了这个newNode对象,
注意:这里first和last头部和尾部都指向了同一个对象,表示这个集合里面目前就存在一个元素,
最后更新集合元素个数size,记录对这个集合的修改次数modCount;至此我们的新增就完成,
最后附上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;
}
}
ArrayList、LinkedList、Vector三个集合的区别
ArrayList:线程不安全,因为底层使用的是数组,进行随机访问所消耗的时间是固定的,所以查询速度快,增删速度慢,
LinkedList:线程不安全,因为底层使用的是双向链表,是不支持快速的随机访问的,所以增删速度快,查询速度慢!
Vector:线程安全,和ArrayList一样底层使用数组,但是他里面的接口基本都使用了同步锁synchronized,所以相比较与ArrayList效率比较慢,而ArrayList扩容是1.5倍,Vector扩容是当前长度的两倍,Vector还可以设置增长因子,ArrayList不可以
Set相关API
public class TestHashSet {
private static Set<String> hashSet = new HashSet<String>();
public static void main(String[] args) {
hashSet.add("hello");
hashSet.addAll(new HashSet<String>());
hashSet.size();
hashSet.isEmpty();
hashSet.equals(hashSet);
hashSet.hashCode();
hashSet.retainAll(hashSet);
hashSet.remove("hello");
hashSet.clear();
hashSet.contains(hashSet);
System.err.println("======================");
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
HashSet:
HashSet的底层实现和扩容机制
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
HashSet里面定义了一个HashMap对象,默认有一个静态的常量Object对象,
public HashSet() {
map = new HashMap<>();
}
我们在创建HashSet的时候,底层会给我们创建了一个map对象,其实HashSet的底层使用的就是HashMap,
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
这里DEFAULT_LOAD_FATOR表示hashMap的加载因子0.75!至此hase的集合对象就创建成功了,其实就是创建了一个hashMap,
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
在我们对集合进行新增的时候,底层调用的就是map.put方法,而map集合又是键值对的方法存储的,所以这里e表示我们要添加的键,而值就是PRESENT,就是我们定义的一个静态常量Object,说直白点就是这个map集合里面的值是统一的object,只是作为一个占位使用的
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
这里调用了putVal方法,在调用putVal方法之前还调用了hash(key)方法,而hash(key)方法主要作用是算出当前key的这个hash值,
注意:这里的hash值并不是完全按照hashCode方法来算的,还做了其他的运算,(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),作用是为了尽可能的使不同的key得到不同的hash值,为了避免这个碰撞,所以增强了这个hash算法
如果传入的这个key是null,那么这个hash值就是0,
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;
}
transient Node<K,V>[] table;表示当前map集合的元素数组,类型是个Node,而这个Node其实就是Map集合里面的一个静态的类部类 static class Node<K,V> implements Map.Entry<K,V> {
1.当我们第一次进来的时候table其实是null,这个这个table是空,那么会进行扩容方法resize()方法,map集合在创建的时候其实是空的,当我们在执行新增的时候,才会判断当前集合长度,如果是空,那么默认创建长度为16,同时,为了处理高并发,在集合长度达到了当前长度的临界值的时候就会进行扩容操作,扩容的倍数是当前集合的2倍,临界值的运算是当前集合长度乘以0.75的加载因子,
2.根据当前的key得到的hash值,去计算应该存放到table表中的具体哪一个索引位置,并且把这个对象赋给p,然后在判断这个p是否为空
2.1.如果为空,那么就说明当前这个hash值在table表中的这个索引位置没有存放任何元素,那么直接创建一个node对象存放到对应的这个位置
2.2.如果不为空,
2.2.1.如果当前元素中的第一个Node对象的p.hash值和当前传入进来的hash值是一样的并且传入的这个key和当前元素中的第一个Node对象中的p.key是一样的,那么就说明当前加入的对象和当前元素中的对象是同一个对象,或者这两个对象的内容相同,两个条件满足其一,那么就证明是重复数据不不能新增进去
注意:这里的equals方法是根据重写之后的equals方法来确定比较的,而重写的equals方法里面的内容具体应该怎么比较的,这个是根据程序员来确定的,重写之后可以根据自己的需要的方式来进行比较,也可以不用修改按照以前的方式来比较。
2.2.2.然后在进行判断,如果当前加入的对象是否为红黑树对象,如果是一个红黑树那么就按照红黑数的方式来添加,putTreeVal(this, tab, hash, key, value)
2.2.3.如果前两个都不满足,那么说明当前加入的对象和当前位置里面的对象不是重复的,同时也不是一个红黑树对象,所以就按照链表的方式来添加,然后使用for死循环的方式来判断当前链表中的对象和当前需要加入的对象是否是一样的,如果是重复对象,那么退出循环,如果没有重复的就直接将当前需要加入的对象通过链表的方法添加到后面,
2.2.4.最后在一次判断,判断当前binCount就是说在链表中元素的大小是否大于7,如果达到了8个节点,就调用treeifyBin(tab, hash);进入这个方法之后有一个判断,表示,如果当前集合长度小于64,那么会将当前这个集合进行在一次扩容,如果大于64,那么就会将当前链表变成红黑树
3.最后++modCount表示对这个集合修改了一次,最后在判断,如果当前size的长度大于当前集合长度的临界值,那么在一次进行扩容,扩容为原来的2倍
4. 而最后的这个afterNodeInsertion(evict)方法,其实在hashMap中没有任何实现,主要目的是为了hashMap的一个子类linkedHashMap,给子类去实现用的,比如做一个有序的双向链表。最后返回null就代表成功了,如果返回的不是空而是以前的那个旧的值,就代表新增失败,添加的这个值在集合中已经存在了