1. 線形テーブルの概念の紹介
線形テーブルは、順番に配置された一連の要素で構成されるデータ セットです。
線形テーブルには 2 つのストレージ構造があります。
- シーケンシャルストレージ構造: メモリに保存されるデータは連続的です。たとえば、配列。
- リンクされたストレージ構造: メモリに保存されるデータは次のように不連続です。リンクされたリスト。
線形テーブルでは
- 最初の要素を除き、各要素には一意の。
- 最後の要素を除く各要素には、一意の後継要素があります。
すべての要素は線形構造を形成します。
2. シーケンシャルストレージ構造 - シーケンステーブルリスト
シーケンシャルなストレージ構造はシーケンス テーブル List です。
シーケンシャルストレージ構造:
- 連続したメモリ:順次ストレージ構造は、メモリ内の線形テーブル内の要素を格納します。
- インデックス アクセス:シーケンシャル ストレージ構造では、データ要素がメモリ内の連続アドレス空間に特定の順序で順次格納され、インデックスを介して要素にアクセスできます。インデックスはメモリアドレスです。
- シーケンシャルストレージ構造(シーケンステーブル)の例:
- 配列
- ArrayListも配列によって内部的に実装されます。
シーケンス テーブルの利点:
- ランダム アクセス:インデックス添字を使用すると、メモリ内の指定された場所にある要素に直接アクセスでき、時間計算量は O(1) です。
- 高いストレージ効率: 他の要素ストレージへのポインターを定義するために追加のスペースを消費する必要はなく、要素自体のストレージ スペースのみが必要です。
シーケンス テーブルの欠点:
- 挿入と削除は非効率的です。シーケンシャル ストレージ構造では、挿入と削除の操作はすべての要素を全体として移動する必要があり、時間計算量は O(n) です。
- 固定記憶域: 配列の作成時に固定サイズを指定する必要があり、作成後にサイズを変更することはできません。
シーケンス テーブルのコード例:シーケンス テーブルは配列に直接格納されます。
class Students {
Student[20];
int size;
}
3. シーケンステーブルArrayListのソースコード解析
Java の ArrayList はシーケンス テーブルです。そのソース コードを以下で分析します。
ArrayList ソース コード アドレス: https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/util/ArrayList.java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始容量。
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例。
*/
private static final Object[] EMPTY_ELEMENTDATA = {
};
/**
* 用于默认大小的空实例的共享空数组实例。我们
* 将其与EMPTY_ELEMENTDATA区分开来,以了解何时膨胀多少
* 添加第一个元素。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
};
/**
* 存储ArrayList元素的数组缓冲区。
* ArrayList的容量就是这个数组缓冲区的长度。任何
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* 将在添加第一个元素时扩展为DEFAULT_CAPACITY。
*/
transient Object[] elementData; // 非私有以简化嵌套类访问
/**
* 数组列表的大小(包含的元素数量)。
*
* @serial
*/
private int size;
}
ArrayList のデフォルトの初期容量は ですint DEFAULT_CAPACITY = 10
。パラメータなしでコンストラクターが呼び出された場合、デフォルトの初期容量は 10 です。
/**
* 构造一个初始容量为10的空列表。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
ArrayList # add メソッドでは、配列のサイズが十分かどうかを処理するためにensureCapacityInternal
関数。
elementData[size++] = e
コードを呼び出して要素 e を配列の末尾に追加します。
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal 関数では、ensureExplicitCapacity 関数が呼び出されます。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity関数では、 ArrayList の容量を増やす関数であるGroove 関数が呼び出されます。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
give 関数では、新しい配列が作成され、元の配列のデータが新しい配列にコピーされます。
/**
* 增加容量以确保它至少可以容纳最小容量参数指定的元素数量。
*
* @param minCapacity 所需的最小容量
*/
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 です。
指定した位置に要素を挿入する場合は、まず現在の配列の最初のインデックス要素をコピーし、次に挿入する要素をコピーし、最後に元の配列のインデックスの後の要素をコピーする必要があります。
コピーが2枚必要となり、操作手順が非常に面倒です。
/**
* 将指定元素插入此列表中的指定位置。
* 将当前在该位置的元素(如果有的话)和任何后续元素向右移动(在它们的索引上加1)。
*
* @param index 要插入指定元素的索引
* @param element 要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}
次のコードの機能は、elementData ソース配列 (インデックス位置から始まる要素) を、 elementData 宛先配列のインデックス + 1 位置にコピーし、size - インデックス要素をコピーすることです。これは、要素を から始まる要素を移動するのと同じです。 elementData 配列の位置の逆方向のインデックス。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
System.arraycopy 関数のプロトタイプ:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
ArrayList #remove 関数を呼び出して要素を削除します。つまり、インデックスの後の要素を 1 位置前に移動します。
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;
}
次のコードの機能は、elementData ソース配列(インデックス + 1 から始まる要素) をelementData 宛先配列のインデックス位置にコピーし、numMoved 要素をコピーすることです。これは、elementData のインデックスから始まる要素を移動するのと同じです。配列を 1 つ前に配置します。
System.arraycopy(elementData, index+1, elementData, index,
numMoved);