lunedì 14 febbraio 2011

Dividere uno stream in blocchi

Capita spesso di dover spezzare un array di byte (o di altro tipo) in "pezzi" da inviare a client remoti in modo continuo fino all'esaurimento dei dati stessi. Il pattern per implementare questa funzionalità  è abbastanza semplice, e ovviamente esistono diverse implementazioni più o meno raffinate; propongo di seguito quella che sono solito usare:

int APPLICATION_BYTE_STREAM_SIZE = 1024;
byte[] myByteArray = ...;
OutputStream os = ...;


int chunksToSend = myByteArray.length / APPLICATION_BYTE_STREAM_SIZE;
for( int chunkNumber = 0; chunkNumber < chunksToSend; chunkNumber++ )
     os.write(  myByteArray,   
        APPLICATION_BYTE_STREAM_SIZE * chunkNumber,
        APPLICATION_BYTE_STREAM_SIZE );

// ho ancora dei chunk?
int lastChunkSize = myByteArray.length % APPLICATION_BYTE_STREAM_SIZE;
if( lastChunkSize > 0 )
    os.write(     myByteArray,
            myByteArray.length - lastChunkSize,
        lastChunkSize );

 
L'idea è abbastanza semplice: dato l'array di byte myByteArray che deve essere inviato in blocchi (chunk) da massimo APPLICATION_BYTE_STREAM_SIZE bytes si procede come segue:
 
1) si calcolano quanti chunk si devono spedire. Se la dimensione dell'array di byte è identica a quella di un singolo chunk si avrà, chiaramente, un chunk solo da spedire; se la dimensione è inferiore non si dovranno spedire chunk in questo primo step, altrimenti si spediranno i chunk necessari per avvicinarsi alla dimensione dell'array di byte.
 
2) nel caso in cui la dimensione dell'array di byte non sia multiplo di APPLICATION_BYTE_STREAM_SIZE si avrà  una rimanenza, che viene calcolata con una semplice divisione per resto: il risultato corrispondenrà  alla dimensione dell'ultimo chunk da inviare. Si procede quindi alla scrittura dell'ultimo chunk considerando che lo spiazzamento è pari ai byte rimanenti dal fondo dell'array.

Il procedimento di cui sopra puo' essere "compresso" in due modi: usando uno spiazzamento all'indietro oppure uno all'avanti (quest'ultimo risulta leggermente piu' chiaro). L'idea in entrambi i casi è quella di sapere in anticipo se ci sarà il chunk di chiusura (ossia quello dei byte rimanenti dovuto al fatto che l'array di byte non è un multiplo di APPLICATION_BYTE_STREAM_SIZE) e di usare un unico ciclo per la scrittura dei byte. Ovviamente occorre riconoscere il caso in cui si debba spedire un chunk intero o la parte di completamento.
Il ciclo all'indietro è il seguente:

int lastChunkSize =  myByteArray.length % APPLICATION_BYTE_STREAM_SIZE;
int chunksToSend = (myByteArray.length / APPLICATION_BYTE_STREAM_SIZE ) + ( lastChunkSize  > 0 ? 1 : 0 );

for( int chunkRemaining = chunksToSend; chunkRemaining > 0; chunkRemaining-- )
     os.write(  myByteArray,                       
             myByteArray.length-(APPLICATION_BYTE_STREAM_SIZE * (chunkRemaining - 1 ) )-lastChunkSize,                ( chunkRemaining != 1 ? APPLICATION_BYTE_STREAM_SIZE : lastChunkSize )
    );


Come si nota, il primo passo consiste nel calcolare la dimensione dell'ultimo chunk e considerare il numero di chunk da spedire incrementato di 1 nel caso tale dimensione non sia nulla (che significa che l'array ha dimensione multipla APPLICATION_BYTE_STREAM_SIZE). Il ciclo itera sul numero calcolato di chunk all'indietro, e quindi l'offset dal quale partire a scrivere è dato dalla differenza fra la dimensione totale dell'array, la dimensione di un singolo chunk (considerato quanti ne rimangono) e la dimensione dell'ultimo chunk, in modo che inizialmente l'offset sia nullo. La dimensione dei dati da scrivere dipende dal numero di chunk che rimangono: se ne rimane solo uno (l'ultimo) allora la dimensione è quella calcolata, altrimenti tutti i chunk hanno la stessa dimensione.
La versione con ciclo in avanti è leggermente piu' comprensibile:


int lastChunkSize =  myByteArray.length % APPLICATION_BYTE_STREAM_SIZE;
int chunksToSend = ( myByteArray.length / APPLICATION_BYTE_STREAM_SIZE );

for( int currentChunk = 0; 
     currentChunk < (chunksToSend + ( lastChunkSize  > 0 ? 1 : 0 ) );
     currentChunk++ )
        os.write( myByteArray,  
                  APPLICATION_BYTE_STREAM_SIZE * currentChunk,
           (currentChunk != chunksToSend ? APPLICATION_BYTE_STREAM_SIZE : lastChunkSize)            );           

In questo caso il numero di chunk viene considerato pari a quello dei chunk completi da inviare, anche se il ciclo itera su un eventuale chunk di completamento. Lo spiazzamento viene semplicemente calcolato in base ai chunk completi già spediti, mentre il numero di byte da scrivere cambia nel caso si siano spediti tutti i chunk completi.

Nessun commento: