活动地址:CSDN21天学习挑战赛
一、数据存储容器核心之数据结构
1、数组
数组是一个固定长度的存储相同数据类型的数据结构,数组中的元素被存储在一段连续的内存空间中。
特点:
- 内存地址连续
- 可以通过下标的成员访问,下标访问的性能高
- 增删操作带来更大的性能消耗(保证数据越界的问题,需动态扩容)
数组增删操作 ——> 新创建数组 ——> 给新数组赋值 ——> 性能消耗
2、链表
单向链表和双向链表
双向链表
如上图,定义了五个节点。
prev 代表当前节点的前一节点地址
next代表当前节点的下一节点地址
双向链表的节点是首尾相连的,可以通过当前节点可以方便的找到上一节点和下一节点
特点:
- 灵活的空间要求,存储空间不要求连续
- 不支持下标访问,支持顺序的遍历检索
- 针对增删操作找到对应的节点改变链表的头尾执行即可,无序移动元素存储位置
添加节点
删除节点
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;
}
}
3、树
3.1 二叉树
添加的第一个元素是根节点。比根节点小添加到左边,比根节点大添加到右边,依此类推到子节点。
二叉树具有如下的特点
- 某节点的左子树节点值仅包含小于该节点值
- 某节点的右子树节点值仅包含大于该节点值
- 左右子树每个也必须是二叉查找树
- 顺序排列
查询效率不高
面对这个问题我们可以参考在高中学习生物时学过一个关键字去除顶端优势,通过去除植物顶端优势,侧芽会迅速生长,慢慢变得强壮和平衡, 红黑树其实就是去除二叉查找树顶端优势的解决方案,从而达到树的平衡
3.2红黑树
红黑树,Red-Black Tree 「RBT」是一个自平衡(不是绝对的平衡)的二叉查找树(BST),树上的每个节点都遵循下面的规则:
- 性质1:每个节点要么是黑色,要么是红色。
- 性质2:根节点是黑色。
- 性质3:每个叶子节点(NIL)是黑色。
- 性质4:每个红色结点的两个子结点一定都是黑色。
- 性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
黑平衡二叉树
recolor (重新标记黑色或红色)
rotation (旋转,这是树达到平衡的关键)
节点描述约定
红黑树能自平衡,它靠的是什么?
三种操作:左旋、右旋和变色
• 左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
• 右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
• 变色:结点的颜色由红变黑或由黑变红。
插入处理的场景
插入情景1:红黑树为空树
最简单的一种情景,直接把插入结点作为根结点就行,但注意,根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。
处理:把插入结点作为根结点,并把结点设置为黑色。
插入情景2:插入结点的父结点为黑结点
由于插入的结点是红色的,当插入结点的黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
处理:直接插入。
插入情景3:插入结点的父结点为红结点
再次回想下红黑树的性质2:根结点是黑色。如果插入的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,因为后续的旋转操作肯定需要祖父结点的参与。
情景3又分为很多子情景,下面进入重点部分
插入情景3.1:叔叔结点存在并且为红结点
从红黑树性质4可以,祖父结点肯定为黑结点,因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红。
插入情景3.2:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点
单纯从插入前来看,也即不算情景3.1自底向上处理时的情况,叔叔结点非红即为叶子结点(Nil)。因为如果叔叔结点为黑结点,而父结点为红结点,那么叔叔结点所在的子树的黑色结点就比父结点所在子树的多了,这不满足红黑树的性质5。后续情景同样如此,不再多做说明了。
前文说了,需要旋转操作时,肯定一边子树的结点多了或少了,需要租或借给另一边。插入显然是多的情况,那么把多的结点租给另一边子树就可以了。
插入情景3.2.1:插入结点是其父结点的左子结点
插入情景3.2.2:插入结点是其父结点的右子结点
这种情景显然可以转换为情景3.2.1
插入情景3.3:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
该情景对应情景3.2,只是方向反转,不做过多说明了,直接看图
二、数据存储核心之ArrayList源码探究
ArrayList底层是数组队列,相当是动态数组,可以理解为是一种可变长度的基于数组实现的集合类。
1、ArrayList类图结构
相关的接口抽象类的介绍
类名 | 说明 |
---|---|
AbstractCollection | 实现了Collection中大量的函数,除了特定的几个函数iterator()和size()之外的函数 |
AbstractList | 该接口继承于AbstractCollection,并且实现List接口的抽象类。它实现了List中除size()、get(int location)之外的函数。AbstractList的主要作用:它实现了List接口中的大部分函数和AbstractCollection相比,AbstractList抽象类中,实现了iterator()接口 |
RandomAccess | RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问 |
List | 有序队列接口,提供了一些通过下标访问元素的函数List是有序的队列,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1 |
2、ArrayList源码
/**
* Default initial capacity.
* 默认数组的长度,即初始容量为10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* 空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* 集合中存储数据的数组对象
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*集合中元素的个数
* @serial
*/
private int size;
3、初始操作之无参构造
ArrayList list = new ArrayList();
源码分析
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
elementData=null;无参构造去并不会真实的创建数组,数组会在add方法中去创建,有助于性能的提升,懒加载的方式。
4、初始操作之有参构造
源码分析
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//初始长度大于0 就创建一个指定大小的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//{}数组赋值给 this.elementData
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
5、add方法源码分析
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
初始无参构造器
第一次添加
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//确定容量 动态扩容 size初始值 0 (原因在下一源码)
ensureCapacityInternal(size + 1); // Increments modCount!!
//将要添加的元素 添加到数组中 elementData[0] = 1 --> size = 1
elementData[size++] = e;
return true;
}
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
private void ensureCapacityInternal(int minCapacity) {
// 10
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* elementData {}
* minCapacity 1
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 10 1 return 10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//1
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//增长 操作次数
// overflow-conscious code
//minCapacity 10 - 0
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
进入grow方法
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {//10
// overflow-conscious code
int oldCapacity = elementData.length;//0
//newCapacity = 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
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:
// {} {,,,,,,,,,} 返回一个新的数组 长度为10
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
//调用MAX_ARRAY_SIZE不执行
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
第二次添加
elementData = {1,,,,,,,,,};
size = 1;
public boolean add(E e) {
// 2
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //elementData[1] = 2 size = 2
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 2
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//增长 操作次数
// overflow-conscious code
//2-10
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
第十一次添加
elementData = {1,2,3,4,5,6,7,8,9,10};
size = 10;
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //elementData[1] = 2 size = 2
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// ensureExplicitCapacity(11)
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//11-10
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {//1
// oldCapacity 10
int oldCapacity = elementData.length;//0
//newCapacity = 15 newCapacity 是 oldCapacity 的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
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:
// {1,2,3,4,5,6,7,8,9,10} 复制到新的数组{1,2,3,4,5,6,7,8,9,10,,,,,}
elementData = Arrays.copyOf(elementData, newCapacity);
}
6、get方法源码分析
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
//检查下标是否合法
rangeCheck(index);
//通过下标获取数组对应的元素
return elementData(index);
}
7、set方法源码分析
public E set(int index, E element) {
rangeCheck(index);//检查下标
//获取下标原来的值
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
8、remove方法源码分析
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);//检查下标
modCount++;//记录修改次数
E oldValue = elementData(index);//获取原来的值
//获取要移动元素的个数 {1,2,3,4,5,6,7,8,9} // 3 size=9 index=3
//{1,2,3,5,6,7,8,9,null}
int numMoved = size - index - 1;// 5
if (numMoved > 0)
//源数组 开始下标 目标数组 开始下标 长度
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
//删除的节点对应的信息
return oldValue;
}
9、ArrayList类总结
ArrayList类 |
---|
动态数组结构 |
查询快,添加/删除慢 |
没有提供对第一个元素和最后一个的操作 |
适合大量查询操作 |
初始容量为10 |
每次扩容后的大小为之前的1.5倍 |
三、集合类图结构
1.集合类图结构
2. Collection接口介绍
3. Map接口介绍
4.两大派系相互之间的关系
HashSet本质上是一个HashMap
TreeSet本质上是一个NavigableMap
四、为什么不推荐使用Vector
Vector的底层与ArrayList类似.都是以动态数组的方式进行对象的存储
Vector与ArrayList的区别在于Vector是线程同步操作安全的
Vector的并发安全保证
看Vector的源码(如下)发现很多对外的方法都用Synchronized关键字进行修饰
所以通过vector进行操作性能并不高,因此慢慢被放弃
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
如上可知,Vector是线程安全的,但是性能较差,不推荐使用。
在工作中,我们有可能将集合(ArrayList与LinkedList)转换为线程安全。
就要用到Collection工具类
五、将集合转换为线程安全的对象
Collections工具类可以将List接口中线程不安全的工具类转换为线程安全的对象
Collections工具类跟Vector相比,Collections的代码灵活度更好,性能更好
Collections工具类同步代码本质如下
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
举个栗子
private static List list=new ArrayList();
List syncList = Collections.synchronizedList(list);
Collections源码
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
其它对象转换方法如下
Java数据存储容器核心之集合底层探究下