JavaRush /Blog Java /Random-FR /Travail octet par octet avec les fichiers
Joysi
Niveau 41

Travail octet par octet avec les fichiers

Publié dans le groupe Random-FR
Spécial pour

Commençons

Au niveau 18, les premières tâches de lecture de fichier octet par octet ont commencé : lire le fichier, puis trouver les octets minimum/maximum ou le sortir sous une forme ordonnée, etc.
Travail octet par octet avec des fichiers - 1
Les gens ici sont très intelligents. Ils connaissent les collections et savent trier et insérer. Les collections sont un mécanisme puissant. Et beaucoup ne les utilisaient pas du tout avant JavaRush. Il est bien sûr louable de les étudier et d’essayer de les frapper aux mauvais endroits. Donc. Prenons un problème qui n'est pas dans les tâches (pour qu'il n'y ait pas de spoilers lors de sa résolution), mais il y en a des très similaires :
  • Entrez le nom du fichier depuis la console
  • Lit tous les octets d'un fichier.
  • En ignorant les répétitions, triez-les par bytecode par ordre décroissant.
  • Afficher
  • Fermer le flux d'E/S
Exemple d'octets d'un fichier d'entrée 44 83 44 Exemple de sortie 83 44 Nous avons en outre introduit des variables startTimeet finishTimepour enregistrer le temps d'exécution du programme. Pour le calcul j'ai utilisé i3-3GHz/8Gb RAM/HDD WD Blue-1Tb/Win7-64/jdk-8u73-windows-x64 (les exemples de programmes dans les options 1-2 sont tirés du forum info.javarush, ils sont légèrement modifiés uniquement pour le tri par ordre croissant, ok - c'est-à-dire qu'ils sont RÉELS !!)

Résolvons-le de front :

// Вариант 1. Загоняем в коллекцию и сортируем используя ее метод Collections.sort
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());
        long startTime = System.currentTimeMillis();

        ArrayList<Integer> listData = new ArrayList<Integer>();
        while (inputStream.available() > 0) listData.add(inputStream.read());
        inputStream.close();
        ArrayList<Integer> result = new ArrayList<Integer>(new HashSet<Integer>(listData));
        Collections.sort(result);

        while (!result.isEmpty()) {
            System.out.print(result.get(result.size()-1) + " ");
            result.remove(result.get(result.size()-1));
        }

        long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
