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

公众号程序猿DD

共 1749字,需浏览 4分钟

 ·

2020-11-18 03:54

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 extends ArrayList
{
   ... ...
}

我们现在大概知道了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 提交代码之后的几种后悔药


扫一扫,关注我

一起学习,一起进步

每周赠书,福利不断

深度内容

推荐加入





浏览 62
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报