t
t
t
t
t t   9) Stockage des objets
tttt
t
carrea) Préface carreb) Avant-propos carre1) Introduction sur les &laqo; objets » carre2) Tout est &laqo; objet » carre3) Contrôle du flux du programme carre4) Initialization & Cleanup carre5) Cacher l'implémentation carre6) Réutiliser les classes carre7) Polymorphisme carre8) Interfaces & classes internes 9) Stockage des objets carre10) Error Handling with Exceptions carre11) Le système d’E/S de Java carre12) Identification dynamique de type carre13) Création de fenêtres & d'Applets carre14) Les &laqo; Threads » multiples carre15) Informatique distribuée carreA) Passage et retour d'objets carreB) L'Interface Java Natif (JNI) carreC) Conseils pour une programation stylée en Java carreD) Resources
Texte original t Traducteur : Jérome Quelin
t
t
///
Ce chapitre contient 14 pages
1 2 3 4 5 6 7 8 9 10 11 12 13 14
\\\
t t t
t t t
t
t t t

Choosing between Maps

t

Choisir entre les Maps

t t t
When choosing between implementations of Map, the size of the Map is what most strongly affects performance, and the following test program gives an indication of this trade-off:
t Lorsqu'on doit choisir entre les différentes implémentations d'une Map, sa taille est le critère qui affecte le plus les performances, et le programme de test suivant donne une indication des compromis :
t t t
//: c09:MapPerformance.java // Demonstrates performance differences in Maps. import java.util.*; import com.bruceeckel.util.*; public class MapPerformance { private abstract static class Tester { String name; Tester(String name) { this.name = name; } abstract void test(Map m, int size, int reps); } private static Tester[] tests = { new Tester("put") { void test(Map m, int size, int reps) { for(int i = 0; i < reps; i++) { m.clear(); Collections2.fill(m, Collections2.geography.reset(), size); } } }, new Tester("get") { void test(Map m, int size, int reps) { for(int i = 0; i < reps; i++) for(int j = 0; j < size; j++) m.get(Integer.toString(j)); } }, new Tester("iteration") { void test(Map m, int size, int reps) { for(int i = 0; i < reps * 10; i++) { Iterator it = m.entrySet().iterator(); while(it.hasNext()) it.next(); } } }, }; public static void test(Map m, int size, int reps) { System.out.println("Testing " + m.getClass().getName() + " size " + size); Collections2.fill(m, Collections2.geography.reset(), size); for(int i = 0; i < tests.length; i++) { System.out.print(tests[i].name); long t1 = System.currentTimeMillis(); tests[i].test(m, size, reps); long t2 = System.currentTimeMillis(); System.out.println(": " + ((double)(t2 - t1)/(double)size)); } } public static void main(String[] args) { int reps = 50000; // Or, choose the number of repetitions // via the command line: if(args.length > 0) reps = Integer.parseInt(args[0]); // Small: test(new TreeMap(), 10, reps); test(new HashMap(), 10, reps); test(new Hashtable(), 10, reps); // Medium: test(new TreeMap(), 100, reps); test(new HashMap(), 100, reps); test(new Hashtable(), 100, reps); // Large: test(new TreeMap(), 1000, reps); test(new HashMap(), 1000, reps); test(new Hashtable(), 1000, reps); } } ///:~ t
//: c09:MapPerformance.java
// Illustre les différences de performance entre les Maps.
import java.util.*;
import com.bruceeckel.util.*;

