序文
最も一般的に使用されるJavaクラスとして設定ArrayListのフレームは、一般的には、最も適切とのデータのセットを記憶します。これらは、より良いArrayListを、以下の側面のArrayListの深い理解から、この記事を理解し、使用するためには、理由を知っている知っています:
- ArrayListを持つのはなぜない配列、
- ソースコード解析のArrayListのプロパティ
- Javaの8 ArrayListの後
- 正しい姿勢を使用してArrayListを
なぜ配列は、ArrayListには使用しません。
Java言語で、による一般的な制限のため、配列の長さによって、それが配列定義された長さを初期化する必要がある場合、要素の数に応じて動的に拡張、および開発者のためのJavaメソッド呼び出しの限られた配列、のみ素子アレイの長さを取り、いくつかの単純な添加元素を得ることができません操作。Javaの1.2の背景には、簡単にセットリストを操作するために、開発者を有効にする、アレイ、すべてのリストのArrayListの実装作業の日々の開発を使用する代わりに、配列リストの実装のArrayListの動的な拡張として使用することができ、強力な豊かなコレクションフレームワークが導入されています。ここでは、私たちが最初にArrayListの下に主な特徴を列挙され、後に詳述します。
整然とした記憶素子
- 繰り返し要素を可能にする貯蔵可能
null
値 - 動的な拡張のサポート
非スレッドセーフ
より良いのArrayListを理解するために、我々は最初のArrayListからUMLのクラス図を見てください:
ArrayListのはAbstractListを継承図から分かるように、直接Cloneableを、直列化、ランダム・タイプフラグインタフェースを実装。
- 実装の抽象リストとしてAbstractList、検索要素を変更するための追加や削除を実行するために特定のサブクラスに引き渡され、それは、動作中の要素の上にデフォルトの実装の繰り返し処理を提供します。
- Cloneableインタフェースを実装し、それは、オブジェクトのArrayListの呼び出しのための支援表明
clone
のArrayListのコピーを達成するための方法を。 - シリアライズインターフェースは、記載された配列も固定有するのArrayListおよびデシリアライゼーションのオペレーションをサポートする
serialVersionUID
プロパティ値。 - RandomAccessインタフェースは、ArrayListのが表す、要素がランダムにアクセス有効な効率、以下の標準的なデジタル方法の要素にアクセスすることができます。ランダム・リスト実装されたインタフェースは、通常のトラバーサルに直接使用することができ
for
、サイクルモード、およびイテレータより効率的な方法を実行します。
ArrayListのソースコード解析
クラスのメンバーの構造からソースコードへのArrayListは、すぐにArrayListの2つの重要な変数を参照してくださいelementData
とsize
。
elementData
オブジェクトは、ArrayListのオブジェクトがこの配列に関連している[]、外部CRUDトラバースを提供するオブジェクトのオブジェクトのこの配列を維持することのArrayListの要素に外部必要、したがってに追加され、記憶素子のアレイでありますArrayListの要素が整然と配列オブジェクトに格納されてelementData
います。size
フィールドは、現在ArrayListに追加された要素の数を示し、オブジェクトが配列に等しい未満でなければならないことに留意すべきであるelementData
長さ。一度場合、同じ長さ、およびリストへの添加元素は、ArrayListの膨張動作が実行される場合にも、より長い配列要素を有する予め記憶されたオブジェクト。size
elementData
オブジェクトの配列を維持する基礎となるように、天然のArrayListのコレクションに追加された要素を繰り返すことができ、それは許可されnull
、そしてそれぞれのインデックス位置は同じではありません。
どのように展開
整然とした記憶要素と要素を繰り返すことができる理由は、エンドArrayListのは理解して、我々は根本的な拡張が達成されるか、動的配列のリストとして見ていきます。
まず、上記のように膨張は、ある次の機会、決定するsize
フィールドで述べたように、場合すなわち十分な容量拡張の必要性がないであろう同じ長さの瞬間に、セットに要素を追加処理動作を添加ArrayListの膨張が発生し、一定の条件が満たされた場合にのみ発生します。size
elementData
単一の要素を追加し、他のセット内のすべての要素を追加:私たちは今、2つのカテゴリに分かれ要素を追加するには4つの方法があります見ることができるコード構造ArrayListクラスを見てみましょう。
参照、分析を開始するための簡単な方法で開始add(E):boolean
方法を:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
上記第三の行から分かるように、単に一つの要素を追加することであり、したがってsize
1だけインクリメントしてから膨張に実装ensureCapacityInternal
その引数であるプロセス、size
+1、実際の添加元素前に決定された要素に追加されアレイの元の長さよりもあれば数、すなわち必要な最小容量を設定します。これを見てensureCapacityInternal
達成する方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
}
内のすべての最初は、比較的単純に見える2つのメソッドの呼び出しがあるcalculateCapacity
方法は:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
場合等しいアレイが空である、リターン要素は、既定の最小容量値を追加してもよい10に対応し、または着信に係る+1最小容量値、その後の実行後見える方法:elementData
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
DEFAULT_CAPACITY
size
ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
膨張が実装されたコードから分かるgrow
方法で、配列の長さは必要最小限の容量未満である場合にのみ行わ:記憶素子のアレイは場合満杯と新たに追加された要素を格納することができません。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
さらにへジャンプgrow
方法の実装、ツールの方法を使用して、ライン8を参照してjava.util.Arrays#copyOf(T[], int)
元の配列をコピーし、長さの内部要素のすべてのnewCapacity
新しい配列の、新しいアレイの割り当てに対応する参照elementData
。以下に示すように、今、配列の長さの新しい基準で更新されたオブジェクト内部のArrayListは、効果が達成されます。
今度は、新しい容量の配列の代表を見てみましょうnewCapacity
数に調整されます。まず、newCapacity
によりoldCapacity + (oldCapacity >> 1)
ビット算術元容量値を用いた計算により得られたoldCapacity
右のいずれかを介して、得られた半分の値が(切り捨て)、その後、元の値の容量を追加し、元の容量の値であるoldCapacity
1.5倍。
>>
右ビット演算子は、2と同等で割った、右オペランドに残される、と表現のような、切り捨て(7 >> 1) == 3
真です。
計算した場合newCapacity
の時間が、アレイの現在の数を示すために、渡された最小の容量値よりもさらに小さい、デフォルトの空でDEFAULT_CAPACITY
容量値として割り当てアレイ。
アレイの最大数の追加の決意があることに注意してください、MAX_ARRAY_SIZE
以下の定義に対応するファイル内のコード:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
制限を超えてJVMをスローするようになります場合はArrayListの記憶素子は、制約の数が最も多いOutOfMemoryError
の例外を。
これにjava.util.ArrayList#add(E)
最後まで拡張分析の論理的な方法。同様に、私たちが見ることができる追加要素の中に達成するために、他の方法でensureCapacityInternal
呼び出されたメソッドは、基になる配列の実際の運転容量前に決定されます、容量が動的な拡張のために十分ではありません。
シリアライズとデシリアライズ
transient Object[] elementData;
ソースに見られるArrayListのelementData
キーワードとtransient
、通常transient
キーワード修飾フィールドはフィールドがシリアル化されないことを示しているが、ArrayListのシリアル化インタフェースを実装し、によって提供される方法のシーケンスwriteObject
逆シリアル化メソッドはreadObject
実装これはそれを行う方法ですか?
私たちは、最初のコードのArrayListの列を見て:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);
for (int i = 0; i < size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
非現在のオブジェクトのコードの4最初の行static
変性、非transient
変性フィールドはストリームに書き込まれ、容量などの要素の数を書き込むこと約6。
次のステップは、このステップがないArrayListのシリアル化のためのデータを記憶するためのメモリ空間に見ることができるすべての要素を含むことになる書き込みサイクルを通って流れることで、実装のそれらの方法の順序で空間および時間を節約します。
同様に、データの入ってくる流れに応じて取得されたデシリアライズ読み取りsize
属性は、アレイの拡張、及びオブジェクトの配列に保持されたデータを読み取るために、データストリームに格納され、最終的にすべての要素を。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
s.readInt(); // ignored
if (size > 0) {
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
for (int i = 0; i < size; i++) {
a[i] = s.readObject();
}
}
}
コピーについて
要素のリストのコピーについては、ArrayListのは、カスタムのクローンは、以下を実現提供します:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
実行上記のコードから明らかなようにcopyOf
操作が浅いコピー操作は、要素がオブジェクトの元のコピーが新しいArrayListオブジェクトに保存した後、それぞれのフィールドに戻ることはありませんArrayListのelementData
各記憶位置には同一の要素であります一度配列内の要素のリストを修正された参照は、別のリストにも影響されるであろう。
JDK 1.8 ArrayListの後
ビューのソースポイントからのArrayListの特性を完全に分析した後、私たちは、新しいJDK 1.8は、ArrayListクラスの変更後に何を見てください。
新しい方法removeIf
removeIf
ArrayListの親クラスがインタフェースを実装し、そのメソッドを持っているので、Collectionインタフェースは、新しいインターフェースメソッドです。removeIf
配列から要素を削除するために指定された条件を実行するための方法。
public boolean removeIf(Predicate<? super E> filter){...}
着信インターフェイス関数パラメータの状態を表すPredicate
状態である場合は、条件に合致する、すなわちラムダ式はtrue
、要素は、以下のサンプルコードは、例えば、アレイから除去されます。
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
numbers.removeIf(i -> i % 2 == 0);
System.out.println(numbers); // [1, 3, 5, 7, 9]
新しいメソッドspliterator
このメソッドは、Collectionインタフェースからも、ArrayListのこの方法が書き直されました。この方法は、素子分離容器を横断するために使用されて格納されるListSpliteratorインスタンスを返します。
@Override
public Spliterator<E> spliterator() {
return new ArrayListSpliterator<>(this, 0, -1, 0);
}
内部静的メソッドの実装でArrayListのは、可能な要素の集合によって操作することができるクラスオブジェクトArrayListSpliteratorを返します。
次の3つの方法の主な操作:
tryAdvance
に似た反復単一の要素、iterator.next()
forEachRemaining
残りの要素イテレーションtrySplit
要素は、並列に二つの部分に切断するが、スレッドセーフではないSpliteratorに注意しています。
この方法は、一般的に3を使用していないが、まだあなたは、単に使用しての方法を検討することができ、理解する必要があります
ArrayList<Integer> numbers = new ArrayList<>(Arrays.asList(1,2,3,4,5,6));
Spliterator<Integer> numbers = numbers.spliterator();
numbers.tryAdvance( e -> System.out.println( e ) ); // 1
numbers.forEachRemaining( e -> System.out.println( e ) ); // 2 3 4 5 6
Spliterator<Integer> numbers2 = numbers.trySplit();
numbers.forEachRemaining( e -> System.out.println( 3 ) ); //4 5 6
numbers2.forEachRemaining( e -> System.out.println( 3 ) ); //1 2 3
我々は確かにジェスチャーを使用します。
元のArrayListや新しいAPIと接触した後、我々は最終的に正常な発達に効率的にArrayListのを使用する方法を学びます。
効率的な初期化
ArrayListの三のコンストラクタを実装して、デフォルトで作成された空の配列オブジェクトに割り当てられEMPTY_ELEMENTDATA
、2番目は、データ収集のタイプを初期化するために渡され、配列の長さ、すなわち、着信第初期値のセット長を可能にします。各配列の長さではないので、十分な容量の拡張が長く、メモリを再申請してリードし、それをコピーします。そして、あなたはパフォーマンスを提供し、拡張アレイの数を減らすことができ、私たちは配列指定された初期サイズのArrayListを初期化してみましょう。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
横断要素
普通を反復:JDK 1.8の前に、ArrayListのが唯一の三つの方法をサポートしてトラバースfor
サイクルは、for-each
強化後、JDK1.8でストリームAPIを導入し、同じセットのArrayListコレクションに所属し、使用することができるstream.foreach()
要素の数を取得する方法を:
ArrayList<String> names = new ArrayList<String>(Arrays.asList( "alex", "brian", "charles"));
names.forEach(name -> System.out.println(name)); // alex brian charles
変換アレイ
ArrayListのリストは、配列を変換するための2つの方法を提供します
public Object[] toArray();
public <T> T[] toArray(T[] a);
- 第一の方法は、Object型の配列を返します
- 第2の方法では、アレイの戻り型が指定された配列型が渡されます。リストの長さとは配列で返されたラインアレイ、コピー後の配列要素に渡された場合。そうでない場合は、新しい配列が入ってくる配列のリストのサイズや種類に応じて再配分され、その後、完成リターンをコピーします。
上述した第2の方法から分かるように、元のタイプを保持することがより適切です。
ArrayList<String> list = new ArrayList<>(4);
list.add("A");
list.add("B");
list.add("C");
list.add("D");
String[] array = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(array)); // [A, B, C, D]
マルチスレッドに対処
必要なリストは、一般的に採用されている場合は、スレッドセーフ、スレッド・セーフな方法自体はないのArrayListここで注意しなければならないjava.util.Collections#synchronizedList(java.util.List<T>)
か、代わりにVectorクラスを使用します。もう一つの方法は、それが元の更新の基になる配列のコピーを作成することによって実現され、複数のスレッドのコンテナクラスCopyOnWriteArrayListとの同時使用を使用することで、追加など、スレッドの同期を減らし、スレッドセーフなだけでなく、同期運転しなければなりませんでした。
ヘッドノードに対処するための追加と削除
ArrayListのアレイが非効率的で、後続のヘッダデータの複製と再整列の必要性、ヘッドの配列の要素を追加または削除がある場合、連続したメモリ空間を使用して実装されます。シーンと同様の操作が多数の場合は、パフォーマンス上の理由から、我々は代わりのLinkedListを使用する必要があります。LinkedListのは、要素の位置を最初からトラバースし、リストの前半分上で動作するリンクリストの実装に基づいているため、すぐに対応する位置に挿入された要素を見つける、または操作を削除します。
エピローグ
ここでは、よりスムーズに、私たちの毎日の使用のために、より多くの私たちはより良い理解を持って、回収容器のベースとして一般的な使用のArrayListを要約して実現することを学びます。次回の記事では、収集段階、私のマイクロチャンネル公衆数に小さなパートナー興味を持って歓迎の注意を分析し、更新し、ArrayListのを楽しみにしてますとにより、上記のリストのLinkedListの別のセットに、それは、異なるシーンを使用して、さまざまな方法で実現しました。
参照
- https://www.cnblogs.com/skywang12345/p/3308556.html
- https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html
- https://yuqirong.me/2018/01/21/ArrayList%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E% 90 /
- https://juejin.im/post/5a58aa62f265da3e4d72a51b
- https://howtodoinjava.com/java-arraylist/
- http://cmsblogs.com/?p=4727