查看原文
其他

HikariCP为什么自己造了一个FastList?

程序猿DD 2021-05-26

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的改进,下面列举出两个类具体对比如下表格所示:

操作FastListArrayList
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的,是给它自己使用的,是特定场景下为了性能极致优化而造出来的一个东西,它只适用于这样特定的场景。


DD自研的沪牌代拍业务,点击直达


往期推荐

Spring Boot 2.4.0 正式发布!全新的配置处理机制,拥抱云原生!

服务网格仍然很难

10道棘手的Java面试题,看看你能答对几个?

如果MySQL磁盘满了,会发生什么?

Mysql 都会遭受哪些方面的攻击?

Git 提交代码之后的几种后悔药


扫一扫,关注我

一起学习,一起进步

每周赠书,福利不断

深度内容

推荐加入





    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存