讀HikariCP原始碼學Java(二)—— 因地制宜的改裝版ArrayList:FastList

繆若塵發表於2021-06-13

前言

如前文所述,HikariCP為了提高效能不遺餘力,其中一個比較特別的優化是它沒有直接使用ArrayList,而是自己實現了FastList,因地制宜,讓陣列的讀寫效能都有了一定程度的提高。

構造方法

FastList:

@SuppressWarnings("unchecked")
public FastList(Class<?> clazz)
{
    this.elementData = (T[]) Array.newInstance(clazz, 32); // ArrayList
    this.clazz = clazz;
}

/**
  * Construct a FastList with a specified size.
  * @param clazz the Class stored in the collection
  * @param capacity the initial size of the FastList
  */
@SuppressWarnings("unchecked")
public FastList(Class<?> clazz, int capacity)
{
    this.elementData = (T[]) Array.newInstance(clazz, capacity);
    this.clazz = clazz;
}

ArrayList

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
transient Object[] elementData;
private int size;

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else {
        if (initialCapacity != 0) {
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }

        this.elementData = EMPTY_ELEMENTDATA;
    }

}

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((this.size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            this.elementData = a;
        } else {
            this.elementData = Arrays.copyOf(a, this.size, Object[].class);
        }
    } else {
        this.elementData = EMPTY_ELEMENTDATA;
    }

}

在無參構造方法中,ArrayList初始化了一個空陣列,這是出於節省空間的考慮,但在HikariCP中,可以確認只要建立了FastList就一定會使用,所以直接初始化一個長度為32的陣列,以減少FastList的擴容次數。

還有一點不同是FastList的所有構造方法都傳入了陣列儲存的元素的類,這是為了之後的擴容過程中不必使用反射進行型別推導。

新增元素

FastList:

public boolean add(T element)
{
    if (size < elementData.length) {
        elementData[size++] = element;
    }
    else {
        // 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++] = element;
        elementData = newElementData;
    }

    return true;
}

ArrayList:

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length) {
        elementData = this.grow();
    }

    elementData[s] = e;
    this.size = s + 1;
}

public boolean add(E e) {
    ++this.modCount;
    this.add(e, this.elementData, this.size);
    return true;
}

public void add(int index, E element) {
    this.rangeCheckForAdd(index);
    ++this.modCount;
    int s;
    Object[] elementData;
    if ((s = this.size) == (elementData = this.elementData).length) {
        elementData = this.grow();
    }

    System.arraycopy(elementData, index, elementData, index + 1, s - index);
    elementData[index] = element;
    this.size = s + 1;
}

private Object[] grow(int minCapacity) {
    return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));
}

private Object[] grow() {
    return this.grow(this.size + 1);
}

首先可以注意到的是,ArrayList中除了預設的一個引數的add方法,還有一個帶有插入元素下標的整型引數的過載,用於插入到陣列的中間位置。但HikariCP中只需要向陣列的末尾新增元素,所以不必實現複雜的陣列後移操作。

這樣的設計帶來的另一個好處是可以省去陣列越界的判斷,因為只會插入到尾部,不會出現不受控制的插入行為。

另外,ArrayList中使用了一個整型變數modCount記錄修改的次數。這是一個簡單的CAS機制,避免在多執行緒訪問ArrayList(迭代器方式)時,陣列發生了結構變化,導致併發問題。

擴容

當新增過程中出現容量不夠時,ArrayList和FastList都會進行擴容。二者有如下兩點區別:

  1. ArrayList完全依靠泛型系統獲知元素的型別,而FastList在例項化陣列的時候就傳入了元素型別,因此FastList的插入效率要更高一些。
  2. ArrayList擴容的倍數的1.5倍,而FastList是2倍,可見FastList是為了減少擴容次數,降低時間複雜度,犧牲了一點空間複雜度。

刪除元素

FastList:

public T remove(int index)
{
    if (size == 0) {
        return null;
    }

    final T old = elementData[index];

    final int numMoved = size - index - 1;
    if (numMoved > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, numMoved);
    }

    elementData[--size] = null;

    return old;
}

public boolean remove(Object element)
{
    for (int index = size - 1; index >= 0; index--) {
        if (element == elementData[index]) {
            final int numMoved = size - index - 1;
            if (numMoved > 0) {
                System.arraycopy(elementData, index + 1, elementData, index, numMoved);
            }
            elementData[--size] = null;
            return true;
        }
    }

    return false;
}

ArrayList:

public E remove(int index) {
    Objects.checkIndex(index, this.size);
    Object[] es = this.elementData;
    E oldValue = es[index];
    this.fastRemove(es, index);
    return oldValue;
}

public boolean remove(Object o) {
    Object[] es = this.elementData;
    int size = this.size;
    int i = 0;
    if (o == null) {
        while(true) {
            if (i >= size) {
                return false;
            }

            if (es[i] == null) {
                break;
            }

            ++i;
        }
    } else {
        while(true) {
            if (i >= size) {
                return false;
            }

            if (o.equals(es[i])) {
                break;
            }

            ++i;
        }
    }

    this.fastRemove(es, i);
    return true;
}

