这里写目录标题
ArrayList源码
创建操作
先确定是无参构造还是有参构造
首先,底层就是一个Object数组
transient 表示瞬间的,短暂的,表示该属性不会被序列化
无参构造:
默认大小为:
private static final Object[] DEFAULTCAPACITY_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);
}
会创建一个你所填写的大小的一个数组。
添加,扩容
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 当前数组大小+1
elementData[size++] = e;
return true;
}
这里表示执行add添加操作,会先执行这个方法
ensureCapacityInternal(size + 1);
点进去,看
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
先判断是否是空数组,若为空,则赋值一个数,大小为
Math.max(DEFAULT_CAPACITY, minCapacity);
后面两个数中的最大值,而DEFAULT_CAPACITY
大小为10,
所以空数组第一次add会创建一个大小为10的数组
然后执行这个语句:
ensureExplicitCapacity(minCapacity);
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
第一句:modCount++;
用来记录这几集合被修改的次数,防止多线程操作出现异常!!
然后进行判断,因为我们之前设置了minCapacity赋值为10了,而我们真实的数组大小为0,所以进行扩容操作!!!
grow(minCapacity);
这个方法执行扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//把数组大小赋值给oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
//也就是原先数组大小再加上它除以2,也就是我们说的扩容1.5倍
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);
}
int oldCapacity = elementData.length;
把数组大小赋值给oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
也就是原先数组大小再加上它除以2,也就是我们说的扩容1.5倍
然后进行判断,扩容后减去之前赋值的minCapacity<0
,为true表示,依旧比它小。所以直接给newCapacity
赋值为minCapacity
继续判断,这时候的newCapacity
大于我们数组的最大值2147483639,执行
hugeCapacity(minCapacity);
这个方法先不看。
elementData = Arrays.copyOf(elementData, newCapacity);
最后执行这个语句
把elementData原先的数组大小扩充为 newCapacity大小的数组,把原先数组的值迁过去,扩大后没有值的地方默认为空(null)。
最后,返回到了原先的add方法
执行elementData[size++] = e;
这个语句,这时候这个elementData的数组大小已经扩容完毕了,然后对这个数组下标为size++的地方放入数据e,这里面的size指的是已经放入的元素数量
注意: 容量和size不是一回事,size是放入的元素数量,容量是数组设定的大小
Vector源码
和Arraylist差不多,只不过加了synchronized
线程安全了
它的扩容是按照2倍扩容的,而Arraylist按1.5倍扩容
它的扩容体现在这段代码上
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
而capacityIncrement
为0,所以执行后面的语句,也就是oldCapacity
,所以连个相加就是2倍!!!
LinkedList源码
对每一个节点设置为一个类,item放真正的值,next指向下一个节点,pre指向前一个结点
还存在两个属性,专门用来记录第一个节点和尾节点。新创建完这个LinkedList,这两个属性默认为null;
主要介绍添加
里面一条语句linkLast(e);
意思是直接把我们要添加的数据。挂在当前尾节点后面(尾插法)
新建一个Node 完了赋值给这个newNode,让last指向这个newNode。这里判断l是否为空 有两种情况,如果本身链表为空,则第一个节点创建后,first和last两个属性都指向这第一个节点,所以这里判断为空的话,说明这是第一个节点,不为空的话,让链表本身最后一个节点的next指向新建的节点。
删除节点
有些同学有疑问,链表是没有下标这一说的,那我们在指定index插入,或者而删除是如何实现的,其实本质就是这段代码:
Node<E> node(int index) {
// assert isElementIndex(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;
}
}
根本就是判断这个下标是在整个链表的前半段还是后半段,前半段就前向遍历,后半段就后向遍历,本质就是for循环遍历,找到对应index的Node节点
remove本质就是这段代码:
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
先记录要删除的节点的前一个结点和后一个节点。
之后的两个判断很简单,就是判断要删除的节点是否是头节点或是尾节点。
然后,让next,pre互相指一下就行了。
其它方法不再赘述,很简单。
HashSet(底层是hashmap,其实看hashmap源码)
底层是HashMap,而HashMap的底层是 数组+链表+红黑树
例如add方法我们点进入底层看见,是map结构,且key为我们add的值,而value为 PRESENT
private static final Object PRESENT = new Object();
PRESENT
它只是起到一个占位的作用,且被static final修饰,不会再改变,每次add的时候放入的都是同一个值,不会每次都新建,所以也就不占啥内存,由此可得。hashset是hashmap键的集合(hashmap的value是可以重复的,key不允许重复)
底层一个一个数据是如何存储的?
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
放在一个Node类中,hash值,key和value,还有指向下一个节点的next
添加操作,第一次插入数据(扩容)
点进去add操作
到map的put方法
在计算hash(key)得到key对应的hash值
赋值,然后进入putVal
方法(哦豁,这下开始加进去了)
这里才是重点
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;
}
Node<K,V>[] tab; Node<K,V> p; int n, i;
这一行是定义一些变量,待使用!
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
执行这一行代码,其中 table
是定义好的一个默认的空的Node数组
主要是判断数组为空,然后进行resize()
,给数组设置个初始大小,点进去
真正的第一步,给数组设置大小(数组扩容也在这里面)
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
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
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
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"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
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;
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;
}
threshold
threshold表示当HashMap的size(加入的节点数量,不是占用的数组大小)大于threshold时会执行resize操作。第一次创建后的第一次put,它的值为0
我们继续看代码:
第一次创建后的第一次put oldCap oldThrnt newCap, newThr 他们的值都是0
然后进入到这个判断
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
给newCap设置为16(DEFAULT_INITIAL_CAPACITY为16)
给newThr设置为12(0.75*16) 做缓冲,不能直接设置为16,表示只要添加了12个节点,再进行添加,就进行扩容
再然后,执行:
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
给threshold设置为12,用来之后判断只要节点数量超过12就扩容
然后新建一个Node<K,V>[] newTab
大小为16 赋值给table
之后的判断先不管,直接看到最后返回return newTab;
数组大小为16
到此,第一步的resize()
结束,目的就是给数组设置大小
将数据放进去
之后回到putVal
方法,执行这个语句
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
让传入的hash值与数组长度减一进行按位与运算,得到要放入到数组的哪个位置上去。
把这个位置上的数据赋值给Node p,有两种情况,一是为空,也就是这个位置上面本来没有数据,直接newNode放进去。
还有就是不为空,本来位置上有数据、执行下一个判断,先掠过这里
最后执行到这段代码:
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
++modCount;
表示对集合的操作次数
size
hashmap中的size这个属性表示返回map中key-value映射的数量。也就是size表示HashMap中存放KV的数量(为链表和树中的KV的总和)。
在判断,是否该size存放的kv数量是否超过threshold,超过就扩容,不超过,直接return null 表示成功
以上第一次插入一个数据,向一个新建的集合里面添加一条数据,就是以上操作。
之后,简述第二次添加数据操作源码解析
跟第一次一样,进入到putVal
方法
不再执行这个判断:
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
因为这里的table
已经在第一次的resize()
里面的这一句赋值了一个大小为16的数组
所以进行下一个判断
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
根据这个判断,往数组的哪一个里面放数据,,看存不存在,不存在直接放进去,存在,则进行其他判断。
以上就上添加第二个数据,不重复的情况下!!!
添加一个重复的数据,也就是让key相同
也是走到putVal
方法**
走到这个判断,发生变化
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
这时候key已经存在过了,所以这里计算的hash值相同,所以数组的这个位置肯定不为空,走到一个全新的领域
这里的数组位置不为空,走到下面的代码
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;
}
}
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
p.hash == hash
这里的p为数组这个位置不为空的第一个元素 ,p.hash就是第一个元素的hash值 ,后面的hash表示你要添加的元素的hash值
(k = p.key) == key
p.key表示第一个元素的key == 要添加元素的key 要注意这里的 == 等号比较的是两个key的引用是否相同
(key != null && key.equals(k)))
表示key不为空,并且他们的equals是否相同,这里的equals比较的是本身也就是内容是否相同
这里不懂得,看看== 与 equals的区别
之后判断为true后,将这个p赋值给e,在走到这个判断
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
e.value
也就是我们set集合默认的value
private static final Object PRESENT = new Object();
走到最后返回一个值,不为空
回到前面
返回不为空,也就是添加失败
如果正好添加的时候,数组位置不为空,但是key不重复的情况
走到putVal
方法的这个判断
判断p这个节点是否为红黑树的结构,是的话走到红黑树底层添加的方法(这个也很复杂,先不看)
又或者不是红黑树,走到这个判断
p不是红黑树,那就是链表了,先判断,头节点是否有next,没有就直接把我们要添加的数据,尾插法跟到后面,如果next不为空,则依次比较后面的节点与我们要插入的数据是否相同,还是那个判断,比较hash,比较key引用是否相同,比较内容是否相同,如果都相同,说明重复就退出,遍历到解为都不重复,就将数据尾插法插到最后。
中间还有这段代码,
如果链表长度达到8,就准备转为红黑树,执行这个方法
先判断数组长度是否达到64,且链表长度到达8,再进行转换!!!
注意:
数组扩容有个阈值,也就是*0.75后得到的值,扩容的条件是添加进去的节点数量(size)大于这个阈值,就进行扩容,而不是占用数组的长度达到阈值!!!!!!
到此为止,添加操作大多讲完了!!!