17Javaコレクションシステムを学ぶ

コレクションとは

最初Collectionにこのクラスのソースコードを開き、最初の段落を表示します

  The root interface in the collection hierarchy.  A collection
  represents a group of objects, known as its elements.  Some
  collections allow duplicate elements and others do not.  Some are ordered
  and others unordered.  The JDK does not provide any direct
  implementations of this interface: it provides implementations of more
  specific subinterfaces like Set and List.  This interface
  is typically used to pass collections around and manipulate them where
  maximum generality is desired.

翻訳はおそらく意味します:

現在のクラスは、コレクション継承システムのルートインターフェイスです。

コレクションはオブジェクトのグループを表し、各オブジェクトはその要素と呼ばれます。

コレクションによっては、重複する要素を表示できるものとできないものがあります。

注文されたセットもあれば、無秩序なセットもあります。

JDKは、このインタフェースの直接的な実現を提供するが、例えば、いくつかの実施サブインターフェースを提供しませんSetList

このインターフェースは通常、いくつかのより一般的な場所でパラメーターとして使用されます。

コレクションインターフェイスは、Javaでのポリモーフィズムの実装です。コレクションが使用する必要のある一部のメソッドのみを提供します。それ自体では実装を提供しません。特定の実装サブクラスによって実装されます。次の図に示すように、これらはすべてによって定義されます。コレクション。メソッド、

メソッド名を見れば、おそらく何をしているのかがわかるでしょう。これは、コードの命名規則の利点でもあります。

add要素を追加することです。

addAll別のセットのすべての要素を現在のセットに追加することです。

remove要素を削除することです。

removeAllパラメータセットに含まれる要素を現在のセットから削除することです。

より多くの読者がこのクラスのソースコードコメントを直接読むことができます。ソースコードコメントドキュメントは詳細に書かれています。

リスト

 An ordered collection (also known as a sequence).  The user of this
 interface has precise control over where in the list each element is
inserted.  The user can access elements by their integer index (position in
 the list), and search for elements in the list.

Listは、Collectionのサブインターフェイスです。Collectionと比較すると、Listインターフェイスは、添え字インデックスによって要素を検索および保存できる一連のメソッドを提供します。

配列リスト

ArrayListは、Listインターフェイスの特定の実装クラスです。このクラスを使用して、要素を直接追加、削除、変更、およびチェックできます。次に例を示します。

List<String> strArr = new ArrayList<String>();
strArr.add("1");
strArr.add("2");
strArr.add("3");
...一直无限添加

問題は、上記のコードのArrayListオブジェクトが実際に無制限の入力を追加できるかどうかです。どのように実行されますか?

ArrayListの本質は実際には配列です。そのクラスにはObject[] elementData属性があります。この属性は、addメソッドやその他のメソッドによって継続的に追加される要素を格納するために使用されます。elementData配列の長さが十分でない場合、より大きな配列は前のアレイのすべてのデータをコピーしてから、最新のデータを新しいアレイに追加します。この方法は拡張と呼ばれます。

// 自己写的代码
public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<String>();
    list.add("1");
}

// ArrayList的add实现方法
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

// 调用add方法时 ArrayList的扩容方法
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 调用add方法时 ArrayList的扩容方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	// 这里算出当前对象的数组是不是初始化时的默认的,
    // 如果是,则在 minCapacity 和 DEFAULT_CAPACITY中取出一个最大的作扩容数组的长度。
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}


// 调用add方法时 ArrayList的扩容方法
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    // 这里如果+1后的最小容量大于等于当前数组里的长度时,将执行真正的扩容操作
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 调用add方法时 ArrayList的扩容方法
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // 默认按照1.5倍扩容
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    // 执行将老数据拷贝到一个新数组,并将新数组赋值给老数组变量操作
    // 这一步是最关键的地方
    elementData = Arrays.copyOf(elementData, newCapacity);
}

