3. Analyse du code source ArrayList

1. Structure des données La structure des données de
Insérez la description de l'image ici
ArrayList est un tableau. Comme le montre la figure ci-dessus, la figure montre un tableau d'une longueur de 10, en comptant à partir de 1, un index indiquant l'indice du tableau, en partant de 0, elementData indiquant les éléments du tableau . De plus, il existe trois concepts de base dans le code source.
Code source

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    //该值用于定义数组第一次扩容时的大小
    private static final int DEFAULT_CAPACITY = 10;

    transient Object[] elementData;

    //该值用于定义当前数组的大小
    private int size;
}

Analyse du code source

  1. DEFAULT_CAPACITY représente la taille du tableau lors de son premier développement. Cette valeur est souvent mentionnée lors des entretiens et également lors de l'initialisation.
  2. size représente la taille du tableau actuel, le type est int et volatile n'est pas modifié, ce qui n'est pas thread-safe.
  3. modCount représente le nombre de révisions du tableau actuel. Si la structure du tableau change, il sera +1.

2. L'initialisation d'
ArrayList fournit trois méthodes d'initialisation, l'initialisation directe sans paramètres, l'initialisation avec la taille spécifiée et l'initialisation avec les données initiales spécifiées. Le code source est le suivant.
Code source

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

    //无参数直接初始化,数组大小为空
    public ArrayList() {
    
    
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
   
    //指定大小初始化
    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(Collection<? extends E> c) {
    
    
        //该值用于保存数组的容器,默认为null
        elementData = c.toArray();
        //如果给定的集合c有值
        if ((size = elementData.length) != 0) {
    
    
            //如果集合元素类型不是Object类型,则转成Object类型
            if (elementData.getClass() != Object[].class) {
    
    
                elementData = Arrays.copyOf(elementData, size, Object[].class);
            }
        } else {
    
    
            //给定的集合c无值
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
}

Analyse du code source

  1. Lorsque le constructeur sans paramètre ArrayList est initialisé, la taille par défaut est un tableau vide, qui n'est pas le 10 habituel, qui est la valeur du tableau développée lorsque l'élément est ajouté pour la première fois.
  2. Lors de la spécification de l'initialisation des données, vous pouvez voir que le commentaire contient un tel commentaire "c.toArray pourrait (incorrectement) ne pas retourner Object [] (voir 6260652)". Il s'agit d'un bogue dans le code source, ce qui signifie que lorsqu'un élément d'une collection donnée n'est pas de type Object, il sera converti en type Object. Dans des circonstances normales, ce bogue ne sera pas déclenché. Il ne sera déclenché que dans les scénarios suivants: Une fois ArrayList initialisé (l'élément ArrayList n'est pas de type Object), la méthode toArray est appelée à nouveau pour obtenir le tableau Object, puis elle sera déclenchée lorsque le tableau Object est assigné. Le bogue, le code de cas spécifique et la capture d'écran du résultat en cours d'exécution sont les suivants.
public class App {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = Arrays.asList("hello world");
        Object[] objArray = list.toArray();
        //打印的值为String[]
        System.out.println(objArray.getClass().getSimpleName());
         //抛出ArrayStoreException异常
        objArray[0] = new Object();
    }
}

Insérez la description de l'image ici

3. Nouvel ajout et développement
L'ajout consiste à ajouter des éléments au tableau, qui est principalement divisé en deux étapes. La première consiste à déterminer si une expansion est nécessaire et, si nécessaire, à effectuer des opérations d'expansion, et l'autre à attribuer directement des valeurs. Le code source spécifique est le suivant.
Code source

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    public boolean add(E e) {
    
    
        //确保数组大小是否足够,不够执行扩容,size为当前数组的大小
        ensureCapacityInternal(size + 1);
        //直接赋值,这里是线程不安全的
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
    
    
        //如果初始化数组大小时,有给定初始值,以给定的大小为准,不走if逻辑
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //确保容积足够
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
    
    
        //记录数组被修改
        modCount++;
        //如果期望的最小容量大于目前数组的长度,即扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    //扩容,并把现有数据拷贝到新的数组里
    private void grow(int minCapacity) {
    
    
        int oldCapacity = elementData.length;
        //oldCapacity >> 1是把oldCapacity除以2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的值小于期望值,扩容后的值就等于期望值
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果扩容后的值大于jvm所能分配的数组的最大值,那么就用Integer的最大值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //通过复制进行扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

Analyse du code source Une fois l'
expansion terminée, ajoutez des éléments directement au tableau via elementData [size ++] = e. C'est grâce à cette simple affectation qu'il n'y a pas de contrôle de verrouillage, donc l'opération ici est thread-unsafe.

Pour résumer

  1. La règle de l'expansion est de ne pas doubler, c'est la capacité d'origine plus la moitié de la capacité. Pour dire les choses franchement, la capacité étendue est de 1,5 fois la capacité d'origine.
  2. La valeur maximale du tableau dans ArrayList est Integer.MAX_VALUE. Si cette valeur est dépassée, la machine virtuelle Java n'allouera pas d'espace mémoire pour la matrice.
  3. Lorsqu'il a été ajouté, la valeur n'était pas strictement vérifiée, donc ArrayList autorise les valeurs nulles.

4. Itérateur
Pour implémenter un itérateur, il vous suffit d'implémenter la classe java.util.Iterator. L'itérateur a trois paramètres et méthodes importants. Le code source est le suivant.
Code source

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    //迭代过程中,下一个元素的位置,默认从0开始
    int cursor;
    /**
     * 新增场景表示上一次迭代过程中,索引的位置
     * 删除场景为-1
     */
    int lastRet = -1;

    /**
     * expectedModCount表示迭代过程中,期望的版本号
     * modCount表示数组实际的版本号
     */
    int expectedModCount = modCount;

    public boolean hasNext() {
    
    
        //cursor表示下一个元素的位置,size表示实际大小,如果两者相等,说明没有可以迭代的元素,如果不等,说明还有元素需要迭代
        return cursor != size;
    }

    public E next() {
    
    
        //迭代过程中,判断版本号是否被修改,若被修改,抛出ConcurrentModificationException异常
        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];
    }

    //版本号比较
    final void checkForComodification() {
    
    
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

    public void remove() {
    
    
        //如果上一次操作时,数组的位置已经小于0,说明数组已经被删除完
        if (lastRet < 0)
            throw new IllegalStateException();
        //迭代过程中,判断版本号是否被修改,若被修改,抛出ConcurrentModificationException异常
        checkForComodification();

        try {
    
    
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            //-1表示元素已经被删除,这里也有防止重复删除的作用
            lastRet = -1;
            //删除元素时modCount的值已经发生变化,在此赋值给expectedModCount,下次迭代时,两者的值便是一致的
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
    
    
            throw new ConcurrentModificationException();
        }
    }
}

Analyse du code source

  1. Comme vous pouvez le voir à partir du code source, la méthode suivante accomplit deux choses: la première consiste à vérifier si l'itération peut se poursuivre, et la seconde consiste à trouver la valeur de l'itération et à préparer l'itération suivante (curseur + 1).
  2. Le but de lastRet = -1 est d'empêcher la suppression en double.
  3. Si l'élément est supprimé avec succès, le modCount actuel du tableau changera, et le expectedModCount sera réaffecté ici, et les valeurs des deux seront les mêmes lors de la prochaine itération.

Je suppose que tu aimes

Origine blog.csdn.net/Jgx1214/article/details/109064315
conseillé
Classement