Mise au point d`une méthode efficace de tri d`un vecteur en Java.

publicité
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
Téléchargement