ArrayListソースコードでgrowメソッドを確認できます。このメソッドは最も重要な場所です。古い配列と新しい配列の置き換えはここにあります。ArrayListは、配列の直接操作をカプセル化するのに役立ちます。

addメソッドを呼び出すだけで、要素を追加し続けることができます。これは、カプセル化の利点の1つです。通常の状況では、ArrayListは、ビジネスシナリオに合った複数のオブジェクトを追加できます。もちろん、不当に使用すると、メモリが発生します。オーバーフロー。メモリがオーバーフローしない限り、いつでも要素を追加できます。

セットする

A collection that contains no duplicate elements.  More formally, sets
contain no pair of elements e1 and e2 such that
 e1.equals(e2), and at most one null element.  As implied by
its name, this interface models the mathematical set abstraction.

Setは、等しい要素を繰り返すことを許可せず、nullのみを含むコレクションです。繰り返されない要素を格納するシナリオや、要素の効率的な検索が必要なシナリオに適しています。

HashSet

HashSetは、Setインターフェイスの実装サブクラスの1つです。このクラスは、Setの特性を実装することに加えて、要素の格納が順番に格納されることを保証しません。

リストの効率と比較したクイック検索の例

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    HashSet<Integer> set = new HashSet<>();
    for (int i = 0; i < 1000_0000; i++) {
        list.add(i);
        set.add(i);
    }
    // list查找元素
    Date listStartTime = new Date();
    list.contains(9999_9999);
    Date listEndTime = new Date();
    System.out.println((listEndTime.getTime() - listStartTime.getTime())   + "ms");
    // set查找元素
    Date setStartTime = new Date();
    set.contains(9999_9999);
    Date setEndTime = new Date();
    System.out.println((setEndTime.getTime() - setStartTime.getTime()) + "ms");
}

リストには34ミリ秒かかり、セットには1ミリ秒未満しかかかりませんでした。これが効率のギャップです。

ストレージの一意性の例

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    HashSet<Integer> set = new HashSet<>();
    for (int i = 0; i < 10; i++) {
        list.add(1);
        set.add(1);
    }
    System.out.println(list);
    System.out.println(set);
}

ご覧のとおり、リストには10​​個の1がありますが、セットには1個しかありません。

HashSetがArrayListHashCodeよりもはるかに高速である理由

JavaのHashCode規則:

1. 同一个对象必须始终返回相同的HashCode
2. 两个对象的equals返回true,必须返回相同的HashCode
3. 两个对象不等,也可能返回相同的HashCode

なぜHashCodeがあるのですか?そうは言っても、例を挙げましょう。

HashSetに100万個の要素がある場合、この時点で1つ追加するときに、それが重複しているかどうかを判断するにはどうすればよいですか?

これらの百万の要素を1つずつ比較するために循環させる必要があり、これは非常に非効率的です。

たとえば、これらの100万個の要素を対応するハッシュバケットに分割すると、Zhang San、Zhang Si、Zhang WuがZhangという名前のバケットに割り当てられ、Liという名前のバケットがLiという名前のバケットに入れられるので、次回は家系の名前が張の人は、張という名前のバケツで探します。家系の名前が李の人は、家系の名前が李の人を探します。

JavaのHashCodeは、オブジェクトをバケットに入れるのと同じです。オブジェクトに基づいてHashCodeバケットを見つけることができます。バケットでは、自分と同じメモリアドレスまたは同じ値を持つオブジェクトがあるかどうかを判断できます。非常に効率的です。自分と同じオブジェクトがあるかどうかを判断します。

アウトオブオーダーストレージの例

public static void main(String[] args) {
    HashSet<String> set = new HashSet<>();
    set.add("3");
    set.add("9");
    set.add("7");
    set.add("13");
    set.add("1");
    System.out.println(set);
}

ご覧のとおり、要素は追加した順序で出力されていません。

地図

An object that maps keys to values.  A map cannot contain duplicate keys;
each key can map to at most one value.

This interface takes the place of the Dictionary class, which
was a totally abstract class rather than an interface.

