序文
この章から、一般的に使用されるArrayList、LinkedList、HashSet、HashMap、TreeSet、TreeMapなどのほとんどのコレクションファミリーと、次のようなイテレータを含む、JAVAコレクション部分の分析について説明します。 Iterator、ListIterator、Collectionsツールクラス、そして一般的に使用されていないコレクションの簡単な分析があり、コレクション間の長所と短所を比較して分析します。このようにして、Javaのコレクション部分を包括的に理解し、コーディングプロセス中に使用するコレクションをよりよく知ることができます。
ArrayList構造図
ArrayListは非常に一般的に使用されており、そのコードはそれほど多くありません。分類によると、図では主にいくつかの部分に分かれています。
(1)プロパティ
// ArrayList的默认容量
private static final int DEFAULT_CAPACITY = 10;
// ArrayList 内部维持的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// ArrayList内部维持的默认空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList维持的可变数组;ArrayList的核心属性。
transient Object[] elementData;
// 元素在容器中的个数,与elementData.length有区别。
private int size;
// 容器被修改的次数
protected transient int modCount = 0;
// ArrayList内部维持的最大数组长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
(2)コンストラクタ
// 无参构造
ArrayList()
// 指定容量构造
ArrayList(int initialCapacity)
// 将指定集合构造成ArrayList
ArrayList(Collection<? extends E> c)
(3)拡張メカニズム
ensureCapacity(int minCapacity)
ensureCapacityInternal(int minCapacity)
ensureExplicitCapacity(int minCapacity)
grow(int minCapacity)
hugeCapacity(int minCapacity)
(4)共通API
a。増加
clone()
add(E e)
add(int index, E element)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)
b。削除
clear()
trimToSize()
remove(int index)
remove(Object o) fastRemove(int index)
removeRange(int fromIndex, int toIndex)
removeAll(Collection<?> c)
retainAll(Collection<?> c)
batchRemove(Collection<?> c, boolean complement)
c。変更
set(int index, E element)
d。チェック
size()
isEmpty()
get(int index)
indexOf(Object o)
lastIndexOf(Object o)
e。判断
contains(Object o)
f。変換
toArray()
toArray(T[] a)
(5)インナークラス
迭代器: Itr
ListItr
集合: SubList
ソースコード分析
ArrayListは、エンコードプロセスでよく使用されるコレクションです。その最下層は配列elementDataを維持します。
すべての操作はこの配列に基づいています。StringBufferまたはStringBuilderに似ています。基礎となる要素は同じです。内部には拡張メカニズムとさまざまなAPIがあります。違いは、ArrayListがより包括的であり、同じオブジェクトのセットをロードできることです。以下では、拡張メカニズムと一般的に使用されるAPIおよびイテレーターの分析に焦点を当て、拡張メカニズムが存在する理由、実装方法、イテレーターの動作、および機能について説明します。
ArrayListは元々一種のコンテナとして定義されていたため、上記のように[Add]、[Delete]、[Check]、[Change]、[Judge]、[Install andReplace]などの多くの外部APIがあります。APIの分析から始めて、拡張メカニズムを徐々に紹介していきましょう。
(1)容量の増減メカニズム
クローン()
クローンメソッドは、シャローコピーを実現する呼び出された親クラスのローカルメソッドです。シャローコピーとディープコピーについては、後で研究のための特別記事を作成します。ここでは繰り返しません。ソースコードは以下に掲載されています:
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*翻译:返回这个实例的浅拷贝。元素本身内容不会被拷贝
* @return a clone of this <tt>ArrayList</tt> instance
*/
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);
}
}
add(E e)、add(int index、E element)
add(E e)メソッドの違いは要素の追加であり、追加位置は終わりです。
add(int index、E element)メソッドは要素を追加し、追加位置は添え字がリスト内のインデックスである場所です。
これらの方法はどちらも、ArrayListの内部データ構造を変更して長さを増やす必要があります。ソースコードの分析は次のとおりです。
追加(Eおよび)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! //确保足够的数组长度
elementData[size++] = e; // 将元素添加到最后一个位置
return true;
}
add(int index、E element)
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++;
}
展開メソッドensureCapacityInternal(size + 1)が上で使用されています。次に、ArrayListの展開メカニズムの分析を開始しましょう。
拡張メカニズム
拡張メカニズムの誕生は、要素の追加、つまり対応する追加メソッドから始まります。容量の長さが十分でない場合にのみ、拡張が含まれます。その他の機能[変更]、[削除]、[チェック]、[判断]、[変換]などは拡張メカニズムを含みません。
对外API手动扩容: ensureCapacity(int minCapacity)
自动扩容:ensureCapacityInternal(int minCapacity)
扩容判断: ensureExplicitCapacity(int minCapacity)
扩容核心: grow(int minCapacity) //小中容量
扩容核心: hugeCapacity(int minCapacity) //大容量
容量拡張には4つの方法があり、最後の2つは容量拡張のコア実装方法です。外側から内側に向かって、以下を分析します。
sureCapacity(int minCapacity)
このメソッドは呼び出し元に提供され、手動で拡張できます。
public void ensureCapacity(int minCapacity) {
// 计算最小扩展长度,如果是用无参构造函数,当前数组为默认空数组,那么扩展长度为10。
// 如果是有参构造或者其他,则扩展长度为0.
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) { // 手动扩容执行条件:传入参数 > 最小扩展长度时,扩容才生效
ensureExplicitCapacity(minCapacity);
}
}
インスタンスデモ
リフレクションを使用して、内部プライベートプロパティの値を表示します。
ArrayList list = new ArrayList();
Class c = list.getClass();
ArrayList list2 = (ArrayList) c.newInstance();
Field f = c.getDeclaredField("elementData");
f.setAccessible(true);
list2.add(1);
list2.ensureCapacity(100); // 手动调用扩容
Object[] element = (Object[]) f.get(list2);
System.out.println(list2.size());
System.out.println(element.length);
结果:1
100
sureCapacityInternal(int minCapacity)
このメソッドは、自動拡張と同等のArrayListによって内部的に呼び出されます。他のメソッドを拡張する必要がある場合は、このメソッドが呼び出されます。拡張ルールはそのソースコードにあります。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 如果为无参构造的默认空数组对象时进行扩容。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 最小扩容容量为10.
}
ensureExplicitCapacity(minCapacity);
}
上記のensureExplicitCapacity(int minCapacity)の方法は、コンテナーに必要な最小容量を計算することです。この条件は、拡張をトリガーするかどうかを決定することです。
// 关于modCount,ArrayList开头有一句话:
A structural modification is
* any operation that adds or deletes one or more elements, or explicitly
* resizes the backing array; merely setting the value of an element is not
* a structural modification.
* 翻译: 结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 这个变量计算当前ArrayList容器结构上被修改的次数,为什么放在扩容之前呢?
// 假如没有执行扩容,modCount也会+1,也就说明即时容器长度不变,
// 但是容器内的元素长度被改变了,如【增】【删】,modCount就会加1
// overflow-conscious code
if (minCapacity - elementData.length > 0) // 触发条件:所需最小容量 > 当前容量
grow(minCapacity);
}
grow(int minCapacity)
拡張のコア実装方法:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容倍数为原容量的1.5倍
if (newCapacity - minCapacity < 0) // 如果扩容1.5倍后还是不够当前所需最小容量
newCapacity = minCapacity; // 新容量就扩容为当前所需最小容量
/** 从上面可以看出,并不是每次扩容都为原来的1.5倍。 **/
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 如果所需容量非常大,超过了 2^31-8,就重新计算扩容容量
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity(int minCapacity)
大容量拡張計算:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? // 只有两种结果:2^31 ,和 2 ^ 31 - 8
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
[increment]関数に戻ると、主に4つのAPIがあります
。add(E e)
add(int index、E element)
addAll(Collection <?extends E> c)
addAll(int index、Collection <?extends E> c )
最初の2つは単一の追加であり、後の2つのAPIは複数の追加です。したがって、容量に対する需要の増加は、0から無限大までオールラウンドで可能です。
拡張メカニズムが明確になり、コンテナに要素を追加するための関連する方法も非常に簡単です。add()メソッドは上で紹介されました。addAll()メソッドを見てみましょう。
addAll(Collection <?extends E> c)
このメソッドは、コレクションCのすべての要素を現在のコンテナーに追加するためのものであり、追加の開始位置は終了です。
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;
}
addAll(int index、Collection <?extends E> c)
このメソッドは同じ効果があり、コレクションCのすべての要素を現在のコンテナーに追加します。増加の開始位置は、指定されたインデックス添え字です。
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;
}
(2)削除してトリミングする
追加方法と拡張メカニズムから、コンテナに要素を追加するときに、容量が不足している場合は、拡張方法を使用して容量を増やすことができることがわかります。次に、逆の操作で、要素を削除するとき、容量が大きすぎるとき、ArrayListは容量をトリミングする方法も提供します。容量がコンテナ内の要素の数よりもはるかに大きい場合、それはメモリスペースを占有しすぎて、スペースの浪費を引き起こすためです。
要素を追加して容量を拡張するメカニズムとは逆に、要素を削除して容量をプルーニングする方法を見てみましょう。
trimToSize() // 修剪容量至当前元素所占长度
clear() // 清空所有元素
remove(int index)
remove(Object o) fastRemove(int index)
removeRange(int fromIndex, int toIndex)
removeAll(Collection<?> c)
retainAll(Collection<?> c)
batchRemove(Collection<?> c, boolean complement)
trimToSize()
public void trimToSize() {
modCount++; // 容量长度的修改属于结构修改,modCount会增加
if (size < elementData.length) { // 如果小于才进行修剪容量,修剪为当前元素占有长度
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
晴れ()
public void clear() {
modCount++; // 元素的删减属于结构修改
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; // 将容器中的所有变量与堆中对象解绑,对象将被交给GC处理
size = 0;
}
remove(int index)
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; // 返回被移除的元素
}
remove(Object o)
public boolean remove(Object o) {
// 因为本容器可以存放null对象,非空对象的判断一般是用equals方法
// 而对于null对象,它不能用equals方法,肯定是要单独判断的。
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;
}
removeAll(Collection <?> c)、retainAll(Collection <?> c)和batchRemove(Collection <?> c、ブール補数)
// removeAll(Collection<?> c)方法中是调用的batchRemove(Collection<?> c, boolean complement)
// 因此下面详细分析batchRemove(Collection<?> c, boolean complement);
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
holdAll(Collection <?> c)
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
batchRemove(Collection <?> c、boolean Complement)
2つのパラメーターがあります。1つ目はコレクションインターフェイスです。つまり、List、Set、Vector、Queueなどを渡すことができ
ます。2つ目のパラメーターは、それが含まれるかどうかを示します。
一緒に、2つのパラメーターは、セットCのすべての要素を保持するか、セットCのすべての要素を削除するかを示します。
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r]; // 元素长度不变,将所需的元素,从第一个位置开始装填。
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
如果c.contains()抛出异常
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
// 装填完成后,由于装填数量 <= 容器元素初始数量,所以还要删除多余的尾部部分。
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified; // 如果元素移动后还是原来集合的样子,返回false
}
(3)変更
set()
要素の変更は構造の変更に影響を与えないため、このメソッドはmodCountを増加させません。
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue; // 返回被换掉的元素
}
(4)チェック
検索関数の場合、ArrayListは、長さ、要素、添え字の3つの次元の検索APIを提供します。ソースコードのアイデアと文字列は比較的単純なので、ここでは詳しく説明しません。
size()
isEmpty()
get(int index)
indexOf(Object o)
lastIndexOf(Object o)
(5)判断
contains(Object o)
によって内部的に呼び出されるindexOfメソッドは比較的単純です。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
(6)変換
toArray()
toArray(T[] a) // 这个方法暂时没看懂
toArray()
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
(7)イテレータ
以下は、3つの内部クラスを含む内部クラスの分析に焦点を当てています。そのうちの2つは、イテレーターとArrayListサブコレクションクラスsubListです。
迭代器: Itr
ListItr
集合: SubList
ArrayListコレクションには2つのイテレータがあり、サブコレクションの関数に類似したsubListのクラスがあります。
その中で、Itr Iteratorは、後方判断と移動の機能を備えた実装型Iteratorであり、ListItrは、ListIteratorを実装したItrのサブクラスであり、父親の能力に加えて、前方判断と移動の機能も追加します。
詳細は次のとおりです
。Itr
hasNext()
next()
remove()
ListItr
hasNext()
next()
remove()
hasPrevious()
previous()
set()
add()
上記の2つのイテレーターを比較すると、ListItrが親クラスに関数を追加して、ArrayListのさまざまな操作を満たすことがわかります。
上記の2つのイテレータでは、次の3つの変数が維持されます。
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
expectedModCount = modCount;は、呼び出し元がコレクションイテレーターを作成する必要がある場合、イテレーターが変更された回数がexpectedModCountに渡され、イテレーターがコレクションの操作能力を引き継ぎ、それ自体の変更のみを許可することを意味します。コレクションは、イテレータ(コレクション自体を含む)を変更することはできません。
程序员:“我有一个集合,现在将它交给你”
迭代器:“放心吧,交给我负责”
したがって、コレクション操作がコレクション自体のイテレーターに引き渡される場合。これで、コレクションの構造を変更するすべての操作が使用できなくなり、次のような構造の変更を伴うオブジェクト操作が使用できなくなります。
add() addAll() remove()等等,这些操作将会引起 modCount 的值改变。但是这个值得操作权力已经交给了迭代器。
したがって、イテレータを使用してトラバースする場合、外部関数がコレクションを操作していると、ConcurrentModificationExceptionがスローされます。これは、「現在の並列操作の例外」を意味します。したがって、イテレータがトラバースしているときは、外部操作は許可されません。トラバーサル中、構造を変更するすべての操作はmodCount値に基づいて判断されます。期待値と異なることが判明した場合、すぐに失敗します。
(8)subListクラス
イテレータがコレクションの繰り返しトラバーサル要件を満たしているように見える場合は、コレクションを操作すると非常に便利です。次に、SubListクラスの外観は、コレクションのスコープの欠点を補うことです。
Stringクラスのソースコードの以前の分析と同様に、内部のStringは、部分文字列に対する操作を含むさまざまな操作を含む配列です。配列が十分に長いが、必要なデータ範囲が配列全体をトラバースする必要がない場合、StringとArrayListの両方が、範囲クエリまたはトラバーサルを制限できる操作を提供する必要があります。
StringクラスはsubStringを提供し、同じsubListはArrayListのローカル操作を補う内部クラスです。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
これはarrayListコレクションのAPIであり、コレクションのサブコレクションを返します。パラメータintfromIndex、int toIndexは、インターセプトする場所を表します。
このsubListもarrayListのようにAbstractListを継承し、イテレーターを含め、ArrayListとほぼ同じ関数を実装します。
ソースコードの一部
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
...
// subList的迭代器
public Iterator<E> iterator() {
return listIterator();
}
subListの各関数は、サブコレクションの対応する操作です。トラバーサルの範囲を狭め、効率を向上させることができます。
総括する
上記はarrayListのソースコード分析です。何か問題があれば教えてください。上記の理解の一部は個人的な理解であり、参照を学習するためにのみ使用されます。ArrayListは最も一般的なコレクションの1つであり、コレクションの大規模なファミリーには多くのメンバーがいます。現在、大家族の紹介の分析を行う予定です。一般的に使用されるものは個別に分析され、あまり使用されない分類は分析されます。