Résout tout très bien ! Le test (s’il y en avait eu, il aurait passé avec brio). Mais dans la vie, il existe peu de fichiers contenant uniquement la ligne « Maman a lavé le cadre ». Alimentons notre programme avec un fichier de 46 Mo (selon les normes actuelles, cela ne semble pas grand-chose). Qu'est-ce que c'est, le programme dure 220 secondes. Une tentative d'alimentation d'un fichier de 1 Go le soir (la taille du film MPEG4 n'est pas de la meilleure qualité) a échoué. Je lisais encore le programme le matin - et je devais déjà aller travailler. Quel est le problème? Probablement en cours d'utilisation ArrayList<Integer>, il contient 1 milliard d'éléments. Chaque élément occupe 16 octets minimum (En-tête : 8 octets + Champ int : 4 octets + Alignement pour multiplicité 8 : 4 octets). Au total, nous avons volontairement mis en mémoire 16 Go de données avec une taille de RAM de 8. Nous ferons mieux. Plongeons plus profondément dans les collections. Et hourra, nous avons trouvé ce dont nous avions besoin.

Rencontrez TreeSet

C'est beaucoup :
  • ne permet pas de stocker deux éléments identiques (ce qui veut dire que nous stockerons les 255 éléments en mémoire, au lieu d'un milliard !)
  • lors de la manipulation de ses éléments, il s'organise automatiquement (se trie - le voilà, le comble de la perfection !)
On a:
// Вариант 2. Загоняем в ТreeSet который сам сортирует (лютый win!)
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        byte[] arrBytes = new byte[256];
        long startTime = System.currentTimeMillis();

        SortedSet<Integer> list = new TreeSet<Integer>();
        while(inputStream.available()>0) list.add(inputStream.read());
        inputStream.close();

        while (!list.isEmpty())        {
            System.out.print(list.last() + " ");
            list.remove(list.last());
        }

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
Le résultat est : fichier de 46 Mo, 176 secondes. Fichier de 1 Go - 3 heures 5 minutes. Les progrès sont évidents. Nous avons pu « attendre » les résultats et le fichier de 46 Mo est traité nettement plus rapidement. Poursuivre. Essayons d'abandonner les collectes (cela sera atrocement douloureux pour certains). Nous utiliserons des tableaux simples (c'est tellement primitif). Notons une chose importante . Le nombre d'octets rencontrés peut être mis dans un tableau de longueur 256. Nous allons donc simplement augmenter de un l'élément du tableau correspondant à l'octet lu.

Tableau - octet par octet

// Вариант 3. Считываем массив поbyteно.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        while (inputStream.available() > 0) arrBytes[inputStream.read()]++;

		inputStream.close();
        // Выводим отсортированный по byte-codeу в обратном порядке
        for (long i = 255; i >= 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

			long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
Le résultat est : fichier de 46 Mo, 158 secondes. Fichier de 1 Go - 2 heures 55 minutes. Encore une amélioration, mais petite. Et nous avons tout fait avec des outils simples. Je n'ai pas utilisé de microscope pour enfoncer les clous . Maintenant une digression lyrique. Rappelons la structure d'un ordinateur. La mémoire RAM (DRAM) , où le programme est généralement exécuté et les variables sont stockées, a une vitesse d'accès élevée, mais est de petite taille. La mémoire sur un disque dur/flash (HDD ou lecteurs Flash) où les fichiers sont habituellement stockés, a au contraire une vitesse d'accès faible mais une grande taille. Ainsi, lorsque nous lisons un fichier de 1 Go octet par octet (c'est-à-dire que nous accédons au disque dur un milliard de fois), nous passons beaucoup de temps à travailler avec un appareil à basse vitesse (nous transférons du sable grain par grain depuis la carrosserie d'un camion KamAZ dans le bac à sable). Essayons de l'améliorer encore.

Vidons tout le camion KAMAZ avec du sable en même temps !

// Вариант 4. Считываем массив сразу целиком за раз в память.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        byte fileImage[]=new byte[inputStream.available()];
        long fileSize=fileImage.length;
        inputStream.read(fileImage);
        for (int i = 0; i = 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
une petite digression, mais encore une fois importante .
  1. L'index arrBytes est défini entre 0 et 255,
  2. fileImage est un tableau d'octets dont les éléments ont la valeur -128..127
Par conséquent, pour compter les octets, nous utiliserons une construction arrBytes[fileImage[i] & 0b11111111]++; qui réinitialisera simplement le bit de signe et nous renverra une valeur comprise entre 0 et 255. Et donc, les résultats : fichier de 46 Mo en 0,13 seconde (moins d'une seconde). Fichier de 1 Go - 9 secondes. Nous l'avons fait! Nous sommes incroyablement cool ! Accéléré de 3 heures à 9 secondes. Ça y est, vous pouvez vous asseoir sur votre chaise et boire du thé. Et maintenant une autre expérience : essayons un fichier de 32 Go (par exemple, un film HD). En conséquence, nous obtenons un crépitement provenant du disque dur en état de marche et le programme plante sous Windows. KamAZ a jeté le corps avec du sable et a cassé le bac à sable ! Qu'est-ce qu'on fait? Rappelons-nous encore un fait. Les fichiers du système d'exploitation sont généralement stockés par portions (clusters) de 2 à 64 Ko (selon le type de système de fichiers, les paramètres, etc.). Nous lirons par portions, par exemple 64 000 octets. Essayons de décharger KamAZ avec une pelle par portions assez importantes :

Utiliser un tampon

// Вариант 5. Считываем массив кусками.
public class Solution {
    public static void main(String[] args) throws Exception {
        FileInputStream inputStream = new FileInputStream(new BufferedReader(new InputStreamReader(System.in)).readLine());

        long[] arrBytes = new long[256];
        long startTime = System.currentTimeMillis();

        int  bufferSize = 64000;
        byte buffer[]   = new byte[64000];

        while (inputStream.available() > 0) {
            if (inputStream.available() < 64000) bufferSize = inputStream.available();
            inputStream.read(buffer, 0, bufferSize );
            for (int i = 0; i = 0 ; i--)
            if (arrBytes[(int) i] > 0) System.out.print(i + " ");

		long finishTime = System.currentTimeMillis();
        System.out.println("\nвремя работы=" + (finishTime-startTime) + "ms.");
    }
}
En conséquence, nous avons obtenu : un fichier de 46 Mo en 0,08 seconde (moins d'une seconde). Fichier de 1 Go - 0,9 seconde (moins d'une seconde). Fichier de 32 Go - 31 secondes. A noter que pour un fichier de 1 Go nous avons amélioré les performances de plusieurs heures à des fractions de secondes !!! Avec ce modeste fait, nous allons terminer l’expérience et améliorer le code initial. Nous avons progressé à bien des égards - nous sommes satisfaits des nouveaux indicateurs de consommation de mémoire et de durée de fonctionnement. De plus, dans ce cas, nous ne récupérons pas les collections inutiles de la bibliothèque standard. PS Quelqu'un dira que l'exemple est tiré par les cheveux, etc. Mais il existe de nombreuses tâches similaires : analyser un énorme volume d'éléments ayant un nombre fini d'états. Par exemple, les images (RVB - généralement stockées sur 24 octets, dans notre cas long[] arrRGB = new long[256*256*256] n'occuperaient que 64 Mo en mémoire), la musique (l'amplitude est généralement numérisée en 16 ou 24 bits ) ou des capteurs indicateurs discrets, etc.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION