JAva実装ホップテーブル

60534 ワード

前言
最近、ネットでredisがなぜバランスツリーではなくジャンプメーターを使っているのかを見て、好奇心を持って見ました.ジャンプテーブルのコンセプトはネット上にありますが、ここでは後述しません.総じて、ホップテーブルは、バランスツリーよりも簡単であり、範囲検索の効率は少なくともバランスツリーと同じである.同時にskiplistはハッシュテーブル、バランスツリーの2つの検索に使用される一般的で効率的なデータ構造に分類できません.だから1つを実現しました(主に実現の簡単さを見ました).
コード#コード#
/**
 *                             O(logn)
 *
 * @author FenG
 * @date 2019-10-25 19:36:28
 */
public class SkipList<T> {

    //              ,    32,           2^32-1     
    private static final int MAX_LEVEL = 32;

    private final SkipNode HEAD;
    private final Random random = new Random();
    private int level = 0;
    private int size = 0;


    public SkipList() {
        this(4);
    }

    public SkipList(int level) {
        this.HEAD = new SkipNode<T>(level);
        this.level = level;
    }

    /**
     *   score      
     *
     * @param score
     * @return
     */
    public T get(double score) {
        SkipNode<T> node = doGet(score);
        return node == null ? null : node.value;
    }

    /**
     *   score start end   
     *
     * @param start
     * @param end
     * @return score start end     
     */
    public SubList<T> range(int start, int end) {
        if (end < start) {
            throw new IllegalArgumentException("End should bigger than start");
        }
        SkipNode<T> p = HEAD;
        int lev = level - 1;
        boolean found = false;
        while (lev >= 0 && p != null) {
            if (p.level[lev].forward == null) {
                lev--;
                continue;
            }
            if (p.level[lev].forward.score < start) {
                p = p.level[lev].forward;
            } else {
                p = p.level[lev].forward;
                found = true;
                break;
            }
        }
        if (!found) {
            return SubList.EMPTY_LIST;
        }
        int len = 0;
        LinkedList<T> list = new LinkedList<T>();
        while (p != null) {
            if (p.score > end) {
                break;
            }
            list.add(p.value);
            p = p.level[0].forward;
        }
        return new SubList<T>((T[]) list.toArray());
    }

    /**
     *  score      value   
     *
     * @param score
     * @param value
     */
    public void put(double score, T value) {
        //               
        SkipNode<T> node = null;
        node = doGet(score);

        if (node != null) {
            //      ,       ,     value  
            node.value = value;
        } else {
            //           ,        ,        doGet(double)
            //         :          ,         Level  ,
            //            Level        forward  
            //             ,  doGet(double)       ,            
            int lev = randomLevel();
            SkipNode<T> newNode = new SkipNode<T>(score, value, lev);
            if (lev > level) {
                //         
                Level[] src = HEAD.level;
                Level[] dest = new Level[lev];
                System.arraycopy(src, 0, dest, 0, level);
                while (level < lev) {
                    dest[level] = new Level();
                    dest[level].forward = newNode;
                    level++;
                }
                HEAD.level = dest;
            }

            SkipNode<T> p = HEAD;
            lev = level - 1;
            int h = newNode.level.length - 1;
            while (lev >= 0) {
                if (p.level[lev].forward != null && p.level[lev].forward.score < score) {
                    p = p.level[lev].forward;
                } else {
                    if (lev > h) {
                        lev--;
                        continue;
                    }
                    if (p.level[lev].forward == newNode) {
                        //                                   forward  
                        //           forward    newNode(while (level < lev)   ),
                        //        newNode.level[lev].forward     ,     
                        //       p.level[lev].forward      newNode  
                        lev--;
                        continue;
                    }
                    newNode.level[lev].forward = p.level[lev].forward;
                    p.level[lev].forward = newNode;
                    lev--;
                }
            }
            size++;
        }
    }

    /**
     *   score       
     *
     * @param score
     */
    public void delete(double score) {
        SkipNode p = HEAD;
        int lev = level - 1;
        boolean deleted = false;
        while (lev >= 0 && p != null) {
            if (p.level[lev].forward == null) {
                lev--;
                continue;
            }
            if (p.level[lev].forward.score == score) {
                deleted = true;
                //        ,          ,           
                p.level[lev].forward = p.level[lev].forward.level[lev].forward;
                lev--;
            } else if (p.level[lev].forward.score < score) {
                p = p.level[lev].forward;
            } else {
                lev--;
            }
        }
        if (deleted) {
            size--;
        }
    }

    public int size() {
        return size;
    }

