JavaのTreeSetのガイド
1. 概要
この記事では、Javaコレクションフレームワークとone of the most popular Set implementations – the TreeSetの不可欠な部分について説明します。
2. TreeSetの紹介
簡単に言えば、TreeSetは、AbstractSetクラスを拡張し、NavigableSetインターフェイスを実装するソートされたコレクションです。
この実装の最も重要な側面の概要は次のとおりです。
-
ユニークな要素を保存します
-
要素の挿入順序は保持されません
-
要素を昇順に並べ替えます
-
スレッドセーフではありません
In this implementation, objects are sorted and stored in ascending order according to their natural order。 TreeSetは、自己平衡二分探索木、より具体的にはa Red-Black treeを使用します。
簡単に言えば、自己バランス型のバイナリ検索ツリーであるため、バイナリツリーの各ノードは余分なビットで構成され、赤または黒のノードの色を識別するために使用されます。 後続の挿入および削除中に、これらの「色」ビットは、ツリーのバランスが多少保たれていることを確認するのに役立ちます。
それでは、TreeSetのインスタンスを作成しましょう。
Set treeSet = new TreeSet<>();
2.1. コンストラクタコンパレータパラメータを使用したTreeSet
オプションで、ComparableまたはComparator:を使用して要素が並べ替えられる順序を定義できるコンストラクターを使用して、TreeSetを作成できます。
Set treeSet = new TreeSet<>(Comparator.comparing(String::length));
TreeSetはスレッドセーフではありませんが、Collections.synchronizedSet()ラッパーを使用して外部で同期できます。
Set syncTreeSet = Collections.synchronizedSet(treeSet);
さて、TreeSetインスタンスを作成する方法が明確になったので、利用可能な一般的な操作を見てみましょう。
3. ツリーセット add()
予想どおり、add()メソッドは、TreeSetに要素を追加するために使用できます。 要素が追加された場合、メソッドはtrue,を返します。それ以外の場合–false.
メソッドのコントラクトは、要素がSetにまだ存在しない場合にのみ、要素が追加されることを示しています。
TreeSetに要素を追加しましょう:
@Test
public void whenAddingElement_shouldAddElement() {
Set treeSet = new TreeSet<>();
assertTrue(treeSet.add("String Added"));
}
The add method is extremely important as the implementation details of the method illustrate how the TreeSet works internally、TreeMap’sputメソッドを活用して要素を格納する方法:
public boolean add(E e) {
return m.put(e, PRESENT) == null;
}
変数mは、内部バッキングTreeMapを参照します(TreeMapはNavigateableMapを実装することに注意してください)。
private transient NavigableMap m;
したがって、TreeSetは、TreeSetのインスタンスが作成されるときにTreeMapのインスタンスで初期化されるバッキングNavigableMapに内部的に依存します。
public TreeSet() {
this(new TreeMap());
}
これについての詳細はthis articleで見つけることができます。
4. TreeSet contains()
The contains() method is used to check if a given element is present in a given TreeSet.要素が見つかった場合はtrueを返し、それ以外の場合はfalse.を返します。
動作中のcontains()を見てみましょう。
@Test
public void whenCheckingForElement_shouldSearchForElement() {
Set treeSetContains = new TreeSet<>();
treeSetContains.add("String Added");
assertTrue(treeSetContains.contains("String Added"));
}
5。 TreeSet remove()
remove()メソッドは、指定された要素が存在する場合、それをセットから削除するために使用されます。
セットに指定された要素が含まれている場合、このメソッドはtrue.を返します
実際の動作を見てみましょう。
@Test
public void whenRemovingElement_shouldRemoveElement() {
Set removeFromTreeSet = new TreeSet<>();
removeFromTreeSet.add("String Added");
assertTrue(removeFromTreeSet.remove("String Added"));
}
6. TreeSet clear()
セットからすべてのアイテムを削除する場合は、clear()メソッドを使用できます。
@Test
public void whenClearingTreeSet_shouldClearTreeSet() {
Set clearTreeSet = new TreeSet<>();
clearTreeSet.add("String Added");
clearTreeSet.clear();
assertTrue(clearTreeSet.isEmpty());
}
7. TreeSet size()
size()メソッドは、TreeSetに存在する要素の数を識別するために使用されます。 これは、APIの基本的な方法の1つです。
@Test
public void whenCheckingTheSizeOfTreeSet_shouldReturnThesize() {
Set treeSetSize = new TreeSet<>();
treeSetSize.add("String Added");
assertEquals(1, treeSetSize.size());
}
8. TreeSet isEmpty()
isEmpty()メソッドを使用して、特定のTreeSetインスタンスが空であるかどうかを判断できます。
@Test
public void whenCheckingForEmptyTreeSet_shouldCheckForEmpty() {
Set emptyTreeSet = new TreeSet<>();
assertTrue(emptyTreeSet.isEmpty());
}
9. TreeSet iterator()
iterator()メソッドは、Set.Those iterators are fail-fastの要素に対して昇順で反復するイテレーターを返します。
ここで昇順の反復順序を確認できます。
@Test
public void whenIteratingTreeSet_shouldIterateTreeSetInAscendingOrder() {
Set treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator itr = treeSet.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
さらに、TreeSetを使用すると、Setを降順で繰り返すことができます。
実際の動作を見てみましょう。
@Test
public void whenIteratingTreeSet_shouldIterateTreeSetInDescendingOrder() {
TreeSet treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator itr = treeSet.descendingIterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
Iteratorは、イテレータのremove()メソッド以外の方法でイテレータが作成された後、いつでもセットが変更されたConcurrentModificationException ifをスローします。
このためのテストを作成しましょう:
@Test(expected = ConcurrentModificationException.class)
public void whenModifyingTreeSetWhileIterating_shouldThrowException() {
Set treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator itr = treeSet.iterator();
while (itr.hasNext()) {
itr.next();
treeSet.remove("Second");
}
}
または、イテレータのremoveメソッドを使用した場合、例外は発生しませんでした。
@Test
public void whenRemovingElementUsingIterator_shouldRemoveElement() {
Set treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Second");
treeSet.add("Third");
Iterator itr = treeSet.iterator();
while (itr.hasNext()) {
String element = itr.next();
if (element.equals("Second"))
itr.remove();
}
assertEquals(2, treeSet.size());
}
同期されていない同時変更が存在する場合、ハード保証を行うことは不可能であるため、イテレータのフェイルファスト動作は保証されません。
これについての詳細はhereで見つけることができます。
10. TreeSet first()
このメソッドは、空でない場合、TreeSetから最初の要素を返します。 それ以外の場合は、NoSuchElementExceptionをスローします。
例を見てみましょう:
@Test
public void whenCheckingFirstElement_shouldReturnFirstElement() {
TreeSet treeSet = new TreeSet<>();
treeSet.add("First");
assertEquals("First", treeSet.first());
}
11. TreeSet last()
上記の例と同様に、セットが空でない場合、このメソッドは最後の要素を返します。
@Test
public void whenCheckingLastElement_shouldReturnLastElement() {
TreeSet treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add("Last");
assertEquals("Last", treeSet.last());
}
12. TreeSet subSet()
このメソッドは、fromElementからtoElement.の範囲の要素を返します。fromElementは包括的であり、toElementは排他的であることに注意してください。
@Test
public void whenUsingSubSet_shouldReturnSubSetElements() {
SortedSet treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
treeSet.add(5);
treeSet.add(6);
Set expectedSet = new TreeSet<>();
expectedSet.add(2);
expectedSet.add(3);
expectedSet.add(4);
expectedSet.add(5);
Set subSet = treeSet.subSet(2, 6);
assertEquals(expectedSet, subSet);
}
13. TreeSet headSet()
このメソッドは、指定された要素よりも小さいTreeSetの要素を返します。
@Test
public void whenUsingHeadSet_shouldReturnHeadSetElements() {
SortedSet treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
treeSet.add(5);
treeSet.add(6);
Set subSet = treeSet.headSet(6);
assertEquals(subSet, treeSet.subSet(1, 6));
}
14. TreeSet tailSet()
このメソッドは、指定された要素以上のTreeSetの要素を返します。
@Test
public void whenUsingTailSet_shouldReturnTailSetElements() {
NavigableSet treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.add(2);
treeSet.add(3);
treeSet.add(4);
treeSet.add(5);
treeSet.add(6);
Set subSet = treeSet.tailSet(3);
assertEquals(subSet, treeSet.subSet(3, true, 6, true));
}
15. Null要素の保存
Java 7より前は、null要素を空に追加することが可能でした。 TreeSet。
ただし、それはバグと見なされていました。 したがって、TreeSet no longer supports the addition of null.
TreeSet,に要素を追加すると、要素は自然な順序に従って、またはcomparator.で指定されたとおりに並べ替えられます。したがって、既存の要素と比較してnull,を追加すると、%(t3 )snullはどの値とも比較できないため:
@Test(expected = NullPointerException.class)
public void whenAddingNullToNonEmptyTreeSet_shouldThrowException() {
Set treeSet = new TreeSet<>();
treeSet.add("First");
treeSet.add(null);
}
TreeSetに挿入される要素は、Comparableインターフェースを実装するか、少なくとも指定されたコンパレーターによって受け入れられる必要があります。 All such elements must be mutually comparable,i.e.e1.compareTo(e2) or comparator.compare(e1, e2)mustn’t throw a ClassCastException.
例を見てみましょう:
class Element {
private Integer id;
// Other methods...
}
Comparator comparator = (ele1, ele2) -> {
return ele1.getId().compareTo(ele2.getId());
};
@Test
public void whenUsingComparator_shouldSortAndInsertElements() {
Set treeSet = new TreeSet<>(comparator);
Element ele1 = new Element();
ele1.setId(100);
Element ele2 = new Element();
ele2.setId(200);
treeSet.add(ele1);
treeSet.add(ele2);
System.out.println(treeSet);
}
16. TreeSetのパフォーマンス
HashSetと比較すると、TreeSetのパフォーマンスは低くなっています。 add、remove、searchなどの操作にはO(log n)時間がかかりますが、n要素をソートされた順序で印刷するような操作にはO(n)時間が必要です。
TreeSetは昇順または降順でアクセスおよびトラバースでき、昇順の操作とビューのパフォーマンスは次のようになる可能性があるため、エントリを並べ替えたままにする場合は、TreeSetを主に選択する必要があります。降順のものよりも速い。
局所性の原理-メモリアクセスパターンに応じて、同じ値または関連するストレージの場所が頻繁にアクセスされる現象の用語です。
地域を言うとき:
-
同様のデータは、多くの場合、同様の頻度でアプリケーションによってアクセスされます
-
2つのエントリが順序付けられて近くにある場合、TreeSetはそれらをデータ構造内、つまりメモリ内で互いに近くに配置します。
TreeSetは、より局所性の高いデータ構造であるため、局所性の原則に従って、メモリが不足している場合や、TreeSetを優先する必要があると結論付けることができます。自然な順序に従って互いに比較的近い要素にアクセスしたい。
データをハードドライブ(キャッシュまたはメモリから読み取られるデータよりも遅延が大きい)から読み取る必要がある場合は、局所性が高いため、TreeSetを優先します。
17. 結論
この記事では、Javaで標準のTreeSet実装を使用する方法を理解することに焦点を当てます。 重複を避け、要素を並べ替える能力があることから、その目的と使いやすさに関してどれほど効率的であるかを見ました。
いつものように、コードスニペットはover on GitHubで見つけることができます。