何がアラリストですか
13608 ワード
概要
ArayListはjava集合の枠組みの中で比較的によく使われるデータ構造である.AbstractListから引き継ぎ、Listインターフェースを実現しました.下部は配列に基づいて容量サイズの動的変化を実現した.nullの存在を許可します.また、RandomAccess、Cloeable、Serializableインターフェースを実現したので、ArayListはクイックアクセス、コピー、プログレッシブをサポートしています.
メンバー変数
ArayList下層は配列に基づいて容量サイズの動的変化を実現した.
デフォルトの初期容量は10です.
構造関数無惨構造関数 は、初期容量の大きさがinitial CapacityであるArayList を構成する.は、指定Collectionを使用してArayListの構造関数 を構築する.
主な操作方法の解析 add操作 Remove操作 get操作
ディエ代機iterator
集合を使用したことがある人は知っています.forで集合を巡回する時は集合をremove操作してはいけません.remove操作で集合の大きさが変わります.結果が不正確であり、配列の下の標的がオフラインになりやすいため、より深刻なものはConcerentModificationExceptionを投げます.
foreach巡回はiteratorと同じです.異常原因を明らかにするためには、ソースコードをもう一度通過しなければなりません.
Itrの3つの主要関数を見てみます.
hasNextは比較的に簡単に実現して、もし次の元素の下付きが集合の大きさに等しいならば、最後まで証明しました.
nextの方法も複雑ではないですが、肝心です.まず、expectedModCountとmodCountが同じかどうかを判断します.その後、cursorを判断して、セットサイズと配列長を超えているかどうかを確認します.その後、cursorはlastRetに値を割り当て、lastRetとラベル付けされた要素を返します.最後にcursorを1から増やします.開始時、cursor=0、lastRet=-1;nextメソッドを呼び出すたびに、cursorとlastRetは1つずつ増加します.
remove方法はまず、lastRetの値が0より小さいかどうかを判断し、expectedCountとmodCountが等しいかを確認します.次にキーとして、直接ArayListを呼び出すremove方法は、lastRetと名付けられた要素を削除する.その後、lastRetをcursorに割り当て、lastRetを−1に再割り当てし、expectedModCountに値を再割り当てする.
次にItrの動作を分析します.図のように、開始時には0とラベルされた要素を指し、lastRetは−1とラベルされた要素、つまりnullを指します.nextを呼び出すたびに、cursorとlastRetはそれぞれ1つずつ増加します.nextが“C”に戻ると、cursorとlastRetはそれぞれ3と2[図2]である.
このときremoveを呼び出します.Itrのremoveではなく、ArayListのremoveです.D Eの二つの要素を直接前に移動します.最後の位置が空いています.そしてmodCountは1を増やします.remove方法から分かります.[図三]
このときcursor=3,size=4は、配列の最後まで来ていないので、ループは継続します.nextメソッドに来て、前のステップのremove方法がmodCountに対して修正をしたため、expected ModCountとmodCountが等しくなくなりました.これがConccurrent Modification Exception異常の原因です.例.pngからもArayListの内部カテゴリItrにおけるcheckForCompodification方法による異常が見られます.
異常な解決:
iterator.removeを直接呼び出してもいいです.この方法ではexpected ModCount=modCount動作が追加されているからです.しかし、このremove方法にも弊害があります.はremove操作しかできません.add、clearなどItrにはありません. Removeを呼び出す前にnextを呼び出す必要があります.removeが始まったので、lastRetをチェックしました.また、lastRetは-1に初期化されます. next以降は一回だけremoveを呼び出すことができます.RemoveはlastRetを-1 に再初期化するためです.
要約:ArayList下層は、配列に基づいて容量の大きさとダイナミック可変性を実現する.拡大機構は、最初の拡大容量が原容量の1.5倍である.もし1.5倍が小さすぎると、私たちが必要とする容量の大きさをnewCapacityに割り当てます.1.5倍が大きすぎたり、必要な容量が大きすぎたりしたら、直接
ArayListはjava集合の枠組みの中で比較的によく使われるデータ構造である.AbstractListから引き継ぎ、Listインターフェースを実現しました.下部は配列に基づいて容量サイズの動的変化を実現した.nullの存在を許可します.また、RandomAccess、Cloeable、Serializableインターフェースを実現したので、ArayListはクイックアクセス、コピー、プログレッシブをサポートしています.
メンバー変数
ArayList下層は配列に基づいて容量サイズの動的変化を実現した.
/**
* The size of the ArrayList (the number of elements it contains).
*/
private int size; //
transient Object[] elementData;
注意:上のsizeはelementaの中に実際にどれぐらいの要素があるかを意味します.element Data.lengthは集合容量で、最大でどれぐらいの要素が収容できるかを表しています.デフォルトの初期容量は10です.
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
この変数はAbstractListに定義されている.Listの操作回数を記録します.主に使用するのはIteratorで、反復の過程で集合が修正されることを防止するためです.protected transient int modCount = 0;
次の二つの変数はコンストラクションの中で使われます./**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
二つの空の配列は何の違いがありますか?We distinguish this from EMPTY_ELIENTDATA to know how much to inflate when first element is added.簡単に言うと、最初に元素を添加した時に、このelement Dataが空からの構造関数か、それとも構造関数が初期化されているかを知っています.どのように拡大するかを確認します.構造関数
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
注:コメントは、容量が10の空のリストセットを構成するということですが、構造関数は、elementaに空の配列を割り当てただけで、実は最初に要素を追加した時に容量が10に拡大しました.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);
}
}
上記のソースから分かります.無参画構造関数を使う時はDEFAULtCAPACITY_です.EMPTY_EEMENTDATAはelementaに値を与えます.initial Capacityがゼロの時はEMPTY_です.EEMENTDATAはelementaに値を与えます.initial Capacityが0より大きいとき、initial Capacityサイズのobject配列を初期化して、element Dataに値を与えます.public ArrayList(Collection extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
Collectionを配列に変換し、element Dataに値を与え、elementaの要素の個数をsizeに与えます.sizeがゼロでなければ、elementaのクラスタイプはObject[]であるかどうかを判断し、そうでなければ一回の変換を行います.sizeがゼロなら、EMPTY_を.EEMENTDATAは、new ArayList(0)に相当するelementaに割り当てられている.主な操作方法の解析
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
したがって、要素を集合に追加するたびに、集合容量の大きさを確認する.そしてsizeを1から増やします.ensureCapacity Internal関数では、elementa=DEFAULTCCAPACITY_EMPTY_EEMENTDATAはDEFAULT_を取ります.CAPACITYとminCapacityの最大値はつまり10です.これがEMPTY_ですELLENTDATAとDEFAULTACACITY_EMPTY_EEMENTDATAの違いはどこですか?また、上記の説明も検証した.無惨な構造関数を使用する場合、初めて元素を添加する時に初期化容量は10である.ensureExplicit CapacityではmodCountに対して1を自己増加し、操作回数を記録し、その後、minCapacityがelementaの長さより大きい場合、集合を拡張する.初めて要素を追加した時のelementaの長さは明らかにゼロです.じゃ、grow関数を見てみます.private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
簡単明瞭な関数で、デフォルトでは1.5倍の容量に拡張されます.しかし、拡張後は必ずしも適用されません.小さすぎるかもしれません.大きすぎるかもしれません.だから次の二つのif判断があります.もし1.5倍が小さすぎると、私たちが必要とする容量の大きさをnewCapacityに割り当てます.1.5倍が大きすぎたり、必要な容量が大きすぎたりしたら、直接newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
を持って拡大します.そして、元の配列のデータをnewCapacityサイズの新しい配列にコピーし、新しい配列をelementaに割り当てます.public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
public boolean addAll(Collection extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
以上のソースコードから分かるように、add(int index、E element)、addAll(Collection extens E>c)、addAll(int index、Collection extens E>c)操作は、集合容量の検査を先に行い、配列が逸脱しないことを確認します.そして、古い配列要素をSystem.arraycopy()法で新しい配列にコピーします.public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work
}
remove(int index)を呼び出すと、まずindexが適法かどうかを確認し、削除する要素が配列の最後の位置にあるかを判断します.indexが最後でないなら、再度System.arraycopy()メソッドコピー配列を呼び出します.つまり、index+1からすべての要素を前に一つの位置に移動します.配列の最後の位置を空白にします.size-1.indexが最後の要素であれば、配列の最後の位置をそのまま空にします.size-1でいいです.remove(Object o)を呼び出すと、oを空に分けて処理します.その後、配列を巡回して、最初のoに対応する下付きindexを見つけて、fastRemove方法を呼び出して、indexとしてマークされた要素を削除します.実際にはfastRemove(int index)方法とremove(int index)方法は基本的に全部同じです.public E get(int index) {
rangeCheck(index);
return elementData(index);
}
ArayList下層は配列に基づいて実現されるので、要素を取得するのはかなり簡単であり、配列ランダムアクセスを直接呼び出すだけでよい.ディエ代機iterator
集合を使用したことがある人は知っています.forで集合を巡回する時は集合をremove操作してはいけません.remove操作で集合の大きさが変わります.結果が不正確であり、配列の下の標的がオフラインになりやすいため、より深刻なものはConcerentModificationExceptionを投げます.
foreach巡回はiteratorと同じです.異常原因を明らかにするためには、ソースコードをもう一度通過しなければなりません.
public Iterator iterator() {
return new Itr();
}
元は直接にItrオブジェクトを返します.private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ソースからは、ArayListはItrインターフェースを実現する内部カテゴリを定義することができます.Itr内部には3つのメンバー変数があります.cursor:次の訪問する要素を表します.lastRet:前に訪問する要素の下付きを表します.expectedModCount:ArayList修正回数に対する期待値を表し、初期値はmodCountである.Itrの3つの主要関数を見てみます.
hasNextは比較的に簡単に実現して、もし次の元素の下付きが集合の大きさに等しいならば、最後まで証明しました.
nextの方法も複雑ではないですが、肝心です.まず、expectedModCountとmodCountが同じかどうかを判断します.その後、cursorを判断して、セットサイズと配列長を超えているかどうかを確認します.その後、cursorはlastRetに値を割り当て、lastRetとラベル付けされた要素を返します.最後にcursorを1から増やします.開始時、cursor=0、lastRet=-1;nextメソッドを呼び出すたびに、cursorとlastRetは1つずつ増加します.
remove方法はまず、lastRetの値が0より小さいかどうかを判断し、expectedCountとmodCountが等しいかを確認します.次にキーとして、直接ArayListを呼び出すremove方法は、lastRetと名付けられた要素を削除する.その後、lastRetをcursorに割り当て、lastRetを−1に再割り当てし、expectedModCountに値を再割り当てする.
次にItrの動作を分析します.図のように、開始時には0とラベルされた要素を指し、lastRetは−1とラベルされた要素、つまりnullを指します.nextを呼び出すたびに、cursorとlastRetはそれぞれ1つずつ増加します.nextが“C”に戻ると、cursorとlastRetはそれぞれ3と2[図2]である.
このときremoveを呼び出します.Itrのremoveではなく、ArayListのremoveです.D Eの二つの要素を直接前に移動します.最後の位置が空いています.そしてmodCountは1を増やします.remove方法から分かります.[図三]
このときcursor=3,size=4は、配列の最後まで来ていないので、ループは継続します.nextメソッドに来て、前のステップのremove方法がmodCountに対して修正をしたため、expected ModCountとmodCountが等しくなくなりました.これがConccurrent Modification Exception異常の原因です.例.pngからもArayListの内部カテゴリItrにおけるcheckForCompodification方法による異常が見られます.
異常な解決:
iterator.removeを直接呼び出してもいいです.この方法ではexpected ModCount=modCount動作が追加されているからです.しかし、このremove方法にも弊害があります.
要約:ArayList下層は、配列に基づいて容量の大きさとダイナミック可変性を実現する.拡大機構は、最初の拡大容量が原容量の1.5倍である.もし1.5倍が小さすぎると、私たちが必要とする容量の大きさをnewCapacityに割り当てます.1.5倍が大きすぎたり、必要な容量が大きすぎたりしたら、直接
newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
を持って拡大します.拡張後は配列のコピーにより元素の正確性を確保しますので、拡大操作をできるだけ減らすようにします.ArayListの最大記憶能力:Integer.MAX_VALEsizeはセットに格納されている要素の個数です.elementeData.lengthは配列長であり、最大でいくつの要素が記憶されてもよいかを示しています.周りを見ながらremoveが必要なら、iteratorを使用しなければなりません.またremoveの前にnextをしなければなりません.nextの後は一回だけremoveを使います.