マップは、キーから値にマップされるオブジェクトです。マップは重複キーを許可せず、各キーは最大で1つの値にマップできます。

MapインターフェースはDictionaryクラスを置き換えます。

MapとDictionaryの違いは、Mapの1つのキーは1つの値にしかマップできないが、Dictionaryは複数の値にマップできることです。

もちろん、Mapは多くの実装との単なるインターフェースです。HashMapの最も一般的に使用される実装について話しましょう。

HashMap

Hash table based implementation of the Map interface. 

This implementation provides all of the optional map operations, and permits null values and the null key. 

The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.

This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.

HashMapは、ハッシュテーブルに基づくマップインターフェイスの実装です。

彼はマップで提供されるすべての操作を実装し、キーまたは値としてnullを許可します。

HashMapとHashtableは基本的に同じですが、HashMapが同期されておらず(スレッドセーフではない)、null値が許可されている点が異なります。

HashMapは、マップされたデータの保存順序を保証するものではなく、元の順序も時間の経過とともに変化する可能性があることに注意してください。

HashMapは、put(Kキー、V値)を介して要素を保存します

 public static void main(String[] args) {
    Map<String,String> hashMap = new HashMap<>();
    hashMap.put("A","1");
    hashMap.put("B","2");
    System.out.println(hashMap);
}

HashMapは、get(Object key)を介して要素を取得します

public static void main(String[] args) {
    Map<String,String> hashMap = new HashMap<>();
    hashMap.put("A","1");
    hashMap.put("B","2");
    System.out.println(hashMap);
    // get获取
    System.out.println(hashMap.get("A"));
}

HashMap的keySet()


Returns a Set view of the keys contained in this map.

The set is backed by the map, so changes to the map are reflected in the set, and vice-versa.

keySet()メソッドはセットセットを返します。セットは現在のマップのキーです。このセットへの変更は現在のマップに影響し、その逆も同様です。(Setは繰り返すことができず、マップ内のキーは繰り返すことができないため、キーはSetに格納されます。)

public static void main(String[] args) {
    Map<String,String> hashMap = new HashMap<>();
    hashMap.put("A","1");
    hashMap.put("B","2");
    // 接收map的key set
    Set<String> keySet = hashMap.keySet();
    // 输出结果
    System.out.println(keySet);
    // 从map中删除一个key
    hashMap.remove("A");
    // 此刻输出结果,发现Set中少了一个A
    System.out.println(keySet);
}

上記のコードと結果から、keySetメソッドによって返されるSetオブジェクトを検証します。マップのキーがCRUD操作を実行している限り、Setオブジェクトもそれに応じて変更されます。

HashMapのentrySet()

public static void main(String[] args) {
    Map<String,String> hashMap = new HashMap<>();
    hashMap.put("A","1");
    hashMap.put("B","2");
    // 循环键值对
    for (Map.Entry<String, String> entry : hashMap.entrySet()) {
        // 输出key和value
        System.out.println("key: " + entry.getKey() + " value:" + entry.getValue());
    }
}

EntrySetはkeySetと同じであり、マップへの変更も彼に影響しますが、keySetメソッドはキーのみを返し、entrySetメソッドはキーと値の両方を返します。

HashMapの一般的な面接の質問

  1. HashMapの拡張

HashMapの展開はArrayListに似ています。これは、より大きな配列を作成し、元の配列の値をその配列に配置し、十分なスペースがあるときに最新の値をその配列に配置することです。

  1. HashMapのスレッドの不安定性

マルチスレッド環境でのHashMapでは、サイズ変更と拡張を同時に行うと、無限ループの問題が発生するため、ConcurrentHashMapを並行環境で使用できます。

  1. HashMapは1.7以降で変更されます

Java 1.7以降のバージョンの後、ハッシュバケットはリンクリストから赤黒木に変更されました。