public class MapPerformance {
  private abstract static class Tester {
    String name;
    Tester(String name) { this.name = name; }
    abstract void test(Map m, int size, int reps);
  }
  private static Tester[] tests = {
    new Tester("put") {
      void test(Map m, int size, int reps) {
        for(int i = 0; i < reps; i++) {
          m.clear();
          Collections2.fill(m,
            Collections2.geography.reset(), size);
        }
      }
    },
    new Tester("get") {
      void test(Map m, int size, int reps) {
        for(int i = 0; i < reps; i++)
          for(int j = 0; j < size; j++)
            m.get(Integer.toString(j));
      }
    },
    new Tester("iteration") {
      void test(Map m, int size, int reps) {
        for(int i = 0; i < reps * 10; i++) {
          Iterator it = m.entrySet().iterator();
          while(it.hasNext())
            it.next();
        }
      }
    },
  };
  public static void
  test(Map m, int size, int reps) {
    System.out.println("Testing " +
      m.getClass().getName() + " size " + size);
    Collections2.fill(m,
      Collections2.geography.reset(), size);
    for(int i = 0; i < tests.length; i++) {
      System.out.print(tests[i].name);
      long t1 = System.currentTimeMillis();
      tests[i].test(m, size, reps);
      long t2 = System.currentTimeMillis();
      System.out.println(": " +
        ((double)(t2 - t1)/(double)size));
    }
  }
  public static void main(String[] args) {
    int reps = 50000;
    // Le nombre de répétitions peut être spécifié
    // via la ligne de commande :
    if(args.length > 0)
      reps = Integer.parseInt(args[0]);
    // Petit :
    test(new TreeMap(), 10, reps);
    test(new HashMap(), 10, reps);
    test(new Hashtable(), 10, reps);
    // Moyen :
    test(new TreeMap(), 100, reps);
    test(new HashMap(), 100, reps);
    test(new Hashtable(), 100, reps);
    // Gros :
    test(new TreeMap(), 1000, reps);
    test(new HashMap(), 1000, reps);
    test(new Hashtable(), 1000, reps);
  }
} ///:~
t t t
Because the size of the map is the issue, you’ll see that the timing tests divide the time by the size to normalize each measurement. Here is one set of results. (Yours will probably be different.)
t Parce que la taille du dictionnaire constitue le facteur principal, les tests de chronométrage divisent le temps par la taille du dictionnaire pour normaliser chaque mesure. Voici un ensemble de résultats (les vôtres différeront probablement) :
t t t
Type
Test size
Put
Get
Iteration

10
143.0
110.0
186.0
TreeMap
100
201.1
188.4
280.1

1000
222.8
205.2
40.7

10
66.0
83.0
197.0
HashMap
100
80.7
135.7
278.5

1000
48.2
105.7
41.4

10
61.0
93.0
302.0
Hashtable
100
90.6
143.3
329.0

1000
54.1
110.95
47.3
t
Type  Test size Put Get Iteration
10 143.0 110.0 186.0
TreeMap  100 201.1 188.4 280.1
1000 222.8 205.2 40.7
  10 66.0 83.0 197.0
HashMap 100 80.7 135.7 278.5
1000 48.2 105.7 41.4
  10 61.0 93.0 302.0
Hashtable 100 90.6 143.3 329.0
1000 54.1 110.95 47.3
t t t
As you might expect, Hashtable performance is roughly equivalent to HashMap. (You can also see that HashMap is generally a bit faster. HashMap is intended to replace Hashtable.) The TreeMap is generally slower than the HashMap, so why would you use it? So you could use it not as a Map, but as a way to create an ordered list. The behavior of a tree is such that it’s always in order and doesn’t have to be specially sorted. Once you fill a TreeMap, you can call keySet( ) to get a Set view of the keys, then toArray( ) to produce an array of those keys. You can then use the static method Arrays.binarySearch( ) (discussed later) to rapidly find objects in your sorted array. Of course, you would probably only do this if, for some reason, the behavior of a HashMap was unacceptable, since HashMap is designed to rapidly find things. Also, you can easily create a HashMap from a TreeMap with a single object creation In the end, when you’re using a Map your first choice should be HashMap, and only if you need a constantly sorted Map will you need TreeMap.
t Comme on pouvait s'y attendre, les performances d'une HashTable sont à peu près équivalentes à celles d'un HashMap (bien que ceux-ci soient généralement un petit peu plus rapides). Les TreeMaps étant généralement plus lents que les HashMaps, pourquoi voudrait-on les utiliser ? En fait, on les utilise non comme des Maps mais comme une façon de créer une liste ordonnée. Le comportement d'un arbre est tel qu'il est toujours ordonné et n'a pas besoin d'être spécifiquement trié. Une fois un TreeMap rempli, il est possible d'appeler keySet() pour récupérer un Set des clefs, puis toArray() pour produire un tableau de ces clefs. On peut alors utiliser la méthode static Arrays.binarySearch() (que nous étudierons plus loin) pour trouver rapidement des objets dans ce tableau trié. Bien sûr, on ne ferait ceci que si, pour une raison ou une autre, le comportement d'un HashMap ne convenait pas, puisqu'un HashMap est conçu justement pour retrouver rapidement des objets. De plus, on peut facilement créer un HashMap à partir d'un TreeMap avec une simple création d'objet. Pour résumer, votre premier réflexe si vous voulez utiliser une Map devrait être de se tourner vers un HashMap, et n'utiliser un TreeMap que si vous avez besoin d'une Map constamment triée.
t t t