private void fastRemove(Object[] es, int i) {
    ++this.modCount;
    int newSize;
    if ((newSize = this.size - 1) > i) {
        System.arraycopy(es, i + 1, es, i, newSize - i);
    }

    es[this.size = newSize] = null;
}

FastList和ArrayList的刪除都分為兩種,一種是刪除指定位置的元素,另一種是刪除指定元素。

刪除指定位置的元素

刪除指定位置的元素比較簡單,二者都通過向前複製陣列實現,區別是ArrayList會對引數做校驗,FastList省略了這一步。

刪除指定元素

二者的實現也類似,但ArrayList首先進行了判空。

另外,ArrayList刪除元素也會修改modCount。

獲取元素

FastList:

public T get(int index) {
    return elementData[index];
}

ArrayList:

public E get(int index) {
    Objects.checkIndex(index, this.size);
    this.checkForComodification();
    return this.root.elementData(this.offset + index);
}

ArrayList會做陣列越界的校驗,FastList不會,其它的沒有區別,都是直接取陣列指定位置的元素。

迭代器

FastList:

public Iterator<T> iterator()
{
    return new Iterator<T>() {
        private int index;

        @Override
        public boolean hasNext()
        {
            return index < size;
        }

        @Override
        public T next()
        {
            if (index < size) {
                return elementData[index++];
            }

            throw new NoSuchElementException("No more elements in FastList");
        }
    };
}

ArrayList:

public Iterator<E> iterator() {
    return this.listIterator();
}

public ListIterator<E> listIterator(final int index) {
    this.checkForComodification();
    this.rangeCheckForAdd(index);
    return new ListIterator<E>() {
        int cursor = index;
        int lastRet = -1;
        int expectedModCount;

        {
            this.expectedModCount = SubList.this.modCount;
        }

        public boolean hasNext() {
            return this.cursor != SubList.this.size;
        }

        public E next() {
            this.checkForComodification();
            int i = this.cursor;
            if (i >= SubList.this.size) {
                throw new NoSuchElementException();
            } else {
                Object[] elementData = SubList.this.root.elementData;
                if (SubList.this.offset + i >= elementData.length) {
                    throw new ConcurrentModificationException();
                } else {
                    this.cursor = i + 1;
                    return elementData[SubList.this.offset + (this.lastRet = i)];
                }
            }
        }

        public boolean hasPrevious() {
            return this.cursor != 0;
        }

        public E previous() {
            this.checkForComodification();
            int i = this.cursor - 1;
            if (i < 0) {
                throw new NoSuchElementException();
            } else {
                Object[] elementData = SubList.this.root.elementData;
                if (SubList.this.offset + i >= elementData.length) {
                    throw new ConcurrentModificationException();
                } else {
                    this.cursor = i;
                    return elementData[SubList.this.offset + (this.lastRet = i)];
                }
            }
        }

        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            int size = SubList.this.size;
            int i = this.cursor;
            if (i < size) {
                Object[] es = SubList.this.root.elementData;
                if (SubList.this.offset + i >= es.length) {
                    throw new ConcurrentModificationException();
                }

                while(i < size && SubList.this.root.modCount == this.expectedModCount) {
                    action.accept(ArrayList.elementAt(es, SubList.this.offset + i));
                    ++i;
                }

                this.cursor = i;
                this.lastRet = i - 1;
                this.checkForComodification();
            }

        }

        public int nextIndex() {
            return this.cursor;
        }

        public int previousIndex() {
            return this.cursor - 1;
        }

        public void remove() {
            if (this.lastRet < 0) {
                throw new IllegalStateException();
            } else {
                this.checkForComodification();

                try {
                    SubList.this.remove(this.lastRet);
                    this.cursor = this.lastRet;
                    this.lastRet = -1;
                    this.expectedModCount = SubList.this.modCount;
                } catch (IndexOutOfBoundsException var2) {
                    throw new ConcurrentModificationException();
                }
            }
        }

        public void set(E e) {
            if (this.lastRet < 0) {
                throw new IllegalStateException();
            } else {
                this.checkForComodification();

                try {
                    SubList.this.root.set(SubList.this.offset + this.lastRet, e);
                } catch (IndexOutOfBoundsException var3) {
                    throw new ConcurrentModificationException();
                }
            }
        }

        public void add(E e) {
            this.checkForComodification();

            try {
                int i = this.cursor;
                SubList.this.add(i, e);
                this.cursor = i + 1;
                this.lastRet = -1;
                this.expectedModCount = SubList.this.modCount;
            } catch (IndexOutOfBoundsException var3) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (SubList.this.root.modCount != this.expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
    };
}

對比ArrayList,FastList的迭代器省去了以下幾步流程:

  1. ArrayList利用前文提到的modCount,實現了checkForComodification方法,進行併發安全性校驗。
  2. ArrayList在判斷陣列越界之外增加了一道校驗:SubList.this.offset + i >= elementData.length,這也是一道併發安全性校驗。
  3. ArrayList在匿名類中實現了方法forEachRemaining,用於lambda呼叫。

總結

另外,FastList省去了很多不需要的ArrayList的方法,減小了類的體積。

綜上,FastList是完全用於內部呼叫的類,不對外暴露,所以可以減少很多安全性的校驗和設計,以提高效能。

相關文章