その理由は、HashMapに10万個のバケットがあるためです。現時点では、100万個のデータを格納していますが、100万個のデータによって返されるHashCodeは同じであるため、すべて1つのバケットに格納されます。

現時点では、ハッシュバケットの利点はなくなり、1.7より前に同じバケットがリンクリストに格納されていたため、現時点では、100万個のオブジェクトの1つを見つけるのは非常に非効率的です。

そのため、1.7以降では、JDKは元のリンクリストを赤黒木に変更しました。

並べ替えを設定

まず、最初にコードを記述し、次に出力結果を分析して要約します。

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(10000, 196, -2 , -334422 , 23332 , 16);
    Set set1 = new HashSet();
    Set set2 = new LinkedHashSet();
    Set set3 = new TreeSet();
    for (Integer i : list) {
        set1.add(i);
        set2.add(i);
        set3.add(i);
    }
    set1.forEach(System.out::println);
    System.out.println("-------------");
    set2.forEach(System.out::println);
    System.out.println("-------------");
    set3.forEach(System.out::println);
}

HashSetの保存順序はランダムです

HashSetの出力:

10000
-334422
16
-2
196
23332

ここで、HashSetの出力結果が完全に無秩序であり、ルールがなく、ランダムに中断されていることがわかります。

LinkedHashSetと挿入の順序は同じです

LinkedHashSetの出力:

10000
196
-2
-334422
23332
16

これは、コードで追加した順序と同じであることがわかります。

TreeSet注文ストレージ

TreeSetの出力:

-334422
-2
16
196
10000
23332

TreeSetは小さいものから大きいものへとソートされます。特に、TreeMapとTreeSetは同じですが、TreeMapがキーと値のペアを格納する点が異なります。

赤黒木入門(二分木の枝)

まず、ArrayListのストレージ構造を見てください。

ArrayListは本質的に大きな配列です。何百万ものデータから要素を検索する場合は、ループして比較して検索する必要があります。

この検索の複雑さは、アルゴリズムでは線形複雑度と呼ばO(n)れます。n回検索される最悪の結果を持つn個の数値がある場合、コストはスケールの増加に比例します。

ツリーのストレージ構造を見てみましょう。

ツリーの子ノードは、左側の上位レベルよりも小さく、右側の上位レベルよりも大きくなっています。

配列では、1、2、3、4、5、6、および6個の要素のうち、配列を使用して6を見つける場合、6回見つける必要があります。

しかし、上のツリーを使用すると、ルートノード3が初めて検出され、6と比較すると、ルートノードが6より小さいことがわかり、ルートノードの右側に移動して検索します。

それから5を見つけ、5はまだ6よりも小さいことがわかったので、右側でそれを探し続け、最終的に6を見つけ、検索数は3で、効率の比較が行われました。

したがって、ツリーを使用すると、アルゴリズムの複雑さO(n)O(log n)線形時間から対数時間に変化します。

値が0の要素がツリーに挿入されると、ツリーは次のように左小と右大の原則を維持します。

収集ツールメソッド収集

まず、Javaのコレクションで、コレクション関連のツールを検索したい場合は、コレクションを検索して、少し知識についてお話ししましょう。

Setの場合は、Setsを検索し、ツールクラスであるコレクションクラスの名前の後にsを追加します。これは小さな仕様です。

  1. Collections.emptySet():空のコレクションを返します

  2. Collections.synchronizedCollection():コレクションをスレッドセーフに変更します

  3. Collections.unmodizableCollection():コレクションを不変に変更します(GuavaのImmutableも使用できます)

コレクションの他の実装

  1. キュー/再カウント

キュー

A collection designed for holding elements prior to processing.
  
Besides basic Collection operations, queues provide additional insertion, extraction, and inspection operations.

キューは、優先度要素を持つ一連のコレクションを格納するために使用されます。

コレクションの基本的な操作に加えて、挿入、抽出、チェックなどの操作も提供します。

キューは一般的なデータ構造です。たとえば、チケットを購入するためにキューに入れる場合、最初に入るのが最初にチケットを受け取るのです。