Sorting and searching Lists

t

Trier et rechercher dans les Lists

t t t
Utilities to perform sorting and searching for Lists have the same names and signatures as those for sorting arrays of objects, but are static methods of Collections instead of Arrays. Here’s an example, modified from ArraySearching.java:
t Les fonctions effectuant des tris et des recherches dans les Lists ont les mêmes noms et signature que celles réalisant ces opérations sur des tableaux d'objets, mais sont des méthodes static appartenant à la classe Collections au lieu de Arrays. En voici un exemple, adapté de ArraySearching.java :
t t t
//: c09:ListSortSearch.java // Sorting and searching Lists with 'Collections.' import com.bruceeckel.util.*; import java.util.*; public class ListSortSearch { public static void main(String[] args) { List list = new ArrayList(); Collections2.fill(list, Collections2.capitals, 25); System.out.println(list + "\n"); Collections.shuffle(list); System.out.println("After shuffling: "+list); Collections.sort(list); System.out.println(list + "\n"); Object key = list.get(12); int index = Collections.binarySearch(list, key); System.out.println("Location of " + key + " is " + index + ", list.get(" + index + ") = " + list.get(index)); AlphabeticComparator comp = new AlphabeticComparator(); Collections.sort(list, comp); System.out.println(list + "\n"); key = list.get(12); index = Collections.binarySearch(list, key, comp); System.out.println("Location of " + key + " is " + index + ", list.get(" + index + ") = " + list.get(index)); } } ///:~ t
//: c09:ListSortSearch.java
// Trier et rechercher dans les Lists avec 'Collections.'
import com.bruceeckel.util.*;
import java.util.*;

public class ListSortSearch {
  public static void main(String[] args) {
    List list = new ArrayList();
    Collections2.fill(list,
      Collections2.capitals, 25);
    System.out.println(list + "\n");
    Collections.shuffle(list);
    System.out.println("After shuffling: "+list);
    Collections.sort(list);
    System.out.println(list + "\n");
    Object key = list.get(12);
    int index =
      Collections.binarySearch(list, key);
    System.out.println("Location of " + key +
      " is " + index + ", list.get(" +
      index + ") = " + list.get(index));
    AlphabeticComparator comp =      new AlphabeticComparator();
    Collections.sort(list, comp);
    System.out.println(list + "\n");
    key = list.get(12);
    index =
      Collections.binarySearch(list, key, comp);
    System.out.println("Location of " + key +
      " is " + index + ", list.get(" +
      index + ") = " + list.get(index));
  }
} ///:~
t t t
The use of these methods is identical to the ones in Arrays, but you’re using a List instead of an array. Just like searching and sorting with arrays, if you sort using a Comparator you must binarySearch( ) using the same Comparator.
t L'utilisation de ces méthodes est identique à celles dans Arrays, mais une List est utilisée à la place d'un tableau. Comme pour les tableaux, il faut passer le Comparator utilisé pour trier la liste lorsqu'on fait un appel à binarySearch().
t t t
This program also demonstrates the shuffle( ) method in Collections, which randomizes the order of a List.
t Ce programme illustre aussi la méthode shuffle() de la classe Collections, qui modifie aléatoirement l'ordre d'une List.
t t t

Utilities

t

Utilitaires

t t t
There are a number of other useful utilities in the Collections class:
t Il existe un certain nombre d'autres méthodes bien pratiques dans la classe Collections :  
t t t
enumeration(Collection)
Produces an old-style Enumeration for the argument.
max(Collection)
min(Collection)
Produces the maximum or minimum element in the argument using the natural comparison method of the objects in the Collection.
max(Collection, Comparator)
min(Collection, Comparator)
Produces the maximum or minimum element in the Collection using the Comparator.
reverse( )
Reverses all the elements in place.
copy(List dest, List src)
Copies elements from src to dest.
fill(List list, Object o)
Replaces all the elements of list with o.
nCopies(int n, Object o)
Returns an immutable List of size n whose references all point to o.
t
enumeration(Collection) Renvoie une Enumeration de l'argument.
max(Collection) min(Collection) Renvoie l'élément maximum ou minimum de l'argument en utilisant la méthode de comparaison naturelle des objets de la Collection.
max(Collection, Comparator) min(Collection, Comparator) Renvoie l'élément maximum ou minimum de la Collection en utilisant le Comparator.
reverse() Inverse tous les éléments sur place.
copy(List dest, List src) Copie les éléments de src vers dest.
fill(List list, Object o) Remplace tous les éléments de la liste avec o.
nCopies(int n, Object o) Renvoie une List non-modifiable de taille n dont les références pointent toutes sur o.
t t t
Note that min( ) and max( ) work with Collection objects, not with Lists, so you don’t need to worry about whether the Collection should be sorted or not. (As mentioned earlier, you do need to sort( ) a List or an array before performing a binarySearch( ).)
t Notez que comme min() et max() fonctionnent avec des objets Collection, et non avec des Lists, il n'est pas nécessaire que celle-ci soit triée (ainsi que nous l'avons déjà vu, il faut trier une List ou un tableau avant de leur appliquer binarySearch()).
t t t

Making a Collection or Map unmodifiable

t

Rendre une Collection ou une Map non-modifiable

t t t
Often it is convenient to create a read-only version of a Collection or Map. The Collections class allows you to do this by passing the original container into a method that hands back a read-only version. There are four variations on this method, one each for Collection (if you don’t want to treat a Collection as a more specific type), List, Set, and Map. This example shows the proper way to build read-only versions of each:
t Il est souvent bien pratique de créer une version en lecture seule d'une Collection ou d'une Map. La classe Collections permet ceci en passant le conteneur original à une méthode qui en renvoie une version non modifiable. Il existe quatre variantes de cette méthode, pour les Collections (si on ne veut pas traiter une Collection comme un type plus spécifique), les Lists, les Sets et les Maps. Cet exemple montre la bonne manière pour construire des versions en lecture seule des collections :
t t t
//: c09:ReadOnly.java // Using the Collections.unmodifiable methods. import java.util.*; import com.bruceeckel.util.*; public class ReadOnly { static Collections2.StringGenerator gen = Collections2.countries; public static void main(String[] args) { Collection c = new ArrayList(); Collections2.fill(c, gen, 25); // Insert data c = Collections.unmodifiableCollection(c); System.out.println(c); // Reading is OK c.add("one"); // Can't change it List a = new ArrayList(); Collections2.fill(a, gen.reset(), 25); a = Collections.unmodifiableList(a); ListIterator lit = a.listIterator(); System.out.println(lit.next()); // Reading OK lit.add("one"); // Can't change it Set s = new HashSet(); Collections2.fill(s, gen.reset(), 25); s = Collections.unmodifiableSet(s); System.out.println(s); // Reading OK //! s.add("one"); // Can't change it Map m = new HashMap(); Collections2.fill(m, Collections2.geography, 25); m = Collections.unmodifiableMap(m); System.out.println(m); // Reading OK //! m.put("Ralph", "Howdy!"); } } ///:~ t
//: c09:ReadOnly.java
// Utilisation des méthodes Collections.unmodifiable.
import java.util.*;
import com.bruceeckel.util.*;

public class ReadOnly {
  static Collections2.StringGenerator gen =
    Collections2.countries;
  public static void main(String[] args) {
    Collection c = new ArrayList();
    Collections2.fill(c, gen, 25); // Insertion des données
    c = Collections.unmodifiableCollection(c);
    System.out.println(c); // L'accès en lecture est OK
    c.add("one"); // Modification impossible
    
    List a = new ArrayList();
    Collections2.fill(a, gen.reset(), 25);
    a = Collections.unmodifiableList(a);
    ListIterator lit = a.listIterator();
    System.out.println(lit.next()); // L'accès en lecture est OK
    lit.add("one"); // Modification impossible

    Set s = new HashSet();
    Collections2.fill(s, gen.reset(), 25);
    s = Collections.unmodifiableSet(s);
    System.out.println(s); // L'accès en lecture est OK
    //! s.add("one"); // Modification impossible
    
    Map m = new HashMap();
    Collections2.fill(m,
      Collections2.geography, 25);
    m = Collections.unmodifiableMap(m);
    System.out.println(m); // L'accès en lecture est OK
    //! m.put("Ralph", "Howdy!");
  }
} ///:~
t t t
In each case, you must fill the container with meaningful data before you make it read-only. Once it is loaded, the best approach is to replace the existing reference with the reference that is produced by the “unmodifiable” call. That way, you don’t run the risk of accidentally changing the contents once you’ve made it unmodifiable. On the other hand, this tool also allows you to keep a modifiable container as private within a class and to return a read-only reference to that container from a method call. So you can change it from within the class, but everyone else can only read it.
t Dans chaque cas, le conteneur doit être rempli de données avant de le rendre non-modifiable. Une fois rempli, la meilleure approche consiste à remplacer la référence existante par la référence renvoyée par l'appel à « unmodifiable ». De cette façon, on ne risque pas d'en altérer accidentellement le contenu une fois qu'on l'a rendu non modifiable. D'un autre côté, cet outil permet de garder un conteneur modifiable private dans la classe et de renvoyer une référence en lecture seule sur ce conteneur à partir d'une méthode. Ainsi on peut le changer depuis la classe, tandis qu'on ne pourra y accéder qu'en lecture depuis l'extérieur de cette classe.
t t t
Calling the “unmodifiable” method for a particular type does not cause compile-time checking, but once the transformation has occurred, any calls to methods that modify the contents of a particular container will produce an UnsupportedOperationException.
t Appeler la méthode « unmodifiable » pour un type particulier n'implique pas de contrôle lors de la compilation, mais une fois la transformation effectuée, tout appel à une méthode tentant de modifier le contenu du conteneur provoquera une UnsupportedOperationException.
t t t

Synchronizing a Collection or Map

t

Synchroniser une Collection ou une Map

t t t
The synchronized keyword is an important part of the subject of multithreading, a more complicated topic that will not be introduced until Chapter 14. Here, I shall note only that the Collections class contains a way to automatically synchronize an entire container. The syntax is similar to the “unmodifiable” methods:
t Le mot-clef synchronized constitue une partie importante du multithreading, un sujet plus compliqué qui ne sera abordé qu'à partir du Chapitre 14. Ici, je noterai juste que la classe Collections permet de synchroniser automatiquement un conteneur entier. La syntaxe en est similaire aux méthodes « unmodifiable » :
t t t
//: c09:Synchronization.java // Using the Collections.synchronized methods. import java.util.*; public class Synchronization { public static void main(String[] args) { Collection c = Collections.synchronizedCollection( new ArrayList()); List list = Collections.synchronizedList( new ArrayList()); Set s = Collections.synchronizedSet( new HashSet()); Map m = Collections.synchronizedMap( new HashMap()); } } ///:~ t
//: c09:Synchronization.java
// Utilisation des méthodes Collections.synchronized.
import java.util.*;

