:: Enseignements :: ESIPE :: E4INFO :: 2015-2016 :: Java Réseau I - Concurrence et E/S ::
[LOGO]

Executeur, Reader, Channel, CharBuffer, ByteBuffer


Exercice 1 - Travail en parallèle

On souhaite écrire un programme calculant le nombre d'entiers premiers dans un tableau de 1 000 000 entiers longs donné.
Pour savoir si un nombre est premier on utilisera la méthode suivante :
    public static boolean isPrime(long n) {
        return IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(divisor -> n % divisor == 0);
    }
      
On souhaite paralléliser le programme en permettant d'exécuter en même temps plusieurs calculs sur différents CPUs. On va donc découper le tableau par blocs de 1 000 valeurs (virtuellement, juste avec des index) pour que chaque CPU calcule le nombre d'entiers premiers sur une partie du tableau.
Pour les tests, on initialisera les cases du tableau avec des valeurs aléatoires entre 1 et 1 000 000.

  1. Dans une méthode primeInParallelWithExecutors qui prend le tableau en paramètre, créez un ExecutorService en utilisant la classe Executors permettant de distribuer le calcul sur 5 threads différents.
    Dans un premier temps, on ne s'intéresse qu'aux sommes intermédiaires que l'on va afficher.
    Dans cette méthode, faites effectuer les calculs en parallèle en utilisant la méthode execute et des Runnable qui effectuent le calcul sur une partie du tableau et finissent par afficher leur résultat sur le terminal.
    Pensez à utiliser la méthode shutdown pour indiquer à l'executor qu'il n'y a plus de calculs à effectuer.
  2. Comment devriez vous faire pour calculer la somme totale des sommes intermédiaires ? (pas de code ici)
  3. Au lieu d'utiliser votre réponse précédente, nous utiliserons le mécanisme de Future.
    Modifiez la méthode primeInParallelWithExecutors pour utiliser la méthode submit de l'executor et des Callable qui effectuent le calcul et retournent le nombre d'entiers premiers de leur partie de tableau en utilisant des Future.
  4. Ecrivez une méthode primeInParallelWithStream qui fait le même calcul avec un Stream parallèle.
    Pour avoir un Stream parallèle, il suffit d'appeler parallel() sur l'interface Stream.
    Vérifiez que vous obtenez le même résultat qu'avec le pool de threads.

Exercice 2 - Pêche à la ligne

On cherche à écrire différentes façons de compter le nombre de lignes d'un fichier en Java.
Pour tester, nous utiliserons des textes de la bible piochés quelque part sur internet.
Le code du main sera donc le suivant :
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("bible.txt");
        System.out.println(countWithAStream(path));
        // System.out.println(countWithAReader(path));
        // System.out.println(countWithAReaderAndANIOCharBuffer(path));
        // System.out.println(countWithAFileChannelAndANIOByteBuffer(path));
        // System.out.println(countWithAFileChannelAndAMappedByteBuffer(path));
    }
      

  1. Un étudiant n'ayant pas vraiment écouté en cours à écrit le code suivant :
        public static long countWithAStream(Path path) throws IOException {
            return Files.lines(path).count();
        }
            
    Corrigez le gros bug de ce code !
  2. On souhaite maintenant écrire une méthode countWithAReader qui compte le nombre de ligne (le nombre de '\n') en utilisant un java.io.Reader.
    Expliquez pourquoi il n'est pas correct d'utiliser un InputStream plutôt qu'un Reader pour compter le nombre de lignes si l'encodage du fichier est par exemple UTF-8 ?
    Comment créer un Reader à partir d'un Path ?
    Ecrivez la méthode countWithAReader en utilisant la méthode read(char[] buffer).
    Pourquoi la valeur de retour est différente de countWithAStream ?
  3. On souhaite écrire une méthode countWithAReaderAndANIOCharBuffer qui au lieu d'utiliser la méthode read(char[] buffer) utilise la méthode read(CharBuffer buffer).
    Lorsque l'on effectue un parcours d'un buffer, doit-on faire une boucle en utilisant get() ou get(int) pour que celle-ci soit plus efficace ?
    Ecrire la méthode countWithAReaderAndANIOCharBuffer.
    Attention: n'oubliez pas d'utiliser la méthode CharBuffer.flip() (et clear()) !
  4. Quelle est la différence entre un buffer (java.nio.ByteBuffer) alloué en utilisant allocate et un buffer alloué en utilisant allocateDirect() ?
    Quel est le type de buffer le plus efficace dans le cas d'un compteur de lignes et pourquoi ?
    Comment faire pour obtenir un CharBuffer direct ?
    Modifiez le code de countWithAReaderAndANIOCharBuffer en conséquence.
  5. Expliquez pourquoi, si le fichier utilise l'encodage US-ASCII ou ISO 8859-1, il n'est pas nécessaire de transformer les byte en char pour pouvoir décoder le fichier ?
    On souhaite écrire une méthode countWithAFileChannelAndANIOByteBuffer qui comme son nom l'indique utilise un FileChannel pour lire dans un ByteBuffer.
    Ecrivez la méthode countWithAFileChannelAndANIOByteBuffer sachant qu'il existe une méthode static open sur un FileChannel pour créer celui-ci.
  6. Enfin, écrivez la méthode countWithAFileChannelAndAMappedByteBuffer qui permet de mapper tout le contenu d'un fichier en mémoire en utilisant pour cela la méthode map de FileChannel.