JavaRush /Java Blog /Random-IT /Lavoro byte per byte con i file
Joysi
Livello 41

Lavoro byte per byte con i file

Pubblicato nel gruppo Random-IT
Speciale per

Iniziamo

Al livello 18 sono iniziati i primi compiti di lettura del file byte per byte: leggere il file, quindi trovare i byte minimi/massimi o emetterlo in una forma ordinata, ecc.
Lavorare byte per byte con i file - 1
Le persone qui sono molto intelligenti. Conoscono le raccolte e possono ordinarle e inserirle. Le raccolte sono un meccanismo potente. E molti non li usavano affatto prima di JavaRush. Naturalmente è encomiabile studiarli e cercare di colpirli nei posti sbagliati. COSÌ. Prendiamo un problema che non è nei compiti (in modo che non ci siano spoiler durante la risoluzione), ma ce ne sono di molto simili:
  • Immettere il nome del file dalla console
  • Legge tutti i byte da un file.
  • Ignorando le ripetizioni, ordinale per bytecode in ordine decrescente.
  • Schermo
  • Chiudere il flusso I/O
Esempio di byte di un file di input 44 83 44 Esempio di output 83 44 Abbiamo inoltre introdotto le variabili startTimee finishTimeper registrare il tempo di esecuzione del programma. Per il calcolo ho utilizzato i3-3GHz/8Gb RAM/HDD WD Blue-1Tb/Win7-64/jdk-8u73-windows-x64 (esempi di programmi nelle opzioni 1-2 sono presi dal forum info.javarush, sono leggermente modificato solo per l'ordinamento in ordine crescente, ok, cioè sono REALI!!)

Risolviamolo di petto:

// Вариант 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.");
    }
}
Risolve tutto alla grande! La prova (se ci fosse stata, sarebbe passata alla grande). Ma nella vita sono pochi i file che contengono solo la riga “La mamma ha lavato il telaio”. Diamo in pasto al nostro programma un file da 46 MB (per gli standard odierni, non sembra molto). Che cos'è, il programma funziona per 220 secondi. Un tentativo di caricare un file da 1Gb la sera (la dimensione del film MPEG4 non è della migliore qualità) non ha avuto successo. La mattina stavo ancora leggendo il programma e dovevo già andare al lavoro. Qual è il problema? Probabilmente in uso ArrayList<Integer>che ha 1 miliardo di elementi al suo interno. Ciascun elemento occupa almeno 16 byte (intestazione: 8 byte + campo int: 4 byte + allineamento per molteplicità 8: 4 byte). In totale, con una dimensione RAM di 8, mettiamo volontariamente 16 Gb di dati nella memoria. Faremo meglio. Immergiamoci più a fondo nelle collezioni. E evviva, abbiamo trovato quello di cui avevamo bisogno.

Incontra TreeSet

Questo è molto:
  • non consente di memorizzare due elementi identici (il che significa che memorizzeremo tutti i 255 elementi, invece di un miliardo!)
  • quando manipola i suoi elementi, si organizza automaticamente (si ordina - eccolo, il massimo della perfezione!)
Noi abbiamo:
// Вариант 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.");
    }
}
L'output è: file da 46 MB, 176 secondi. File da 1 GB - 3 ore e 5 minuti. Il progresso è evidente. Abbiamo potuto "attendere" i risultati e il file da 46 MB viene elaborato notevolmente più velocemente. Andare avanti. Proviamo a rinunciare alle raccolte (per alcuni questo sarà atrocemente doloroso). Useremo array semplici (è così primitivo). Notiamo una cosa importante . Il numero di byte incontrati può essere inserito in un array di lunghezza 256. Quindi aumenteremo semplicemente di uno l'elemento dell'array corrispondente al byte letto.

Array: byte per byte

// Вариант 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.");
    }
}
L'output è: file da 46 MB, 158 secondi. File da 1 GB - 2 ore e 55 minuti. Ancora un miglioramento, ma piccolo. E abbiamo fatto tutto con strumenti semplici. Non ho usato il microscopio per piantare i chiodi . Ora una digressione lirica. Ricordiamo la struttura di un computer. La memoria RAM (DRAM) , dove solitamente viene eseguito il programma e vengono memorizzate le variabili, ha un'elevata velocità di accesso, ma è di piccole dimensioni. La memoria su un disco rigido/flash (HDD o unità Flash) dove solitamente vengono archiviati i file, al contrario, ha una bassa velocità di accesso ma una grande dimensione. Quindi, quando leggiamo un file da 1 Gb byte per byte (ovvero, accediamo all'HDD un miliardo di volte), passiamo molto tempo a lavorare con un dispositivo a bassa velocità (trasferiamo sabbia granello per granello dal corpo di un camion KamAZ nella sandbox). Proviamo a migliorarlo ulteriormente.

Scarichiamo subito l'INTERO camion KAMAZ con la sabbia!

// Вариант 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.");
    }
}
una piccola, ma ancora importante digressione .
  1. L'indice arrBytes è definito tra 0..255,
  2. fileImage è un array di byte i cui elementi hanno il valore -128..127
Pertanto, per contare i byte, utilizzeremo una costruzione arrBytes[fileImage[i] & 0b11111111]++; che resetterà semplicemente il bit di segno e ci restituirà un valore compreso tra 0 e 255. E così, il risultato: file da 46 MB 0,13 secondi (meno di un secondo). File da 1 GB - 9 secondi. Ce l'abbiamo fatta! Siamo incredibilmente fantastici! Accelerato da 3 ore a 9 secondi. Questo è tutto, puoi sederti sulla sedia e bere un po' di tè. E ora un altro esperimento: proviamo un file da 32 GB (ad esempio un film HD). Di conseguenza, otteniamo un suono scoppiettante dall'HDD funzionante con il programma che si blocca in Windows. KamAZ ha scaricato il corpo con la sabbia e ha rotto la sabbiera! Cosa facciamo? Ricordiamo un altro fatto. I file nel sistema operativo vengono generalmente archiviati in porzioni (cluster) di 2-64 KB (a seconda del tipo di file system, impostazioni, ecc.). Leggeremo in porzioni, ad esempio 64.000 byte. Proviamo a scaricare KamAZ con un escavatore in porzioni abbastanza grandi:

Utilizzando un buffer

// Вариант 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.");
    }
}
Di conseguenza, abbiamo ottenuto: file da 46 MB 0,08 secondi (meno di un secondo). File da 1 GB - 0,9 secondi (meno di un secondo). File da 32 GB - 31 secondi. Da notare che per un file da 1 Gb abbiamo migliorato le prestazioni da diverse ore a frazioni di secondi !!! Con questo modesto fatto completeremo l'esperimento e miglioreremo il codice iniziale. Abbiamo fatto progressi in molti modi: siamo soddisfatti dei nuovi indicatori di consumo di memoria e tempo di funzionamento. Inoltre, in questo caso, non recuperiamo raccolte inutili dalla libreria standard. PS Qualcuno dirà che l'esempio è inverosimile, ecc. Ma ci sono molti compiti simili: analizzare un enorme volume di elementi che hanno un numero finito di stati. Ad esempio, le immagini (RGB - solitamente memorizzate in 24 byte, nel nostro caso long[] arrRGB = new long[256*256*256] occuperebbero solo 64 MB in memoria), musica (l'ampiezza è solitamente digitalizzata in 16 o 24 bit ) o sensori indicatori discreti, ecc.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION