Analyse du principe ArrayList

Poser des questions

  1. Basé sur quoi? Array? Liste liée? queue?
  2. Pourquoi pouvez-vous conserver l' addélément?

une analyse

Méthode pour réaliser

Variables définies:

Un tableau est maintenu:

transient Object[] elementData; // non-private to simplify nested class access

private int size;

ArrayListTout fonctionne en interne add、remove、set、getsur elementDatace tableau, l' ArrayListimplémentation est donc basée sur le tableau.

Deux longueurs: size====> La longueur de la liste courante elementData.length====> Longueur du
tableau Longueur du tableau ≥ Longueur de la liste

La longueur par défaut et deux tableaux par défaut:

/**
 * 默认的数组长度,当我们直接创建一个ArrayList对象时,容量为10
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 直接创建无参ArrayList时,内部指向该数组
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 创建带初始长度的ArrayList,或者传入另一个列表为参数创建对象时,如果长度为0或者传入列表长度为0,内部指向该数组
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

Méthode de construction

ArrayListTrois méthodes de construction sont définies. Ce sont des constructions sans paramètre. Une intméthode de construction de paramètre de type est transmise et une liste est transmise comme méthode de construction de paramètre.

  1. Passer la longueur initiale. Cette méthode est généralement utilisée lorsque l'on connaît la longueur de la liste pour éviter d'appliquer trop d'espace mémoire inutile
public ArrayList(int initialCapacity) {
    //如果长度大于0,则创建该长度的数组
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    //长度为0,指向默认数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
  1. Aucune construction de paramètre, elementDatapointera vers le tableau par défaut, longueur 0
  2. Passez une liste. Arrays.copyOf()Copiez la liste transmise dans le nouveau tableau par méthode, puis elementDatapointez sur l'adresse

Méthode d'opération

L'implémentation interne de chaque méthode est une opération sur le tableau

get

Récupère directement la valeur de l'indice correspondant du tableau

public E get(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    return (E) elementData[index];
}

set

La même opération sur le tableau, remplacez la valeur correspondant à l'indice du tableau par la nouvelle valeur

public E set(int index, E element) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    E oldValue = (E) elementData[index];
    elementData[index] = element;
    return oldValue;
}

add

L'indice est actuel size+1et la valeur à ajouter est définie sur la valeur correspondant à l'indice dans le tableau. Parce que la longueur du tableau est fixe, cela implique la stratégie d'expansion et de croissance la plus importante, la ensureCapacityInternal(size + 1)méthode

public boolean add(E e) {
    //扩容算法,传入当前长度+1
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //扩容完成后赋值
    elementData[size++] = e;
    return true;
}

addAll

Méthode, convertissez d'abord le paramètre de liste en tableau, puis utilisez la méthode System.arraycopy pour y copier le tableau elementData, ce qui implique également la stratégie d'expansion et de croissance, mais les paramètres entrants sont différents .

public boolean addAll(Collection<? extends E> c) {
    //先转为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    //扩容算法,传入当前长度+需要add的元素的数量
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //拷贝数组
    System.arraycopy(a, 0, elementData, size, numNew);
    //列表长度修改
    size += numNew;
    return numNew != 0;
}

remove

La même opération de copie est effectuée sur le tableau, ce qui est similaire au déplacement de chaque élément vers l'avant d'un bit après l'élément à supprimer dans le tableau, en écrasant la valeur d'origine.

public E remove(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    modCount++;
    E oldValue = (E) 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;
}

Expansion

Avant addet addAllméthodes, il existe ensureCapacityInternal1une méthode pour les algorithmes d'expansion qui
examinent l'expansion de code pertinente:

//①传入列表长度值
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);
}

//③对数组进行扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    //获取当前长度
    int oldCapacity = elementData.length;
    //计算新长度为旧长度 * 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //如果新长度小于需要的长度,使用传入的长度,适用于第一次创建后添加元素的扩容和addAll方法元素很多超过原有1.5倍的情况下
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果计算后长度大于最大列表长度
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //通过copyOf方法将原有数据拷贝到一个新的数组对象中,再赋值给elementData,至此,扩容完成。
    elementData = Arrays.copyOf(elementData, newCapacity);
}

une analyse:

  1. Nous passons d'abord une valeur, qui représente la longueur de notre liste après add ou addAll
  2. Vous ne pouvez pas étendre le tableau à chaque fois que vous ajoutez , sinon le coût sera trop élevé, de sorte que l'extension du tableau n'augmentera pas seulement la longueur de un à chaque fois.
  3. S'il s'agit actuellement d'un tableau vide, prenez le maximum de la valeur par défaut et de la valeur passée. Afin de créer un tableau de longueur 10 directement pour la première fois, sinon ajoutez un élément à développer une fois, ce qui est coûteux
  4. Calculez si une expansion est nécessaire
  5. Fonctionnement d'extension, dans des circonstances normales, la capacité de la baie est multipliée par 1,5 , deux cas particuliers:
    1. Ajouter un élément pour la première fois, étendre directement à la longueur par défaut de 10
    2. Il y a beaucoup d'éléments addAll, et la longueur totale est plus de 1,5 fois la longueur d'origine, étendre directement à cette longueur
  6. Copiez toutes les données d'origine dans le nouveau tableau développé via la méthode de copie de tableau, et affectez enfin le tableau à elementData
  7. Expansion terminée

Pour résumer

  1. ArrayList est implémenté sur la base d'un tableau, la longueur par défaut du tableau est de 10, toutes les opérations sont des opérations sur le tableau maintenu
  2. Les méthodes add et addAll peuvent déclencher l'expansion de la baie
  3. Lorsque la longueur n'est pas suffisante, la longueur d'expansion est généralement 1,5 fois la longueur existante
  4. L'expansion, la suppression, etc. sont toutes réalisées en copiant le tableau, donc la liste ne doit pas être trop longue, sinon chaque copie consommera trop de performances!

Je suppose que tu aimes

Origine blog.csdn.net/lizebin_bin/article/details/88876878
conseillé
Classement