public class Synchronization {
  public static void main(String[] args) {
    Collection c =
      Collections.synchronizedCollection(
        new ArrayList());
    List list = Collections.synchronizedList(
      new ArrayList());
    Set s = Collections.synchronizedSet(
      new HashSet());
    Map m = Collections.synchronizedMap(
      new HashMap());
  }
} ///:~
t t t
In this case, you immediately pass the new container through the appropriate “synchronized” method; that way there’s no chance of accidentally exposing the unsynchronized version.
t Dans ce cas, on passe directement le nouveau conteneur à la méthode « synchronisée » appropriée ; ainsi la version non synchronisée ne peut être accédée de manière accidentelle.
t t t

Fail fast

t

Echec rapide

t t t
The Java containers also have a mechanism to prevent more than one process from modifying the contents of a container. The problem occurs if you’re iterating through a container and some other process steps in and inserts, removes, or changes an object in that container. Maybe you’ve already passed that object, maybe it’s ahead of you, maybe the size of the container shrinks after you call size( )—there are many scenarios for disaster. The Java containers library incorporates a fail-fast mechanism that looks for any changes to the container other than the ones your process is personally responsible for. If it detects that someone else is modifying the container, it immediately produces a ConcurrentModificationException. This is the “fail-fast” aspect—it doesn’t try to detect a problem later on using a more complex algorithm.
t Les conteneurs Java possèdent aussi un mécanisme qui permet d'empêcher que le contenu d'un conteneur ne soit modifié par plus d'un processus. Le problème survient si on itère à travers un conteneur alors qu'un autre processus insère, supprime ou change un objet de ce conteneur. Cet objet peut déjà avoir été passé, ou on ne l'a pas encore rencontré, peut-être que la taille du conteneur a diminué depuis qu'on a appelé size() - il existe de nombreux scénarios catastrophes. La bibliothèque de conteneurs Java incorpore un mécanisme d'échec-rapide qui traque tous les changements effectués sur le conteneur autres que ceux dont notre processus est responsable. S'il détecte que quelqu'un d'autre tente de modifier le conteneur, il produit immédiatement une ConcurrentModificationException. C'est l'aspect « échec-rapide » - il ne tente pas de détecter le problème plus tard en utilisant un algorithme plus complexe.
t t t
It’s quite easy to see the fail-fast mechanism in operation—all you have to do is create an iterator and then add something to the collection that the iterator is pointing to, like this:
t Il est relativement aisé de voir le mécanisme d'échec rapide en action - tout ce qu'on a à faire est de créer un itérateur et d'ajouter alors un item à la collection sur laquelle l'itérateur pointe, comme ceci :
t t t
//: c09:FailFast.java // Demonstrates the "fail fast" behavior. import java.util.*; public class FailFast { public static void main(String[] args) { Collection c = new ArrayList(); Iterator it = c.iterator(); c.add("An object"); // Causes an exception: String s = (String)it.next(); } } ///:~ t
//: c09:FailFast.java
// Illustre la notion d'« échec rapide ».
import java.util.*;

public class FailFast {
  public static void main(String[] args) {
    Collection c = new ArrayList();
    Iterator it = c.iterator();
    c.add("An object");
    // Génére une exception :
    String s = (String)it.next();
  }
} ///:~
t t t
The exception happens because something is placed in the container after the iterator is acquired from the container. The possibility that two parts of the program could be modifying the same container produces an uncertain state, so the exception notifies you that you should change your code—in this case, acquire the iterator after you have added all the elements to the container.
t L'exception survient parce que quelque chose est stocké dans le conteneur après que l'itérateur ait été produit par le conteneur. La possibilité que deux parties du programme puissent modifier le même conteneur mène à un état incertain, l'exception notifie donc qu'il faut modifier le code - dans ce cas, récupérer l'itérateur après avoir ajouté tous les éléments au conteneur.
t t t
Note that you cannot benefit from this kind of monitoring when you’re accessing the elements of a List using get( ).
t Notez qu'on ne peut bénéficier de ce genre de surveillance quand on accède aux éléments d'une List en utilisant get().
t t t
t t t
t t
\\\
///
t t t
t
     
Sommaire Le site de Bruce Eckel