したがって、キューはこのモードで、先入れ先出し(LILO:Last in Last out)で、最初に入力された要素が最初に処理され、次にキューに入れられます。

Deque(両端キュー)

A linear collection that supports element insertion and removal at both ends.

The name deque is short for "double ended queue" and is usually pronounced "deck".

Dequeは、ヘッドとテールの両端での操作の追加と削除をサポートする線形コレクションです。

彼の名前はdequeの略です。

  1. ベクター/スタック(JDKによって放棄されました)

ベクター

 If a thread-safe implementation is not needed, it is recommended to use ArrayList in place of Vector.

VectorはArrayListの前身です。現在、JDKはそれを推奨していませんが、ArrayListを推奨しています。

スタック

A more complete and consistent set of LIFO stack operations is
provided by the {@link Deque} interface and its implementations,   
which should be used in preference to this class.  For example:

Deque<Integer> stack = new ArrayDeque<Integer>();

Stackはfirst-in-last-outをサポートするキューですが、現在JDKはサポートしていません。Dequeをお勧めします。

  1. LinkedList

LinkedListは、リンクリストの実装です。ArrayListとの違いは、ArrayListが配列を使用して順序を並べ替えることです。

LinkedListは、前の要素が次の要素を指し、各要素が次の要素の位置を格納することを意味するため、リンクリストと呼ばれます。

  1. ConcurrentHashMap

ConcurrentHashMapは、HashMapのスレッドセーフな実装です。

  1. PriorityQueue
An unbounded priority Queue queue based on a priority heap.

ヒープ実装に基づくボーダレス優先キュー。

このキューは携帯電話の目覚まし時計のようなもので、リストです。たとえば、朝7時、リストの最後の9時に目覚まし時計があり、その他はすべて午後のアラームです。

ただし、9時以降は、ストレージの順序に関係なく、現在の環境で優先度が最も高いため、9時のアラームが最初に鳴ります。

グアバ

Guavaは、Googleによって開発されたコアJavaクラスライブラリのセットであり、Javaネイティブコレクションの多くの充填と拡張を行います。

興味のある読者は、githubにアクセスして調べることができます:https://github.com/google/guava

まず、Mavenプロジェクトでjarパッケージを紹介します

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

マルチセット

public static void main(String[] args) {
    Multiset multiset = HashMultiset.create();
    multiset.add(1);
    multiset.add(2);
    multiset.add(3);
    multiset.add(3);
    System.out.println(multiset);
}

HashSetの場合、出力結果は1、2、3ですが、マルチセット出力は[1、2、3 x 2]、

Multisetは、同じオブジェクトが配置された回数をカウントするのに役立つため、これはGuavaが拡張するのに役立つSetコレクションです。

マルチマップ

public static void main(String[] args) {
    Multimap multimap = HashMultimap.create();
    multimap.put(1,1);
    multimap.put(1,2);
    multimap.put(1,3);
    System.out.println(multimap);
}

従来のマップはキーごとに1つの値しか保存できず、マルチマップはキーごとに複数の値に対応できます。

BiMap

public static void main(String[] args) {
    BiMap biMap = HashBiMap.create();
    biMap.put(1,"我是1");
    System.out.println(biMap.inverse().get("我是1"));
}

従来のマップはキーによってのみ値を見つけることができ、BiMapは値によってキーを見つけることができます。

上記はいくつかの典型的なグアバの例です、他の読者は彼ら自身で見つけるでしょう。

総括する

データ構造とアルゴリズムはいたるところにあります。JavaおよびJavaサードパーティライブラリでは、データ構造の実装アルゴリズムはすべて非常に優れています。この記事は興味深い記事です。読者がJavaシステムで他のデータ構造の実装をもっと見つけられることを願っています。原則について考え、この分野でのあなた自身の知識を強化してください。

おすすめ

転載: blog.csdn.net/cainiao1412/article/details/108552575