ArrayListとLinkedListを組み合わせるとどうなりますか?

あいさつします!
コレクション、すなわちArrayListLinkedListなどのList実装を検討した後、これらのデータ構造を1つに結合して、その結果を確認してください。


なぜこれが必要なのですか?



解決策


要素の挿入と受信が一定の時間で行われるようなデータ構造を作成するとどうなりますか。 配列を再作成せずにArrayListテクノロジーを使用します
それらを一緒に接続するために、二重リンクリストを使用します:


実装


ソースコードに直接進みます。


 public class Node<T> { Node prev; Node next; T[] value; public Node(Node prev, Node next, T[] value) { this.prev = prev; this.next = next; this.value = value; } } 

まず、標準の二重リンクリスト構造を作成します。ここで、 valuevalueの配列です。
次に、クラスの具体的な実装に進み、必要な変数を宣言します。


 public static int INIT_CAPACITY = 100; private Object[] arr = new Object[INIT_CAPACITY]; private int index = 0; private int level = 0; private Node<T> first = new Node<>(null, null, (T[]) arr); private Node last = first; private Node current = null; private int size = 0; 

ここでINIT_CAPACITYは配列の初期サイズであり、対応するコンストラクターで再定義できますINIT_CAPACITYは配列自体です。 index変数はインデックスの計算に必要です。 levellevel計算に使用されます。 firstにリストの最初の要素へのリンク、
lastリストの最後の要素へのリンク、current-リストの現在の要素(最後の選択)へのリンク。これにより、連続する要素またはそれらに近い要素の選択を高速化できます。size-サイズ(またはデータ量)。
デフォルトで2つのkostruktoraを設定し、初期サイズを変更します。


 public MyLinkedList() { first.next = last.next; } public MyLinkedList(int size) { INIT_CAPACITY = size; arr = new Object[INIT_CAPACITY]; first.next = last.next; } 

アイテムの追加:


 public void add(T i) { if (index == INIT_CAPACITY) { arr = new Object[INIT_CAPACITY]; last.next = new Node<>(last, null, (T[]) arr); index = 0; last = last.next; } arr[index] = i; index++; size++; } 

ここで、配列が満杯の場合に条件をチェックし、新しい配列を作成して、そのリンクを記憶します。
アイテムを受け取る:


 public T get(int i) { T value; int level = i / INIT_CAPACITY; int index = i % INIT_CAPACITY; if (this.current == null) { this.level = 0; this.current = first; } if(this.level > level) for (int j = this.level; j < level; j++) { this.level = level; current = current.prev; } else for (int j = this.level; j < level; j++) { this.level = level; current = current.next; } value = (T) current.value[index]; return value; } 

レベルはリスト内の配列の数です。つまり、レベル0、1配列、レベル1、2配列などです。 indexは現在のレベル0..INIT_CAPACITYインデックスであり、現在の要素へのリンクもあります。前の選択から取得されたcurrentリスト、つまり 新しいレベルが前のレベルよりも大きい場合、現在の要素から先に進み、逆の場合は元に戻ります。 また、2つのクイック操作を追加しました-最初と最後の要素を取得します。


 public T getFirst(){ return first.value[0]; } public T getLast() { return (T) last.value[(size-1) % INIT_CAPACITY]; } 

最初と最後のアイテムの取得は、配列の場合と同じくらい簡単で高速です。
最後の要素を削除する操作は、値をnullで上書きする最も簡単な方法です。配列全体がnullでいっぱいになると、リンクが失われ、ガベージコレクターがすべてをクリアします。


 public void removeLast(){ if (last.value[0] == null) { last = last.prev; index=INIT_CAPACITY-1; } last.value[(size-1) % INIT_CAPACITY]=null; size--; index--; } 

サイズを取得することは明らかです。 イテレータも追加されました。 このクラスはIterableを実装し、iteratorメソッドを実装します


 private Node<T> first; private int index = -1; public MyLinkedListIterator(Node<T> first) { this.first = first; } @Override public boolean hasNext() { index++; return first != null; } @Override public T next() { T value; int index = this.index % INIT_CAPACITY; value= first.value[index]; if(index==INIT_CAPACITY-1||this.first.value[index+1]==null) first=first.next; return value; } 

作業時間


JMHベンチマーク、テスト例を使用して測定:


 public class TestGetMyDeque { private static class SetDeque { MyDeque<Integer> deque = new MyDeque<>(); SetDeque() { for (int i = 0; i <Constant.COUNT_ELEMENT; i++) { deque.add(i); } } } @State(Scope.Benchmark) public static class MyState{ SetDeque deque; @Setup(Level.Invocation) public void setUp() { deque = new SetDeque(); } } @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.SECONDS) public void getFirst(MyState state, Blackhole blackhole) { for (int i = 0; i < Constant.COUNT_ELEMENT; i++) { blackhole.consume(state.deque.deque.getFirst()); } } } 

ドキュメントはインターネットで見ることができます
異なる数の要素を取得し、数秒で結果をテストします。


N = 100000最後に挿入最初の取得平均を取得する最後に
ミデケ0.00200.0010
配列0.0020-0.001
配列リスト0.0020.00100.001
LinkedList0.003028,4650.001

100万個の要素を取ります:


N = 1,000,000最後に挿入最初の取得平均を取得する最後に
ミデケ0.0190.0050.0100.007
配列0,0250.005-0.005
配列リスト0,0360.0050.0050.005
LinkedList0.0510.005OutOfMemoryError:Javaヒープスペース0.006

最後に、1000万の要素を取得します。


N = 10,000,000最後に挿入最初の取得平均を取得する最後に
ミデケ1,8320,0470,0900,071
配列2,0650,046-0.058
配列リスト4,4130,0480,0470,048
LinkedListOutOfMemoryError:Javaヒープスペース

https://github.com/jheusser/core-java-performance-examples/の原則に基づいてメモリ測定を行いました


(x軸は100万要素、y軸はmbのメモリ)


グラフからわかるように、この構造によるメモリ消去は、同様のデータ構造とそれほど変わりません。


おわりに


一番下の行には、通常のDequeよりも高速に動作するLIFOキューがあり、ArrayListよりもはるかに高速に要素を挿入するという点で、LinkedListを使用するときに遭遇する問題もありません。 将来的には、パフォーマンスを損なうことなくどこからでも挿入や削除などの操作を実装することが計画されています。このテーマについてはすでにいくつかの開発がありますが、この段階でフィードバックを受け取り、新しいデータ構造のさらなる作業のためにコミュニティの関心を見てみたいです。


プロジェクトはここで見ることができます



Source: https://habr.com/ru/post/J352930/


All Articles