    @Override
    public String toString() {
        if (size <= 0) {
            return "SkipList{}";
        }
        StringBuilder sb = new StringBuilder("SkipList{
"
); SkipNode p = HEAD; int lev = level - 1; while (lev >= 0) { while (p != null) { sb.append(p.value); sb.append(" "); p = p.level[lev].forward; } sb.append("
"
); lev--; p = HEAD; } sb.append("}"); return sb.toString(); } private int randomLevel() { int lev = 1; while ((random.nextInt() & 1) == 0 && lev < MAX_LEVEL) { lev++; } // 1 return lev; } private SkipNode<T> doGet(double score) { SkipNode<T> p = HEAD; int lev = level - 1; while (lev >= 0 && p != null) { if (p.score == score) { return p; } if (p.level[lev].forward == null) { // , lev--; continue; } if (p.level[lev].forward.score > score) { // , lev--; } else { p = p.level[lev].forward; } } return null; } private static class SubList<E> extends AbstractList<E> { private static final SubList EMPTY_LIST = new SubList(); E[] element; int size; public SubList() { } public SubList(E[] element) { this.element = element; this.size = this.element.length; } @Override public E get(int index) { if (index < 0 || index >= size) { throw new IllegalStateException("Array index is out of range: " + index); } return element[index]; } @Override public int size() { return size; } @Override public String toString() { if (size <= 0) { return "SubList{}"; } StringBuilder sb = new StringBuilder("SubList{"); for (E e : element) { sb.append(e); sb.append(", "); } sb.delete(sb.length() - 2, sb.length()); sb.append("}"); return sb.toString(); } } private class SkipNode<E> { Level[] level; double score; E value; SkipNode<E> backward; SkipNode(int lev) { initLevel(lev); this.score = Double.MIN_VALUE; } SkipNode(double score, E value, int lev) { initLevel(lev); this.value = value; this.score = score; } private void initLevel(int lev) { level = new Level[lev]; for (int i = 0; i < lev; i++) { level[i] = new Level(); } } } private class Level<E> { SkipNode<E> forward; // // int span; } }

ネット上ではホップテーブルを実現する例が多く見られたが,いずれもdownで下層を表すため,多くのインスタンスノードが同じデータを表す必要があり,より大きな空間浪費をもたらしたため,自分で配列で層を実現し,redisソースコードにはspan属性があるが,スパンをどのように実現するかは考えられなかったため,しばらくこの属性は用いられなかった.そして挿入は最適化できるが、書くのがおっくうだ...
テスト
/**
     *         :
     * -XX:AutoBoxCacheMax=1000000 -Xmx1g -Xms1g
     *       Integer   
     */
    @Test
    public void skipListCompareToLinkedList() {
        Random random = new Random();
        SkipList<Integer> skipList = new SkipList<Integer>();
        LinkedList<Integer> linkedList = new LinkedList<Integer>();
        //   IntegerCache ,      
        Integer.valueOf(1);

        System.out.println("-------------               -------------");

        long start = System.currentTimeMillis();
        for(int i = 0; i < 1000000; i++) {
            skipList.put(i, i);
        }
        long end = System.currentTimeMillis();
        System.out.println("  skipList   : " + (end - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            linkedList.add(i);
        }
        end = System.currentTimeMillis();
        System.out.println("  linkedList   : " + (end - start));

        System.out.println("-------------         -------------");
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            skipList.get(random.nextInt(1000000));
        }
        end = System.currentTimeMillis();
        System.out.println("skipList: " + (end - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            linkedList.get(random.nextInt(1000000));
        }
        end = System.currentTimeMillis();
        System.out.println("linkedList: " + (end - start));

    }

仮想マシンの3つのパラメータはそれぞれ
  • -XX:AutoBoxCacheMax=1000000
  • -Xmx1g
  • -Xms1g

  • 最初のパラメータはInteger自動箱詰めキャッシュの最大値であり、テスト時にIntegerを繰り返し作成しないために、開始前にIntegerを作成してキャッシュします.2番目と3番目のパラメータはjavaスタックのサイズを指定し、発見空間申請が何回も失敗したことを指定しなかった後、fullGCを実行するとLinkedListの作成がSkipListよりも遅くなるため、スタックのサイズを調整した.
    -------------               -------------
      skipList   : 545
      linkedList   : 25
    -------------         -------------
    skipList: 19
    linkedList: 7175
    

    SkipListの挿入はLinkedListよりも時間がかかることがわかりますが、検索が少しでも速くなり、挿入にかかる時間は完全に受け入れられます.