java集合分为两大类分别为Collection还有Map
先把不要进去的类调试勾选给取消,这样可以方便直接在调试里看到集合的源码
ArrayList
ArrayList实现了List接口、使用数组结构存储数据
1) ArrayList中维护了-个Object类型的数组elementData.
transient Object[] elementData; //transient表示瞬间,短暂的,表示该属性不会被序
列号
2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0, 第1
次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
3)如果使用的是指定大小的构造器,则初始elementData容量为指定大小, 如果需要扩容,
则直接扩容elementData为1.5倍。
接下来用以下代码调试看看
public ArrayList() {
//在构造空大小的ArrayList集合中会默认存下面的数
// 使用这个数组是在添加第一个元素的时候会扩容到默认大小10
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
执行add方法时会走到这里
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureExplicitCapacity(int minCapacity) {
//modCount记录修改的数据
modCount++;
//这里minCapacity是10,意味着扩容10
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
public boolean add(E e) {
//确实是否扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
grow扩展大小
private void grow(int minCapacity) {
// overflow-conscious code
//获取元素长度,如果是第一次则是0
int oldCapacity = elementData.length;
//按照1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//如果是第一次获取则给它默认赋值10
//如果新容量
newCapacity = minCapacity;
//如果新容量已经超过最大容量了,则使用最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//扩容使用copyof 以新容量拷贝出来一个新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
第一次add之后,给了10个默认空间
在给出有构造函数的情况下:
public static void main(String[] args) {
List list=new ArrayList(8);
for (int i=0;i<15;i++){
list.add(i);
}
}
构造函数没赋值的话,大小就是默认的大小
当上面的i执行到8时,按照1.5倍扩容
Vector 底层结构和源码剖析
Vector和ArrayList源码几乎一样,多了线程安全也就是synchronized和扩容倍数
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
ArrayList和Vector的区别。
ArrayList | 可变数组 | 不安全,效率高 | 如果无参默认给10大小,之后每次扩容1.5倍 |
Vector | 可变数组Object[] | 安全,效率不高 | 如果无参默认给10大小,之后每次扩容2倍 |
LinkedList
1)LinkedList底层实现了双向链表和双端队列特点
2)可以添加任何元素(包括null)。
3)线程不安全,没有实现同步。
1、LinkedList底层维护了一个双向链表。
2、LinkedList中维护了两个属性first和last分别指向首节点和尾节点。
3、每个节点(Node对象),里面又维护了prev,next,item三个属性。其中prev指向上一个节点,通过next指向后一个节点。最终实现双向链表.
4、所以LinkedList的元素的添加和删除,不是通过数组完成,相对来说效率较高。
5、模拟一个简单的双向链表
如图示结构
LinkedList简单增删查改+bug源码底层测试
public static void main(String[] args) {
// List list=new LinkedList();
LinkedList list=new LinkedList();
list.add(1);
list.add(2);
list.add(3);
System.out.println("linkedList=" + list);
//删除一个节点
//list.remove();
list.remove();
list.remove(2);
System.out.println("linkedList=" +list);
//修改某个节点对象
list.set(1,50);
System.out.println("linkedList=" + list);
//得到某个节点对象
System.out.println(list.get(2));
//因为LinkedList是实现了List接口,遍历方式
for (Object i:list){
System.out.println(i);
}
}
打断点进到内部代码可以看到有size、first(指向首元素)和last(指向尾元素)三个属性。
LinkedList添加元素有两个方法:add(E e) 和 add(int index,E e)。分别是在末尾添加元素和在指定位置添加元素。这里我们用的是在末尾添加元素。
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 自增
size++;
modCount++;
}
第一次的添加,first和last还有newNode都会指向新节点。
再添加第二次之后则会
删除节点主要方法有
remove(int index)、remove(Object object)和remove()
其中remove()是删除头部节点,执行removeFirst
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
//把原来头部节点的值置为空
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
//头节点指向原来头节点的下一个节点
first = next;
if (next == null)
last = null;
else
//有第二个节点的话,就把头节点的前置节点指向null
next.prev = null;
size--;
modCount++;
return element;
}
如果删除执行的Object。
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;
}
由图可知把中间的链接断开了,被删除元素的前后进行链接。
底层结构 | 增删的效率 | 改查的效率 | |
ArrayList | 可变数组 | 较低 数组扩容 | 较高 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 |
Set接口
1)无序没有索引。
2)不允许重复元素。
3)常用的实现类有HashSet、TreeSet
HashSet
1) HashSet实现了 Set接口
2) HashSet实际上是HashMap
3)可以存放null值,但是只能有一-个null
4) HashSet不保证元素是有序的,取决于hash后,再确定索引的结果. (即,不
保证存放元素的顺序和取出顺序一致)
| 5)不能有重复元素/对像
HashSet的底层是:数组+链表+红黑树。
1、HashSet底层是HashMap
2、添加一个元素时,先得到hash值-会转成->索引值
3、如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树).
4、找到存储数据表,看这个索引位置是否已经存放的有元素
5、如果没有就加入
6、如果有就调用equals进行比较,如果相同就放弃添加,不相同则添加到最后
使用简单的数据进行bug看源码
public static void main(String[] args) {
HashSet hashSet=new HashSet();
hashSet.add("java"); //到此位置,第1次添加
hashSet.add("php"); //到此为止,第2次添加
hashSet.add("java"); //添加失败
System.out.println(hashSet);
}
public boolean add(E e) {
//e=java
return map.put(e, PRESENT)==null;
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//定义了辅助变量
Node<K,V>[] tab; Node<K,V> p; int n, i;
//在表的长度为0时,第一次扩容,到 16 个空间.
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据key,得到hash去计算该key应该存放到table表的哪个索引位置.
//并把这个位置的对象,赋值给p
//判断p是否为null
//如果p为空则说明没有这个对象,就创建一个 Node (key="java",value=PRESENT)
//就放在该位置tab[i]= newNode(Node,key,value,null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//在需要局部辅助变量的时候创建.
Node<K,V> e; K k;
//如果当前添加的hash索引一样
//并且满足以下条件任何之一
//1、准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
//2、key不等于null并且key的值等于准备加入的p指向的Node节点的key相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判断p是不是一颗红黑树如果是的话,通过树状结构添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//遍历链表
for (int binCount = 0; ; ++binCount) {
//依次遍历添加的链表数值
//立即判断 该链表是否已经达到 8 个结点
// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
//这里e赋值p表的下一个,同时实现了迭代效果
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//在转成红黑树时,在treeifyBin内部进行扩容
//判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先 table 扩容.
// 只有上面条件不成立时,才进行转成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果要添加的e指向的节点的key和原链表中有相等的,则不添加,直接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;
}
}
//size 就是我们每加入一个节点Node(k,n,h,next),size++
++modCount;
if (++size > threshold)
resize(); //扩容
//添加操作
afterNodeInsertion(evict);
return null;
}
临界值(threshold)是 16*加载因子(loadFactor)是 0.75 = 12
如果 table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32,
新的临界值就是 32*0.75 = 24, 依次类推
测试添加扩容,默认临近值为12。
HashSet hashSet=new HashSet();
for (int i=0;i<15;i++){
//在同一链上添加了15个元素
hashSet.add(new A(i));
}
在链表元素有8个时则进行扩容,在达到table长度为64为时则进行红黑树的树化。
接着再来看看扩容的代码
final Node<K,V>[] resize() {
//记录表
Node<K,V>[] oldTab = table;
//存储表长
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//记录上表的临近值,上表的临界值为threshold;
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;
//新的临近值计算=0.75*表长
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;
}