4
votes

Boucle For à la place tandis que boucle dans les collections

J'ai une question sur Collections.class et la méthode "copy".

1) Pourquoi vérifions-nous la taille d'une liste source en secondes si elle est conditionnelle au code ci-dessous et pourquoi elle doit être inférieure à 10? Pourquoi est-ce si important?

2) De plus, pourquoi nous utilisons la boucle for dans ce conditionnel à la place alors que - while (hasNext ())

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size()) {
        throw new IndexOutOfBoundsException("Source does not fit in dest");
    } else {
        if (srcSize < 10 || src instanceof RandomAccess && dest instanceof RandomAccess) {
            for (int i = 0; i < srcSize; ++i) {
                dest.set(i, src.get(i));
            }
        } else {
            ListIterator<? super T> di = dest.listIterator();
            ListIterator<? extends T> si = src.listIterator();

            for (int i = 0; i < srcSize; ++i) {
                di.next();
                di.set(si.next());
            }
        }
    }
}

Pourquoi nous utilisons


0 commentaires

3 Réponses :


5
votes

1) 10 est une constante qui représente une limite entre les petites listes et les grandes listes. Si une Liste ne prend pas en charge l'accès aléatoire (ce qui signifie qu'elle ne prend pas en charge le temps O (1) pour list.get (i) ), get (i) peut être coûteux, vous ne voulez donc l'utiliser que si la liste est petite. LinkedList est un exemple de List qui ne prend pas en charge l'accès aléatoire.

2) Les boucles pour et while sont possibles, mais lorsque les List prennent en charge l'accès aléatoire (ou sont suffisamment petits), peut être plus efficace d'utiliser get et set au lieu de créer des itérateurs.


2 commentaires

2) Pourquoi avez-vous besoin d'un comptoir? Épuisez simplement l'itérateur, et vous avez fait le même nombre de get / sets


@AndyTurner j'ai changé # 2. OP demandait sur une boucle for différente de celle à laquelle j'avais initialement répondu.



3
votes

Le commenter au-dessus de certaines des constantes donne un bon aperçu ici

De nombreux algorithmes de liste ont deux implémentations, dont l'une est appropriée pour RandomAccess listes, l'autre pour «séquentiel». Souvent, la variante d'accès aléatoire donne de meilleures performances sur de petites listes d'accès séquentielles. le les paramètres de réglage ci-dessous déterminent le point de coupure pour quoi constitue une "petite" liste d'accès séquentielle pour chaque algorithme. Le les valeurs ci-dessous ont été empiriquement déterminées pour bien fonctionner pour LinkedList . J'espère qu'ils devraient être raisonnables pour d'autres accès séquentiels List implémentations. Ceux qui effectuent des travaux de performance sur ce code feraient bien pour valider les valeurs de ces paramètres de temps en temps.

Le peu que j'ai mis en gras est particulièrement pertinent, je pense. Comme le seuil n'est pas arbitraire, ils y sont parvenus via une série de mesures et d'observations, comme toutes les bonnes optimisations de performances devraient l'être. Ce qui est considéré comme une «petite» liste diffère également en fonction du contexte.


2 commentaires

10 est arbitraire, en ce sens qu'il est arbitrairement réglé pour LinkedList (par opposition à toute autre classe / s), et mesuré à un moment non spécifié dans des conditions non spécifiées.


@AndyTurner 'arbitraire' est une décision prise sans raison. LinkedList est probablement l'implémentation de liste chaînée la plus fréquemment utilisée, donc l'optimisation en fonction de cela est un choix raisonnable.



1
votes

2) De plus, pourquoi nous utilisons la boucle for dans ce conditionnel à la place alors que - while (hasNext ())

Je suppose que cette question se réfère à la dernière boucle for dans le code cité, celle qui boucle sur les itérateurs:

            Iterator<T> it = src.iterator();
            while (it.hasNext()) {
                T t = it.next();
                // process t
            }

La plupart des boucles d'itérateur conventionnelles semblent quelque chose comme ceci:

        ListIterator<? super T> di = dest.listIterator();
        ListIterator<? extends T> si = src.listIterator();

        for (int i = 0; i < srcSize; ++i) {
            di.next();
            di.set(si.next());
        }

Ce code ne fait pas cela. En effet, il n'appelle pas du tout hasNext () sur l'un ou l'autre des itérateurs, ce qui est assez inhabituel. (Les itérateurs sont des instances de ListIterator au lieu de Iterator en raison de la nécessité d'appeler la méthode set () . Utilisation d'un ListIterator n'est pas pertinent pour la question du contrôle de boucle, qui s'applique également à Iterator .)

La raison pour laquelle hasNext () n'est pas t appelé dans la boucle est que le code connaît déjà les tailles de la collection. Il connaît le nombre d'éléments à copier, qui est srcSize . Il a déjà vérifié la taille de la destination pour s'assurer qu'elle est suffisamment grande pour recevoir tous les éléments. Ainsi, la boucle peut appeler si.next () et di.next () jusqu'à srcSize fois sans avoir à appeler aucun méthodes hasNext () . La seule façon dont un appel next () pourrait échouer est si l'un des itérateurs de la collection est implémenté de manière incorrecte.

(La boucle peut également échouer si l'une des listes change de taille simultanément . Hmmm. Ce code suppose clairement qu'aucune des deux listes ne change pendant l'itération.)

En supposant que les tailles des listes ne changent pas, il n'y a aucune raison d'appeler un hasNext () méthodes. Puisque le nombre d'éléments à copier est connu, une boucle comptée pour peut être utilisée, et elle est plus efficace car elle évite tout appel de méthode dans le cadre de la logique de contrôle de boucle. Autrement dit, i et ++ i sont compilés en ligne par rapport à quelque chose comme while (si.hasNext ()) lequel des cours nécessite un appel de méthode.


2 commentaires

En ce qui concerne la modification de la taille des listes pendant l'itération, il est probable (bien que non garanti) qu'un tel changement lèverait une ConcurrentModificationException si la liste source est un java.util.ArrayList ou java.util .LinkedList. Mais il semble qu'il pourrait être utile que la méthode de copie elle-même lève une ConcurrentModificationException si elle détecte des changements structurels dans la collection sous-jacente.


@MattLeidholm Correct pour les implémentations de base de List comme ArrayList et LinkedList. L'une des listes pourrait également être une liste de sécurité simultanée qui pourrait être manipulée par un autre thread; ses itérateurs seraient probablement faiblement cohérents au lieu de fail-fast (lançant ConcurrentModificationException). Dans ce cas, la boucle peut déclencher accidentellement IndexOutOfBoundsException ou NoSuchElementException. Je suppose que cette méthode pourrait être enveloppée dans un bloc try-catch qui intercepte ces exceptions et relance ConcurrentModificationException, mais cela ne semble pas ajouter beaucoup de valeur.