记录,符合自己思维的描述,原码jdk17。
一、Collection(集合)
继承结构图: 图1:
Collection 接口继承Iterable接口。所以所有实现Colletcion接口的类都在一个iterator()方法。该方法会返回一个Iterable对象(迭代器)。
1、List,有序可重复
(1)ArrayList
ArrayList.iterator()方法会返回一个Itr对象,该对象实现了Iterable接口。该类提供了两个int类型的属性,cursor(cursor代表下一个索引),lasRet=-1(最后一个元素索引,没有返回-1)。在next()方法中,cursor代表索引若小于当前集合的size(这里会进行两次if(索引>=长度)操作),会返回当前索引指向的对象。返回的对象是Object[]的指定下标的元素。该Object[]在ArrayList实例化时被创建出来,不指定大小的话则是一个空的Object[]。
ArrayList.add(Object)方法会调用重载的add(Object, Object[], int size),将Object 放入Object[]中,并将集合size+1,这里会先判断容量是否满足然后调用grow(),再调用重载grow(size+1)。不指定ArrayList大小,首次调用add会将ArrayList容量扩充为10。
ArraryList.remove()调用fastRemove(),将Object[]指定元素删除,size-1。
foreach()使用了Iterable。
ArrayList扩容机制:
不指定ArrayList大小,首次调用add会将ArrayList容量扩充为10。当容量不够时将容量扩充为当前容量的1.5倍,下图中oldCapacity >> 1 。指定ArrayList大小,当容量不够时将容量扩充为当前容量的1.5倍 。
(2)Vector
是线程安全的,方法上有synchronized关键字,适合多线程操作一个集合使用。
默认无参构造方法会调用重载有参方法传入初始容量为10。
需要扩容时,增加当前容量,即当前容量的2倍。
(3)LinkedList
有两个Node属性:
transient Node first;----指向上一个节点
transient Node last;----指向下一个节点
LinkedList.add(o),该方法调用了linkLast(o),这个方法很有意思的,很厉害!看源码
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++;
}
首次添加节点时,first 和last 都会指向新的节点。再次添加时,first指向首节点,last指向尾节点。听着就像是费话,实在不知道该怎么描述。就是觉得写的从很厉害,太聪明了。remove也差不多。脑子才是程序员的第一武器。(前途堪忧啊)
Vector和ArrayList都有一个核心属性数组,可以说它们两个是基于数组实现。而LinkedList是基于一个双向链表。ArrayList 和linkedList都不是线程安全的。而Vector是线程安全的。
2、Set,无序不重复
(1)HashSet
保存数据通过HashMap实现,HashMap通过数组加单向链表的形式实现。
创建实例时,调用了HashMap的无参构造方法,实例化了一个HashMap对象,给负载因子赋值0.75。
调用add方法调用map的put方法,put中调用和putVal(hash(key), key, value ,false, true)。
*hash()得到的是key的hashCode值与该值无符号左移16位进行异或后的int。为的是该值不重复*
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
putVal中第一个if语句会判断当前table(核心Node数组)当前是否为空。为空调用resize()创建一个长度为16的Node[],设置其扩容临界值为当前数组长度16 * 负载因子0.75 = 12;第二个if会判断当前key要存放的位置是否为空,空则存放,不为空则判断当前要存放的key和链表中k是否相同,相同返回已存在的value,不同则保存到链表最后。当链表长度>8时,会调用treeifyBin()方法,判断数组长度是不超过64,超过则转成红黑树,不超过则将元素追加到链表后并对Node[]扩容到当前数组长度的2倍直到不小于64进行红黑树转化,此时扩容阈值也随着扩大2倍。第三个if会判断,当哈希表中的Node数量大于扩容阈值就进行扩容,这里的Node数量指的不仅仅是数组索引处的第一个元素,还包括数组索引处链表上的所有元素的数量。
(2)LinkedHashSet
保存数据基于数组加双向链表实现。
创建LinkedhashSet调用了LinkedHashMap构造器,LHM又调用了HashMap()构造器Node[]数组长度为16,负载因子为0.75。值得注意的是这里table(Node[])存放的元素是LinkedHashMap内部静态类Entry,该类继承了HashMap.Node,多了2个Entry属性:before指向存入的前一个元素,after指向存入的下一个元素。且该集合有head,tail两个Entry属性分别指向头尾节点。
(3)TreeSet
…
继承结构图: 图2:
二、 Map,保存映射关系键值对(k-v)数据的集合
1、HashMap
参考HashSet。
key相同时,再次put,新value会替换旧value。
key不可以重复,value可以重复。
key只可以保存一个null, value可以保存多个null。
存取顺序不同。
不是线程安全。
EntrySet extends AbstractSet<Map.Entry<K,V>>是HashMap的内部类。
该类并没有属性用来保存table-----Node<K,V>[] (或者说是K-V),只是在其内部的方法引用了table。
如:
2、Hashtable
存储方式是数组加单向链表
存取顺序不同。
线程安全的。
Key,Value都不能为null。
实例化时new Hashtable();会调用this(11, 0.75),设置初始数组长度为11,加载因子为0.75,阈值为数组长度 * 加载因子。
put时,会判断k,v是否为null,不为空,根据hashCode获取对应数组下标,判断该索引处的链表是否存在相同的k,若存在新v替换旧v;若不存在相同的k,调用addEntry(),首先会所有元素个数是否大于等于阈值;超过调用rehash()进行扩容:数组长度 * 2 + 1 ,阈值为数组长度 * 加载因子,同时将旧数组所有元素复制到新数组中。回到不存在相同k,会new Entry,其属性next将指向新数组对应的索引处,将new Entry放在对应数组索引处完成添加。put方法加了关键字synchronized,所以是线程安全的。
3、Properties
数组加单向链表。
该类继承了Hashtable。但是存储数据没有使用从Hashtable继承过来的table属性,而是使用自己的map属性—ConcurrentHashMap.Node<K,V>[]。put时初始长度为16。
4、TreeMap
…
HashMapjdk8是数组+链表+树,jdk7是数组+链表
三、 Collections 工具类
sort();排序时,可以自定义比较器Comparator作为参数。
copy();拷贝数组,目标数组的size要大于等于源数组的size;不是数组长度,而是数组中元素的个数。