HikariCP为什么自己造了一个FastList?
The following article is from 阿飞的博客 Author 阿飞
HikiriCP作为当今世界上最快的数据库连接池中间件,其对代码追求的极致一直被开源爱好者津津乐道。HikariCP之所以这么快的其中一个原因就是:开发FastList取代ArrayList。那么FastList是个什么东西?相比ArrayList有哪些出色的地方?接下来让我们通过对源码的分析来一探究竟。
首先,让我们打开HikariCP的源代码,如果你不想下载它的源码的话,你也可以只打开 https://github.com/brettwooldridge/HikariCP/blob/master/src/main/java/com/zaxxer/hikari/util/FastList.java 即可,就能看到FastList的源码。
我们首先看一下这个类的注释,如下所示。通过这段注释我们可以得出几个结论,1. FastList并不是完全凭空造出来的而是基于ArrayList造出来的,正所谓站在巨人的肩膀上。2. 由这个类的注释我们可知,它没有range checking,即范围检查。如果看过ArrayList的源码的话,我们就知道,它的get()、set()、remove()等很多方法中都执行了范围检查(rangeCheck(index)):
/**
* Fast list without range checking.
*
* @author Brett Wooldridge
*/
public final class FastList<T> extends ArrayList<T>
{
... ...
}
我们现在大概知道了FastList相比ArrayList的改进,下面列举出两个类具体对比如下表格所示:
操作 | FastList | ArrayList |
---|---|---|
add | 重写 | 支持 |
get | 重写 | 支持 |
removeLast | 支持 | 不支持 |
remove(Object) | 重写 | 支持 |
clear | 重写 | 支持 |
size | 一样 | 一样 |
isEmpty | 一样 | 一样 |
set | 重写 | 支持 |
remove(int) | 重写 | 支持 |
iterator | 重写 | 支持 |
toArray、containsAll、contains、addAll、removeAll... ... | throw new UnsupportedOperationException() | 支持 |
精简部分
如上表格可知,FastList相比ArrayList,它屏蔽了很多它不需要的操作方法,例如:toArray、containsAll、contains、addAll、removeAll... ... 。只要调用这些方法,就会抛出UnsupportedOperationException异常。这是因为,FastList是HikariCP用来管理Statement(例如PrepareStatement)的,它并不需要这些方法。
add
add方法是一个非常有用、使用频率很高的方法。而且刚才我们说了,HikariCP是用FastList用来管理Statement的,这就意味着,在我们使用拿出数据库连接执行SQL时,都需要创建Statement,这就需要调用FastList的add()方法,所以这个方法是一个非常高频的方法。FastList的add()源码如下。它和ArrayList最大的区别是,FastList假设大部分情况下不需要扩容,直接给数据最后一个元素赋值即可。而ArrayList恰恰相反,需要先确保容量足够:
public boolean add(T element)
{
try {
elementData[size++] = element;
}
catch (ArrayIndexOutOfBoundsException e) {
// overflow-conscious code
final int oldCapacity = elementData.length;
final int newCapacity = oldCapacity << 1;
@SuppressWarnings("unchecked")
final T[] newElementData = (T[]) Array.newInstance(clazz, newCapacity);
System.arraycopy(elementData, 0, newElementData, 0, oldCapacity);
newElementData[size - 1] = element;
elementData = newElementData;
}
return true;
}
FastList的add方法与ArrayList相比还有一点不同,FastList需要扩容时,通过左移一位来实现成倍扩容(final int newCapacity = oldCapacity << 1),而ArrayList每次都是1.5倍扩容(int newCapacity = oldCapacity + (oldCapacity >> 1))。很明显FastList的扩容少了一次+操作,性能更高。
get&set&remove
get也是使用频率极高的一个方法,那么FastList做了哪些优化了,请看源码。优化的地方非常清楚了,FastList假定绝大部分调用get方法的index是合法的,从而不进行范围检查。而ArrayList则需要先进行范围检查,再获取具体位置的值。我们知道:无论什么语言编写的多么高性能的代码,只要有代码执行,就会有性能损耗。这也是FastList干掉范围检查的原因,能快一点是一点:
/**
* @return the element, or ArrayIndexOutOfBounds is thrown if the index is invalid
*/
@Override
public T get(int index)
{
// ArrayList需要先调用rangeCheck(index)进行范围检查
return elementData[index];
}
set方法以及remove(int)方法和get方法进行了完全一样的优化,去掉了范围检查,它们都假定操作的index是完全合法的,只要每次操作的index完全有效,那么范围检查就是多余的,就可以通过干掉范围检查来提高性能。
removeLast
这是FastList新增的一个方法,ArrayList是没有这个方法的。为什么HikariCP新增了一个这样的方法呢?我们知道,在获取数据库连接执行SQL时,如果创建了多个Statement,那么后创建的Statement需要先关闭。以3个Statement为例,我们依次创建了stmt1,stmt2,stmt3。那么良好的编程习惯是先执行stmt3.close(),再stmt2.close(),最后stmt1.close()。而HikiriCP是用FastList管理Statement这个过程中实际调用了3次add()方法,然后依次调用3次removeLast方法,这就是为什么HikariCP需要造一个removeList方法的原因:
public T removeLast()
{
T element = elementData[--size];
elementData[size] = null;
return element;
}
size&isEmpty
这两个方法FastList都没有进行任何优化,和ArrayList是一样的。
总结
现在知道HikariCP为什么要这样做了吧?这是因为,HikariCP的FastList和JDK中的ArrayList定位不一样,ArrayList是提供给无数使用JDK的用户使用的,所以,它的使用环境非常恶劣,那么它必须做各种合法性的检查。而FastList是HikariCP用来管理Statement的,是给它自己使用的,是特定场景下为了性能极致优化而造出来的一个东西,它只适用于这样特定的场景。
往期推荐
扫一扫,关注我
一起学习,一起进步
每周赠书,福利不断
﹀
﹀
﹀
深度内容
推荐加入