Mise au point d’une méthode efficace de tri d’un vecteur en Java. Sébastien Piérard 8 juin 2007 Dans cet article, nous présentons une méthode efficace pour trier le contenu d’un vecteur, instance de la classe java.util.Vector. Typiquement, le programmeur utilisera cette structure de données comme une liste, telle que définie par l’interface java.util.List. Il lui est ainsi possible d’appeler la méthode statique sort de la classe java.util.Collections. Ce n’est malheureusement pas la bonne façon de procéder. Commençons par rappeler ce qu’est un vecteur. Il s’agit d’une structure de données dont l’accès indexé se fait en temps constant, mais dont le nombre d’éléments peut varier. Son implémentation est réalisée grâce à un tableau surdimensionné, permettant ainsi l’ajout de nouveaux éléments. Lorsque la taille du tableau devient insuffisante, un nouveau tableau est alloué et les références vers les objets contenus sont copiées depuis l’ancien tableau dans le nouveau. La classe Vector contient notamment les deux champs suivants : /** * The array buffer into which the components of the vector are stored. * Any array elements following the last element in the Vector are null. */ protected Object[] elementData ; /** * The number of valid components in this Vector object. Components * elementData[0] through elementData[elementCount-1] are the actual items. */ protected int elementCount ; 1 Voyons à présent pourquoi l’utilisation de Collections.sort est inefficace. Pour cela, observons le code-source du projet GNU Classpath 0.95 1 . Dans java.util.Collections : 2107: 2108: 2109: 2110: 2111: 2112: 2113: 2114: 2115: 2116: 2117: public static <T> void sort(List<T> l, Comparator<? super T> c) { T[] a = (T[]) l.toArray(); Arrays.sort(a, c); ListIterator<T> i = l.listIterator(); for (int pos = 0, alen = a.length; pos < alen; pos++) { i.next(); i.set(a[pos]); } } Dans java.util.Vector : 179: 180: 181: 182: public synchronized void copyInto(Object[] a) { System.arraycopy(elementData, 0, a, 0, elementCount); } 550: 551: 552: 553: 554: 555: public synchronized Object[] toArray() { Object[] newArray = new Object[elementCount]; copyInto(newArray); return newArray; } Autrement dit, la méthode naı̈ve a deux sources d’inefficacité. Premièrement, elle nécessite de recopier le tableau interne du vecteur. Cela est nécessaire car la méthode proposée pour trier est générale, et ne peut donc pas tirer parti du fait qu’un vecteur utilise un tableau en interne. Deuxièmement, le parcours de la structure pour y replacer les éléments triés est lourde de par les nombreux appels de fonctions nécessaires. La méthode de tri mise au point tire parti du fait que la classe Vector n’est pas déclarée finale, et que le tableau interne est seulement protected. Nous créons donc une nouvelle classe, SortableVector, qui hérite de Vector et y ajoute les fonctions de tri. Nous pouvons ainsi passer le tableau interne directement à Arrays.sort. De plus, cette façon de procéder permet d’ajouter des fonctions permettant de trier une partie du contenu du vecteur, ce qui n’est pas possible via la classe Collections. Voici la solution proposée : 1 http ://developer.classpath.org/doc/ 2 import import import import java.util.Arrays ; java.util.Collection ; java.util.Comparator ; java.util.Vector ; public class SortableVector <E> extends Vector <E> { public SortableVector () { super () ; } public SortableVector ( Collection <? extends E> c ) { super ( c ) ; } public SortableVector ( int initialCapacity ) { super ( initialCapacity ) ; } public SortableVector ( int initialCapacity , int capacityIncrement ) { super ( initialCapacity , capacityIncrement ) ; } public void sort () { Arrays.sort ( elementData , 0 , elementCount ) ; } public void sort ( Comparator <? super E> c ) { Arrays.<E>sort ( ( E [] ) elementData , 0 , elementCount , c ) ; } public void sort ( int fromIndex , int toIndex ) { checkIndex ( fromIndex ) ; checkIndex ( toIndex ) ; Arrays.sort ( elementData , fromIndex , toIndex ) ; } public void sort ( int fromIndex , int toIndex , Comparator <? super E> c ) { checkIndex ( fromIndex ) ; checkIndex ( toIndex ) ; Arrays.<E>sort ( ( E [] ) elementData , fromIndex , toIndex , c ) ; } private void checkIndex ( int index ) { if ( index < 0 || index >= elementCount ) throw new ArrayIndexOutOfBoundsException () ; } } 3 Il convient à présent de mesurer le temps requis pour effectuer un tri, et ce pour diverses quantités de données. Pour ce faire, nous mesurons le temps nécessaire à la création, au remplissage et au tri des structures de données. Nous moyennons ces temps sur une vingtaine de mesures. Nous répétons ensuite la procédure en désactivant la commande de tri, pour avoir le temps réellement consommé par la fonction de tri. Le graphique suivant illustre les résultats obtenus. 3000 temps ( millisecondes ) 2500 2000 1500 1000 500 avec SortableVector avec Collections.sort avec Collections.sort sans toArray () 0 0 1 2 3 4 5 6 nombre d’elements a trier 7 8 9 10 5 x 10 Nous en concluons que la méthode décrite est de loin préférable à l’utilisation de la méthode disponible. Il est ainsi possible de gagner jusqu’à 40% du temps d’exécution. Sur le graphique, nous avons également représenté en noir le temps requis par Collections.sort auquel on a soustrait le temps nécessaire à toArray (). On peut ainsi identifier plus présisément la source d’inefficacité de Collections.sort : elle ne vient qu’en faible partie du recopiage du tableau, sa source principale étant la façon de replacer les éléments triés dans la structure de données. L’inconvénient principal de la méthode proposée est l’obligation à utiliser la classe SortableVector. Ainsi, il est impossible de trier les éléments d’une structure déclarée comme Vector ou toute sous classe autre que SortableVector, par exemple Stack. Une solution alternative serait de définir SortableVector dans le package java.util et de transformer les méthodes pour qu’elles prennent en argument le vecteur sur lequel agir. Enfin, notons que l’implémentation de java.util.ArrayList est similaire, du moins dans le projet GNU Classpath. Cependant, les champs utiles étant déclarés privés, il n’y a pas moyen de s’en servir. 4