自学Java——集合
数据结构:存储数据的某种结构
(1)底层的物理结构
①数组:开辟连续的存储空间,每一个元素使用[下标]进行区别
②链式:不需要开辟连续的存储空间,但是需要“结点”来包装要存储的数据,结点包含两部分内容:A、数据 B、记录其他结点的地址,例如:next,pre,left,right,parent等
(2)表现出来的逻辑结构:动态数组、单向链表、双向链表、队列、栈、二叉树、哈希表、图等
1.2 手动实现一些逻辑结构
1、动态数组
包含:
(1)内部使用一个数组,用来存储数据
(2)内部使用一个total,记录实际存储的元素的个数
public class MyArrayList { //为什么使用Object,因为只是说这个容器是用来装对象的,但是不知道用来装什么对象。 private Object[] data; private int total; public MyArrayList(){ data = new Object[5]; } //添加一个元素 public void add(Object obj){ //检查是否需要扩容 checkCapacity(); data[total++] = obj; } private void checkCapacity() { //如果data满了,就扩容为原来的2倍 if(total >= data.length){ data = Arrays.copyOf(data, data.length*2); } } //返回实际元素的个数 public int size(){ return total; } //返回数组的实际容量 public int capacity(){ return data.length; } //获取[index]位置的元素 public Object get(int index){ //校验index的合理性范围 checkIndex(index); return data[index]; } private void checkIndex(int index) { if(index<0 || index>=total){ throw new RuntimeException(index+"对应位置的元素不存在"); // throw new IndexOutOfBoundsException(index+"越界"); } } //替换[index]位置的元素 public void set(int index, Object value){ //校验index的合理性范围 checkIndex(index); data[index] = value; } //在[index]位置插入一个元素value public void insert(int index, Object value){ /* * (1)考虑下标的合理性 * (2)总长度是否够 * (3)[index]以及后面的元素往后移动,把[index]位置腾出来 * (4)data[index]=value 放入新元素 * (5)total++ 有效元素的个数增加 */ //(1)考虑下标的合理性:校验index的合理性范围 checkIndex(index); //(2)总长度是否够:检查是否需要扩容 checkCapacity(); //(3)[index]以及后面的元素往后移动,把[index]位置腾出来 /* * 假设total = 5, data.length= 10, index= 1 * 有效元素的下标[0,4] * 移动:[1]->[2],[2]->[3],[3]->[4],[4]->[5] * 移动元素的个数:total-index */ System.arraycopy(data, index, data, index+1, total-index); //(4)data[index]=value 放入新元素 data[index] = value; //(5)total++ 有效元素的个数增加 total++; } //返回所有实际存储的元素 public Object[] getAll(){ //返回total个 return Arrays.copyOf(data, total); } //删除[index]位置的元素 public void remove(int index){ /* * (1)校验index的合理性范围 * (2)移动元素,把[index+1]以及后面的元素往前移动 * (3)把data[total-1]=null 让垃圾回收器尽快回收 * (4)总元素个数减少 total-- */ //(1)考虑下标的合理性:校验index的合理性范围 checkIndex(index); //(2)移动元素,把[index+1]以及后面的元素往前移动 /* * 假设total=8, data.length=10, index = 3 * 有效元素的范围[0,7] * 移动:[4]->[3],[5]->[4],[6]->[5],[7]->[6] * 移动了4个:total-index-1 */ System.arraycopy(data, index+1, data, index, total-index-1); //(3)把data[total-1]=null 让垃圾回收器尽快回收 data[total-1] = null; // (4)总元素个数减少 total-- total--; } //查询某个元素的下标 public int indexOf(Object obj){ if(obj == null){ for (int i = 0; i < total; i++) { if(data[i] == null){//等价于 if(data[i] == obj) return i; } } }else{ for (int i = 0; i < data.length; i++) { if(obj.equals(data[i])){ return i; } } } return -1; } //删除数组中的某个元素 //如果有重复的,只删除第一个 public void remove(Object obj){ /* * (1)先查询obj的[index] * (2)如果存在,就调用remove(index)删除就可以 */ //(1)先查询obj的[index] int index = indexOf(obj); if(index != -1){ remove(index); } //不存在,可以什么也不做 //不存在,也可以抛异常 //throw new RuntimeException(obj + "不存在"); } public void set(Object old, Object value){ /* * (1)查询old的[index] * (2)如果存在,就调用set(index, value) */ // (1)查询old的[index] int index = indexOf(old); if(index!=-1){ set(index, value); } //不存在,可以什么也不做 } }
2、单向链表
包含:
(1)包含一个Node类型的成员变量first:用来记录第一个结点的地址
如果这个链表是空的,还没有任何结点,那么first是null。
最后一个结点的特征:就是它的next是null
(2)内部使用一个total,记录实际存储的元素的个数
(3)使用了一个内部类Node
private class Node{ Object data; Node next;
Node first; }
public class SingleLinkedList { //这里不需要数组,不需要其他的复杂的结构,我只要记录单向链表的“头”结点 private Node first;//first中记录的是第一个结点的地址 private int total;//这里我记录total是为了后面处理的方便,例如:当用户获取链表有效元素的个数时,不用现数,而是直接返回total等 /* * 内部类,因为这种Node结点的类型,在别的地方没有用,只在单向链表中,用于存储和表示它的结点关系。 * 因为我这里涉及为内部类型。 */ private class Node{ Object data;//因为数据可以是任意类型的对象,所以设计为Object Node next;//因为next中记录的下一个结点的地址,因此类型是结点类型 //这里data,next没有私有化,是希望在外部类中可以不需要get/set,而是直接“结点对象.data","结点对象.next"使用 Node(Object data, Node next){ this.data = data; this.next = next; } } public void add(Object obj){ /* * (1)把obj的数据,包装成一个Node类型结点对象 * (2)把新结点“链接”当前链表的最后 * ①当前新结点是第一个结点 * 如何判断是否是第一个 if(first==null)说明暂时还没有第一个 * ②先找到目前的最后一个,把新结点链接到它的next中 * 如何判断是否是最后一个 if(某个结点.next == null)说明这个结点是最后一个 */ // (1)把obj的数据,包装成一个Node类型结点对象 //这里新结点的next赋值为null,表示新结点是最后一个结点 Node newNode = new Node(obj, null); //①当前新结点是第一个结点 if(first == null){ //说明newNode是第一个 first = newNode; }else{ //②先找到目前的最后一个,把新结点链接到它的next中 Node node = first; while(node.next != null){ node = node.next; } //退出循环时node指向最后一个结点 //把新结点链接到它的next中 node.next = newNode; } total++; } public int size(){ return total; } public Object[] getAll(){ //(1)创建一个数组,长度为total Object[] all = new Object[total]; //(2)把单向链表的每一个结点中的data,拿过来放到all数组中 Node node = first; for (int i = 0; i < total; i++) { // all[i] = 结点.data; all[i] = node.data; //然后node指向下一个 node = node.next; } //(3)返回数组 return all; } public void remove(Object obj){ if(obj == null){ //(1)先考虑是否是第一个 if(first!=null){//链表非空 //要删除的结点正好是第一个结点 if(first.data == null){ //让第一个结点指向它的下一个 first = first.next; total--; return; } //要删除的不是第一个结点 Node node = first.next;//第二个结点 Node last = first; while(node.next!=null){//这里不包括最后一个,因为node.next==null,不进入循环,而node.next==null是最后一个 if(node.data == null){ last.next = node.next; total--; return; } last = node; node = node.next; } //单独判断最后一个是否是要删除的结点 if(node.data == null){ //要删除的是最后一个结点 last.next = null; total--; return; } } }else{ //(1)先考虑是否是第一个 if(first!=null){//链表非空 //要删除的结点正好是第一个结点 if(obj.equals(first.data)){ //让第一个结点指向它的下一个 first = first.next; total--; return; } //要删除的不是第一个结点 Node node = first.next;//第二个结点 Node last = first; while(node.next!=null){//这里不包括最后一个,因为node.next==null,不进入循环,而node.next==null是最后一个 if(obj.equals(node.data)){ last.next = node.next; total--; return; } last = node; node = node.next; } //单独判断最后一个是否是要删除的结点 if(obj.equals(node.data)){ //要删除的是最后一个结点 last.next = null; total--; return; } } } } public int indexOf(Object obj){ if(obj == null){ Node node = first; for (int i = 0; i < total; i++) { if(node.data == null){ return i; } node = node.next; } }else{ Node node = first; for (int i = 0; i < total; i++) { if(obj.equals(node.data)){ return i; } node = node.next; } } return -1; } }
因为集合的类型很多,那么我们把它们称为集合框架。
集合框架分为两个家族:Collection(一组对象)和Map(一组映射关系、一组键值对)
它们虽然:有些可能是有序的,有些可能是无序的,有些可能可以重复的,有些不能重复的,但是它们有共同的操作规范,因此这些操作的规范就抽象为了Collection接口。
常用方法:
(1)boolean add(Object obj):添加一个
(2)boolean addAll(Collection c):添加多个
(3)boolean remove(Object obj):删除一个
(4)boolean removeAll(Collection c ): 删除多个
(5)boolean contains(Object c):是否包含某个
(6)boolean containsAll(Collection c): 是否包含所有
(7)boolean isEmpty():是否为空
(8)int size():获取元素个数
(9)void clear():清空集合
(10)Object[] toArray():获取所有元素
(11)Iterator iterator(): 获取遍历当前集合的迭代器对象
(12)retainAll(Collection c):求当前集合与c集合的交集
Collection c = ....; Iterator iter = c.iterator(); while(iter.hashNext()){ Object obj = iter.next(); //... }
2.Iterator 接口的方法:
1)boolean hasNext()
(2)Object next()
(3)void remove()
3、foreach
Collection c = ....; for(Object obj : c){ //... }
什么样的集合(容器)能够使用foreach遍历?
(1)数组:
(2)实现了java.lang.Iterable接口
这个接口有一个抽象方法:Iterator iterator()
Iterator也是一个接口,它的实现类,通常在集合(容器)类中用内部类实现。并在iterator()的方法中创建它的对象。
public class MyArrayList implements Iterable{ //为什么使用Object,因为只是说这个容器是用来装对象的,但是不知道用来装什么对象。 private Object[] data; private int total; //其他代码省略.... @Override public Iterator iterator() { return new MyItr(); } private class MyItr implements Iterator{ private int cursor;//游标 @Override public boolean hasNext() { return cursor!=total; } @Override public Object next() { return data[cursor++]; } } }
思考:如果遍历数组,什么情况下选用foreach,什么情况下选用for循环?
当如果你的操作中涉及到[下标]操作时,用for最好。
当你只是查看元素的内容,那么选foreach更简洁一些。
思考:如果遍历Collection系列集合,什么情况下选用foreach,是否能选用for循环?
首先考虑使用foreach,如果该集合也有索引信息的话,也可以通过for来操作,如果没有下标的信息,就不要用for。即,如果该集合的物理结构是数组的,那么可以用for,如果物理结构是链式,那么使用下标操作效率很低。
思考:如果遍历Collection系列集合,什么情况下选用foreach,什么情况下使用Iterator?
如果只是查看集合的元素,使用foreach,代码会更简洁。
但是如果要涉及到在遍历集合的同时根据某种条件要删除元素等操作,那么选用Iterator。
(因为如果用forache的时候删除集合中的元素时,会报异常,也可能不报错但是结果可能有问题,用foreach删除的时候调用的是集合remove方法,遍历调用的是Iterator方法)
1.4 List
1.4.1 List概述
List:是Collection的子接口。
List系列的常用集合:ArrayList、Vector、LinkedList、Stack
12.4.2 List的API
常用方法:
(1)boolean add(Object obj):添加一个
(2)boolean addAll(Collection c):添加多个
(3)void add(int index, Object obj):添加一个,指定位置添加
(4)void addAll(int index, Collection c):添加多个
(5)boolean remove(Object obj):删除一个
(6)Object remove(int index):删除指定位置的元素,并返回刚刚删除的元素
(7)boolean removeAll(Collection c ): 删除多个
(8)boolean contains(Object c):是否包含某个
(9)boolean containsAll(Collection c): 是否包含所有
(10)boolean isEmpty():是否为空
(11)int size():获取元素个数
(12)void clear():清空集合
(13)Object[] toArray():获取所有元素
(14)Iterator iterator(): 获取遍历当前集合的迭代器对象
(15)retainAll(Collection c):求当前集合与c集合的交集
(16)ListIterator listIterator():获取遍历当前集合的迭代器对象,这个迭代器可以往前、往后遍历
(17)ListIterator listIterator(int index):从[index]位置开始,往前或往后遍历
(18)Object get(int index):返回index位置的元素
(19)List subList(int start, int end):截取[start,end)部分的子列表
12.4.3 ListIterator 接口
Iterator 接口的方法:
(1)boolean hasNext()
(2)Object next()
(3)void remove()
ListIterator 是 Iterator子接口:增加了如下方法
(遍历的同时可以使用)
(4)void add(Object obj)
(5)void set(Object obj)
(6)boolean hasPrevious()
(7)Object previous()
(8)int nextIndex()
(9)int previousIndex()
1.4.4 List的实现类们的区别
ArrayList、Vector、LinkedList、Stack
(1)ArrayList、Vector:都是动态数组
Vector是最早版本的动态数组,线程安全的,默认扩容机制是2倍,支持旧版的迭代器Enumeration
ArrayList是后增的动态数组,线程不安全的,默认扩容机制是1.5倍
(2)动态数组与LinkedList的区别
动态数组:底层物理结构是数组
优点:根据[下标]访问的速度很快
缺点:需要开辟连续的存储空间,而且需要扩容,移动元素等操作
LinkedList:底层物理结构是双向链表
优点:
缺点:我们查找元素时,只能从first或last开始查找
(3)Stack:栈
是Vector的子类。比Vector多了几个方法,能够表现出“先进后出或后进先出”的特点。
①Object peek():访问栈顶元素
②Object pop():弹出栈顶元素
③push():把元素压入栈顶
(4)LinkedList可以作为很多种数据结构使用
单链表:只关注next就可以
队列:先进先出,找对应的方法
双端队列(JDK1.6加入):两头都可以进出,找对应的方法
栈:先进后出,找对应的方法
建议:虽然LinkedList是支持对索引进行操作,因为它实现List接口的所有方法,但是我们不太建议调用类似这样的方法,因为效率比较低。
12.4.5 源码分析
(1)Vector
public Vector() { this(10);//指定初始容量initialCapacity为10 } public Vector(int initialCapacity) { this(initialCapacity, 0);//指定capacityIncrement增量为0 } public Vector(int initialCapacity, int capacityIncrement增量为0) { super(); //判断了形参初始容量initialCapacity的合法性 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); //创建了一个Object[]类型的数组 this.elementData = new Object[initialCapacity];//默认是10 //增量,默认是0,如果是0,后面就按照2倍增加,如果不是0,后面就按照你指定的增量进行增量 this.capacityIncrement = capacityIncrement; }
//synchronized意味着线程安全的 public synchronized boolean add(E e) { modCount++; //看是否需要扩容 ensureCapacityHelper(elementCount + 1); //把新的元素存入[elementCount],存入后,elementCount元素的个数增1 elementData[elementCount++] = e; return true; } private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code //看是否超过了当前数组的容量 if (minCapacity - elementData.length > 0) grow(minCapacity);//扩容 } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length;//获取目前数组的长度 //如果capacityIncrement增量是0,新容量 = oldCapacity的2倍 //如果capacityIncrement增量是不是0,新容量 = oldCapacity + capacityIncrement增量; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); //如果按照上面计算的新容量还不够,就按照你指定的需要的最小容量来扩容minCapacity if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果新容量超过了最大数组限制,那么单独处理 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //把旧数组中的数据复制到新数组中,新数组的长度为newCapacity elementData = Arrays.copyOf(elementData, newCapacity); }
public boolean remove(Object o) { return removeElement(o); } public synchronized boolean removeElement(Object obj) { modCount++; //查找obj在当前Vector中的下标 int i = indexOf(obj); //如果i>=0,说明存在,删除[i]位置的元素 if (i >= 0) { removeElementAt(i); return true; } return false; } public int indexOf(Object o) { return indexOf(o, 0); } public synchronized int indexOf(Object o, int index) { if (o == null) {//要查找的元素是null值 for (int i = index ; i < elementCount ; i++) if (elementData[i]==null)//如果是null值,用==null判断 return i; } else {//要查找的元素是非null值 for (int i = index ; i < elementCount ; i++) if (o.equals(elementData[i]))//如果是非null值,用equals判断 return i; } return -1; } public synchronized void removeElementAt(int index) { modCount++; //判断下标的合法性 if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } else if (index < 0) { throw new ArrayIndexOutOfBoundsException(index); } //j是要移动的元素的个数 int j = elementCount - index - 1; //如果需要移动元素,就调用System.arraycopy进行移动 if (j > 0) { //把index+1位置以及后面的元素往前移动 //index+1的位置的元素移动到index位置,依次类推 //一共移动j个 System.arraycopy(elementData, index + 1, elementData, index, j); } //元素的总个数减少 elementCount--; //将elementData[elementCount]这个位置置空,用来添加新元素,位置的元素等着被GC回收 elementData[elementCount] = null; /* to let gc do its work */ }
1.5 Set
1.5.1 Set概述
Set系列的集合,有有序的也有无序的。
HashSet无序的
TreeSet按照元素的大小顺序遍历
LinkedHashSet按照元素的添加顺序遍历
1.5.2 实现类的特点
(1)HashSet:
底层是HashMap实现。添加到HashSet的元素是作为HashMap的key,value是一个Object类型的常量对象PRESENT。
依赖于元素的hashCode()和equals()保证元素的不可重复,存储位置和hashCode()值有关,根据hashCode()来算出它在底层table数组中的[index]
(2)TreeSet
底层是TreeMap实现。添加到TreeSet的元素是作为TreeMap的key,value是一个Object类型的常量对象PRESENT。
依赖于元素的大小,要么是java.lang.Comparable接口compareTo(Object obj)
要么是java.util.Comparator接口的compare(Object o1, Object o2)来比较元素的大小。
认为大小相等的两个元素就是重复元素。
public static void main(String[] args) { TreeSet set =new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2){ student s1=(student)o1; student s2=(student)o2; return s1.getId()-s2.getId(); } }); set.add(new student(2,"李四")); set.add(new student(2,"李四" )); set.add(new student(3,"王五")); System.out.println(set); } } class student{ private int id; private String name; public student() { } public student(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return id + name; } }
(3)LinkedHashSet
底层是LinkedHashMap。添加到LinkedHashSet的元素是作为LinkedHashMap的key,value是一个Object类型的常量对象PRESENT。
LinkedHashSet是HashSet的子类,比父类多维护了元素的添加顺序。
当且仅当,你既想要元素不可重复,又要保证元素的添加顺序时,再使用它。
1.6.1 Map概述
用来存储键值对,映射关系的集合。所有的Map的key都不能重复。
键值对、映射关系的类型:Entry类型
Entry接口是Map接口的内部接口。所有的Map的键值对的类型都实现了这个接口。
HashMap中的映射关系,是有一个内部类来实现Entry的接口
JDK1.7是一个叫做Entry的内部类实现Entry接口。
JDK1.8是一个叫做Node的内部类实现Entry接口。
TreeMap中的映射关系,是有一个内部类Entry来实现Entry的接口
1.6.2 API
(2)putAll(Map m):添加多对映射关系
(3)clear():清空map
(4)remove(Object key):根据key删除一对
(5)int size():获取有效元素的对数
(6)containsKey(Object key):是否包含某个key
(7)containsValue(Object value):是否包含某个value
(8)Object get(Object key):根据key获取value
(9)遍历相关的几个方法
Collection values():获取所有的value进行遍历
public void test(){ Map map = new HashMap(); map.put("杨洪强", "翠花"); map.put("崔志恒", "如花"); map.put("甄玉禄", "凤姐"); map.put("苏海波", "翠花"); Collection values = map.values(); for (Object value : values) { System.out.println(value); }
Set keySet():获取所有key进行遍历
public void test(){ Map map = new HashMap(); map.put("杨洪强", "翠花"); map.put("崔志恒", "如花"); map.put("甄玉禄", "凤姐"); Set keys = map.keySet(); for (Object key : keys) { System.out.println(key + "->" + map.get(key));//通过key得到value }
Set entrySet():获取所有映射关系进行遍历
public void test(){ Map map = new HashMap(); map.put("杨洪强", "翠花"); map.put("崔志恒", "如花"); map.put("甄玉禄", "凤姐"); map.put("苏海波", "翠花"); Set entrySet = map.entrySet(); for (Object entry : entrySet) { System.out.println(entry); }
1.6.3 Map的实现类们的区别
(1)HashMap:
依据key的hashCode()和equals()来保证key是否重复。
key如果重复,新的value会替换旧的value。
hashCode()决定了映射关系在table数组中的存储的位置,index = hash(key.hashCode()) & table.length-1
HashMap的底层实现:JDK1.7是数组+链表;JDK1.8是数组+链表/红黑树
(2)TreeMap
依据key的大小来保证key是否重复。key如果重复,新的value会替换旧的value。
key的大小依赖于,java.lang.Comparable或java.util.Comparator。
(3)LinkedHashMap
依据key的hashCode()和equals()来保证key是否重复。key如果重复,新的value会替换旧的value。
LinkedHashMap是HashMap的子类,比HashMap多了添加顺序
JDK1.6源码:
public HashMap() { //this.loadFactor加载因子,影响扩容的频率 //DEFAULT_LOAD_FACTOR:默认加载因子0.75 this.loadFactor = DEFAULT_LOAD_FACTOR; //threshold阈值 = 容量 * 加载因子 //threshold阈值,当size达到threhold时,考虑扩容 //扩容需要两个条件同时满足:(1)size >= threhold (2)table[index]!=null,即新映射关系要存入的位置非空 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); //table是数组, //DEFAULT_INITIAL_CAPACITY:默认是16 table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
public HashMap() { //DEFAULT_INITIAL_CAPACITY:默认初始容量16 //DEFAULT_LOAD_FACTOR:默认加载因子0.75 this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } public HashMap(int initialCapacity, float loadFactor) { //校验initialCapacity合法性 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + //校验initialCapacity合法性 initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //校验loadFactor合法性 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " +loadFactor); //加载因子,初始化为0.75 this.loadFactor = loadFactor; // threshold 初始为初始容量 threshold = initialCapacity; init(); }
public V put(K key, V value) { //如果table数组是空的,那么先创建数组 if (table == EMPTY_TABLE) { //threshold一开始是初始容量的值 inflateTable(threshold); } //如果key是null,单独处理 if (key == null) return putForNullKey(value); //对key的hashCode进行干扰,算出一个hash值 int hash = hash(key); //计算新的映射关系应该存到table[i]位置, //i = hash & table.length-1,可以保证i在[0,table.length-1]范围内 int i = indexFor(hash, table.length); //检查table[i]下面有没有key与我新的映射关系的key重复,如果重复替换value 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; } private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize);//容量是等于toSize值的最接近的2的n次方 //计算阈值 = 容量 * 加载因子 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); //创建Entry[]数组,长度为capacity table = new Entry[capacity]; initHashSeedAsNeeded(capacity); } //如果key是null,直接存入[0]的位置 private V putForNullKey(V value) { //判断是否有重复的key,如果有重复的,就替换value for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; //把新的映射关系存入[0]的位置,而且key的hash值用0表示 addEntry(0, null, value, 0); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { //判断是否需要库容 //扩容:(1)size达到阈值(2)table[i]正好非空 if ((size >= threshold) && (null != table[bucketIndex])) { //table扩容为原来的2倍,并且扩容后,会重新调整所有映射关系的存储位置 resize(2 * table.length); //新的映射关系的hash和index也会重新计算 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } //存入table中 createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; //原来table[i]下面的映射关系作为新的映射关系next table[bucketIndex] = new Entry<>(hash, key, value, e); size++;//个数增加 }