背景
现在非常流行的一款数据库连接池工具HikariCP经常会出现在我们的日常工作中,这块工具的最大特点就是快,它在提高提升速度方面做了非常多的努力,其中的一项就是通过自己实现的FastList来代替了Java中自带的ArrayList来对部分数据进行处理,那我们这个地方就会有一个问题,FastList到底快在哪里呢?带着这个疑问,我们来看一下FastList的源码,看看它到底做了那些优化。
版本信息
我当前查看的POM文件的版本是5.0.2-SNAPSHOT
源码解读
在FastList类的最上面,有一行最明显的注释:Fast list without range checking. 这一句就非常直白的说明了它和ArrayList最大的一个区别:没有范围检查;下面我们来详细的看一下这个它的每个方法的一些
初始化
FastList中的数据默认是存在一个叫elementData的数组中,默认情况首次初始化32个数组空间,也可以通过传参来自定义指定的个数,对应的方法是:
public FastList(Class<?> clazz)
public FastList(Class<?> clazz, int capacity)
添加
FastList采用的是尾插法,即默认在数组的尾部插入新的元素,
public boolean add(T element)
{
//判断数组的长度是否小于当前已存入元素的个数,如果小于的,则表示数组还有空间来存放新的元素,直接在数组的已经存入的元素的后面增加新的元素
if (size < elementData.length) {
elementData[size++] = element;
} else { //数据空间已满
//保存过去数组的长度
final var oldCapacity = elementData.length;
//新的数组长度为旧的数组长度左移1位,等同于 oldCapacity * 2,移位运算对于计算机处理起来效率更高
final var newCapacity = oldCapacity << 1;
//重新申请新长度的空数组
final var newElementData = (T[]) Array.newInstance(clazz, newCapacity);
//将过去的元素拷贝到新的数组当中
System.arraycopy(elementData, 0, newElementData, 0, oldCapacity);
//将该次添加的新元素存入新数组中现有元素的尾部
newElementData[size++] = element;
//将对象的数组替换成新申请的数组
elementData = newElementData;
}
return true;
}
获取指定下标元素
获取指定下标的元素在获取前没有范围检查,直接返回指定下标的数组元素,当然这样如果下标的范围超出了数组的范围的话,会抛出ArrayIndexOutOfBounds异常,在频繁的获取元素的过程中,默认是正确的也是一种非常不错的处理方案,当然还有一方面原因是这个类是开发者在当前的项目中在使用的,他在使用前会通过各种方式来保证查询的下标是正确的,这应该是开发者这样写的原因吧
public T get(int index)
{
return elementData[index];
}
移除最后一个元素
该方法没有判断集合中元素的个数,直接将最后一个元素赋值为null,如果本来集合就是空的话,那么size是等于0的,–size将出现ArrayIndexOutOfBounds异常,正常的情况是将最后一个元素取出,然后将集合中的最后一个元素赋值为空,然后将最后一个元素的返回
public T removeLast()
{
T element = elementData[--size];
elementData[size] = null;
return element;
}
移除指定的元素
按照从后往前的顺序逐个比较每一个元素与指定的元素是否相等,相等则将该元素下标后的所有元素向前复制一遍,将原来的最后一个元素赋值为null,该处有一个细节,两个对象比较不是用的equals方法,而是用的 == ,对于两个对象相比的话相当于是在比较他们的对象引用地址。
public boolean remove(Object element)
{
for (var index = size - 1; index >= 0; index--) {
if (element == elementData[index]) {
final var numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return true;
}
}
return false;
}
清空
将集合中的每个元素设置为null
public void clear()
{
for (var i = 0; i < size; i++) {
elementData[i] = null;
}
size = 0;
}
插入元素到指定下标
public T set(int index, T element)
{
T old = elementData[index];
elementData[index] = element;
return old;
}
删除指定下标元素
删除指定下标的元素,为了更加快的处理速度,没有对下标和数组的范围进行比较,可能会出现ArrayIndexOutOfBounds的异常,如果指定下标的的元素存在,则将该下标后面的元素向前拷贝一位,然后将最后一个元素赋值为null,并且返回被删除的元素
public T remove(int index)
{
if (size == 0) {
return null;
}
final T old = elementData[index];
final var numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
}
elementData[--size] = null;
return old;
}
其它方法
其它的还有很多方法在FastList中是没有进行支持的,因为在HikariCP中并没有用到这些方法
小拓展
在这个代码里面有很多地方用到了 –size 和 size++ 这样两种不同的写法,我们在使用的时候一定注意这两种不同写法所代表的含义,这样才能保证代码与你期望的效果一致,下面来简单说一下 –size 和 size– 这两种写法的区别
-
–size : 先对size进行 -1 的操作,然后再使用size对应的值
-
size-- : 先使用size,在对size进行 -1 的操作
下面来在实际使用场景中看一下
int k = 1;
//该打印语句打印出的是1
System.out.println(k--);
//该打印语句打印出的是0;
System.out.println(k);
int m = 1;
//该打印语句打印出的是0
System.out.println(--m);
//该打印语句打印出的是0;
System.out.println(m);
结语
HikariCP为了达到更快的速度,做了非常多的优化和非常细节的处理,FastList这个类只是其中的一个优化点,希望上面的梳理对大家深入的了解HikariCP这个数据库连接池可以提供一些帮助