/*
无参构造,返回一个空数组
参数:elementData - ArrayList集合维护的数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/*
* 有参构造
* 构造一个具有指定初始容量的空数组。
* 参数:initialCapacity - 列表的初始容量
* 抛出:IllegalArgumentException – 如果指定的初始容量为负
* */
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 boolean add(E e) {
modCount++;//记录集合被修改的次数,防止多个线程同时修改
/*
第一个参数:添加的元素
第二个参数:数组名
第三个参数:集合长度/现在元素的插入位置索引
*/
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
//如果集合长度与数组长度相同,则需要扩容
if (s == elementData.length)
elementData = grow();
elementData[s] = e;//赋值
size = s + 1;//集合长度加一,亦表示下个元素插入位置
}
private Object[] grow() {
return grow(size + 1);//传入参数表示添加完当前元素后的最小容量
}
private Object[] grow(int minCapacity) {
/*
copyOf方法会创建一个长度为newCapacity(minCapacity)的新数组,并将elementData数组中的元素全部复制到新数组当中去
*/
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//记录老容量
//新容量等于老容量加上老容量的一半,即老容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//作新容量是否小于等于最小容量判断
if (newCapacity - minCapacity <= 0) {
//如果数组是初始的空数组(即第一次添加元素),返回默认容量大小10和最小容量值两者中的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
/*如果最小容量值小于0,抛出内存溢出异常,最小容量超过整数最大值Integer.MAX_VALUE时就会内存溢出*/
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//返回最小容量值
return minCapacity;
}
/*新容量值大于最小容量值时,判断新容量值是否小于等于数组容量最大值,满足则返回新容量值,否则使用hugeCapacity方法确保不会扩容得太大*/
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
private static int hugeCapacity(int minCapacity) {
/*如果最小容量值小于0,抛出内存溢出异常,最小容量超过整数最大值Integer.MAX_VALUE时就会内存溢出*/
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判断最小容量值是否超过了数组最大容量值,超过则返回整数最大值,否则返回数组最大值
return (minCapacity > MAX_ARRAY_SIZE)
? Integer.MAX_VALUE
: MAX_ARRAY_SIZE;
}
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//记录尾指针所指向的元素
final Node<E> l = last;
//创建新节点,将l作为新节点的前一个节点,指向后一个节点的指针赋值为null
final Node<E> newNode = new Node<>(l, e, null);
//将新节点赋值给尾指针
last = newNode;
//如果尾指针初始为null,说明当前插入的节点是第一个节点,让first指向它
if (l == null)
first = newNode;
else
//当前插入的不是第一个节点,将新节点赋值给插入前的尾结点的next指针
l.next = newNode;
size++;
modCount++;
}
//Node类是LinkedList的静态内部类
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<String> strings = new ArrayList<>();
strings.add("aaa");
strings.add("bbb");
strings.add("ccc");
//获取迭代器对象
Iterator<String> iterator = strings.iterator();
//调用Itr的无参构造,返回一个迭代器对象
public Iterator<E> iterator() {
return new Itr();
}
//ArrayList的内部类
private class Itr implements Iterator<E> {
int cursor; // 光标,迭代器的指针,初始值为0
int lastRet = -1; // 上一个被遍历元素的索引
int expectedModCount = modCount;//创建迭代器时,集合被添加/删除的总次数
// 无参构造
Itr() {}
//光标不等于集合元素总数时,光标所指位置有值
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//检查此时集合有没有被修改(添加/删除)
checkForComodification();
int i = cursor;
//当前光标的值超过集合总元素个数,抛出不存在此元素异常
if (i >= size)
throw new NoSuchElementException();
//获取集合存储数据的数组
Object[] elementData = ArrayList.this.elementData;
//当前光标的值超过数组长度,抛出并发修改异常
if (i >= elementData.length)
throw new ConcurrentModificationException();
//移动光标位置,指向下一个元素
cursor = i + 1;
//返回光标移动前位置的元素,并将其索引赋值给lastRet
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
//当前集合的被操作次数与创建迭代器时不一致,抛出并发修改异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
创建HashSet集合时,创建一个默认长度16,默认加载因子为0.75的数组,数组名为table
添加元素时,根据元素的哈希值跟数组长度计算出应存入位置的索引值
若该位置为null,直接将元素插入进去,若不为null,比较当前元素与该位置元素是否相等,相等则不插入
JDK8以前,不相等的情况 使用头插法,将新元素插入到数组中,旧元素挂在新元素下面,形成链表
JDK8以后,不相等时使用尾插法,即直接将新元素挂在旧元素下面,形成链表
且在JDK8以后,当链表长度超过8并且数组长度大于等于64时,将链表转换为红黑树
若集合中存储的时自定义对象,必须要重写hashCode()和equals()方法
当数组中的元素个数达到数组长度的75%(加载因子)时,数组将扩容为原来的2倍
//HashMap的三个重要成员变量
//数组默认初始长度
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//数组最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//数组默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//无参构造
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
考虑三种情况
- 数组位置为null
- 数组位置不为null,键不重复,挂在数组下面形成链表或红黑树
- 数组位置不为null,键重复,覆盖原来的值
//put方法传入当前添加元素的key和value
/*
调用putval()方法
第一个参数:调用hash()方法,hash方法使用hashCode()方法得到了key对应的hash值,并进行了一定的处理
简单理解:第一个参数就是根据key获取的hash值
第二个参数:key
第三个参数:value
第四个参数:key相同时是否覆盖,true表示不覆盖,false表示覆盖
第五个参数:不相关,不做解释
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
//h = key.hashCode()计算key的hash值
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//核心方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义一个局部变量,用于记录hash表中table的地址值
Node<K,V>[] tab;
//第三方变量,用于记录集合中元素的地址值
Node<K,V> p;
//n用于记录数组长度,i用于记录索引
int n, i;
//第一次添加元素时,table为null
if ((tab = table) == null || (n = tab.length) == 0)
//resize方法创建一个默认长度为16,加载因子为0.75的数组赋值给tab,并将数组长度赋值给n
n = (tab = resize()).length;
/*使用当前插入元素的key和数组长度减一做与运算得到应该插入的位置索引,并获取该位置的元素赋值给p,
判断p是否为空,即判断要插入的位置是否为空*/
if ((p = tab[i = (n - 1) & hash]) == null)
//要插入的位置为空,创建一个新节点并插入
tab[i] = newNode(hash, key, value, null);
else {
//要插入的位置不为空
Node<K,V> e;
K k;
/*判断该位置元素的hash值与新元素的hash值是否相等,若相等再次判断该位置的元素与新元素的key是否相等,若也相等,将该位置元素交由e保存,留待进行值覆盖操作*/
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
/*判断获取到的数组元素是否是红黑树中的节点,若是,则调用putTreeVal方法将新元素按照红黑树规则添加到树当中去*/
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//获取到的数组元素是链表节点,使用binCount记录数组中的元素个数
for (int binCount = 0; ; ++binCount) {
/*获取p所指向节点的下一个节点,赋值给e(第一次判断时,p代表的是从数组中获取到的元素,即判断该元素下面是否还有节点),判断是否为空*/
if ((e = p.next) == null) {
//为空,则创建一个新的节点挂在下面
p.next = newNode(hash, key, value, null);
//判断链表长度是否超过8,超过则调用treeifyBin方法
//treeifyBin方法底层还会继续判断数组长度是否大于等于64
//如果两个条件同时满足,就会将数组转为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
//跳出循环
break;
}
//不为空,判断e与要插入的节点hash值是否相同,key是否相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
//相同,跳出循环,留待进行值覆盖操作
break;
//不相同,将e赋值给p,继续往下遍历
p = e;
}
}
//判断e是否为空,若为空,表示本次插入没有元素被覆盖,若不为空说明需要做覆盖操作
if (e != null) {
//记录e的存储的值
V oldValue = e.value;
//判断是否做覆盖操作
if (!onlyIfAbsent || oldValue == null)
//将新元素的值赋值给e
e.value = value;
//不相关,不做解释
afterNodeAccess(e);
//返回被覆盖的值
return oldValue;
}
}
++modCount;
//判断数组中的元素是否达到了扩容阈值,未达到阈值,不做操作
if (++size > threshold)
//达到扩容阈值(16*0.75=12),resize方法会将数组扩容为原来的两倍,并将旧数组中的元素全部复制到新数组中去
resize();
//不相关,不做解释
afterNodeInsertion(evict);
//返回null表示没有元素被覆盖
return null;
}
//比较器
private final Comparator<? super K> comparator;
//根节点
private transient Entry<K,V> root;
//节点个数
private transient int size = 0;
//空参构造
public TreeMap() {
comparator = null;
}
//有参构造,传入构造器赋值
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//使用静态常量表示节点颜色,增加代码可读性
private static final boolean RED = false;
private static final boolean BLACK = true;
//静态内部类,Entry,集合中的元素都以Entry的类型存储
static final class Entry<K,V> implements Map.Entry<K,V> {
//键
K key;
//值
V value;
//左节点
Entry<K,V> left;
//右节点
Entry<K,V> right;
//父节点
Entry<K,V> parent;
//节点颜色
boolean color = BLACK;
...
}
public V put(K key, V value) {
//记录根节点
Entry<K,V> t = root;
//判断根节点是否为空
if (t == null) {
compare(key, key);
//为空,将插入的节点作为根节点
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//用于记录两个元素键比较的差值
int cmp;
//用于记录新插入节点的父节点
Entry<K,V> parent;
//获取集合的比较器
Comparator<? super K> cpr = comparator;
if (cpr != null) {
//集合指定了比较器
do {
//第一次循环,将根节点作为父节点
parent = t;
//比较新插入元素的key和根元素的key,将差值赋值给cmp
cmp = cpr.compare(key, t.key);
if (cmp < 0)
//cmp小于0,说明新元素要放在t节点的左边,故将t节点的左节点赋值给t
t = t.left;
else if (cmp > 0)
//cmp大于0,说明新元素要放在t节点的右边,故将t节点的右节点赋值给t
t = t.right;
else
//cmp等于0,说明新元素与t节点键相同,做值覆盖操作
return t.setValue(value);
} while (t != null);
}
else {
//集合未被指定比较器,使用默认比较规则
//如果传入的key为null,抛出空指针异常
if (key == null)
throw new NullPointerException();
//压制未检查警告
@SuppressWarnings("unchecked")
//将新元素的key强转为Comparable类型,要求key必须实现了Comparable接口,否则报错
Comparable<? super K> k = (Comparable<? super K>) key;
do {
//下列代码与上面相同,不做解释
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
//遍历完红黑树,没有覆盖元素,做插入操作,创建新节点
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
//从新插入节点开始调整红黑树
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
private void fixAfterInsertion(Entry<K,V> x) {
//将当前节点颜色设为红色,红黑树节点默认为红色
x.color = RED;
//按照红黑树规则调整节点
//判断当前节点是否为根节点,父节点是否为红色,若父节点为黑色则不需要调整
while (x != null && x != root && x.parent.color == RED) {
//判断当前节点的父节点是否为爷爷节点的左节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//当前节点的父节点是爷爷节点的左节点
//获取当前节点的叔叔节点,即当前节点爷爷节点的右节点
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//判断叔叔节点的是否为红色
if (colorOf(y) == RED) {
//叔叔节点为红色
//将父节点设为黑色
setColor(parentOf(x), BLACK);
//将叔叔节点设为黑色
setColor(y, BLACK);
//将爷爷节点设为红色
setColor(parentOf(parentOf(x)), RED);
//将爷爷节点赋值给当前节点
x = parentOf(parentOf(x));
} else {
//叔叔节点为黑色
//判断当前节点是否为父节点的右节点
if (x == rightOf(parentOf(x))) {
//当前节点是父节点的右节点
//将父节点作为当前节点
x = parentOf(x);
//以父节点为当前节点进行左旋
rotateLeft(x);
}
//将父节点设为黑色
setColor(parentOf(x), BLACK);
//将爷爷节点设为红色
setColor(parentOf(parentOf(x)), RED);
//以爷爷节点为当前节点右旋
rotateRight(parentOf(parentOf(x)));
}
} else {
//当前节点的父节点是爷爷节点的右节点,以下代码与上面相同,不再解释
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
//将根节点颜色设为黑色
root.color = BLACK;
}