2009-05-16 15 views
5

Esiste un articolo/algoritmo su come posso leggere un file lungo ad una determinata velocità?Leggere il file ad una determinata velocità in Java

Dire che non voglio passare 10 KB/sec durante l'emissione di letture.

+1

La domanda è: perché si vuole a leggere un file ad una certa velocità? Sembra che tu voglia leggere i dati su richiesta, quindi capire la tua "domanda" potrebbe consentirci di indicarti una soluzione migliore. – EFraim

+0

Ho intenzione di scaricare un file di grandi dimensioni da Internet ma non voglio che la mia applicazione passi il limite impostato dall'utente. –

+0

https://svn.apache.org/repos/asf/hadoop/common/trunk/hadoop-tools/hadoop-distcp/src/main/java/org/apache/hadoop/tools/util/ThrottledInputStream.java – pvllnspk

risposta

4

La soluzione grezza consiste nel leggere un chunk alla volta e quindi nel sonno, ad es. 10k, quindi dormire un secondo. Ma la prima domanda che devo porre è: perché? Ci sono un paio di risposte probabili:

  1. Non si desidera creare lavoro più rapidamente di quanto si possa fare; oppure
  2. Non si desidera creare un carico eccessivo sul sistema.

Il mio suggerimento non è quello di controllarlo a livello di lettura. È un po 'disordinato e impreciso. Controllalo invece alla fine del lavoro. Java ha un sacco di grandi strumenti di concorrenza per affrontare questo. Ci sono alcuni modi alternativi per farlo.

Mi piace usare un modello producer consumer per risolvere questo tipo di problema. Ti offre grandi opzioni per essere in grado di monitorare i progressi con un thread di reporting e così via e può essere una soluzione davvero pulita.

Qualcosa come un ArrayBlockingQueue può essere utilizzato per il tipo di throttling necessario per entrambi (1) e (2). Con una capacità limitata il lettore alla fine bloccherà quando la coda sarà piena, quindi non si riempirà troppo velocemente. I lavoratori (consumatori) possono essere controllati per lavorare solo così velocemente per limitare anche il tasso di copertura (2).

1

Dipende un po 'dal fatto che tu intenda "non superare una certa velocità" o "stai vicino a una certa velocità".

Se vuoi dire "non superare", si può garantire che con un semplice ciclo:

while not EOF do 
    read a buffer 
    Thread.wait(time) 
    write the buffer 
od 

La quantità di tempo di attesa è una semplice funzione della dimensione del buffer; se la dimensione del buffer è 10K byte, si desidera attendere un secondo tra le letture.

Se si desidera avvicinarsi, probabilmente è necessario utilizzare un timer.

  • creare un Runnable per fare la lettura
  • creare un Timer con un TimerTask per fare la lettura
  • elenco gli TimerTask n volte al secondo.

Se siete preoccupati per la velocità con cui si sta passando i dati a qualcos'altro, invece di controllare la lettura, inserire i dati in una struttura di dati come una coda o buffer circolare, e controllare il altra fine; inviare dati periodicamente. Tuttavia, è necessario prestare attenzione, a seconda delle dimensioni del set di dati e di tali fattori, poiché è possibile incorrere in limiti di memoria se il lettore è molto più veloce dello scrittore.

1

Se si utilizza l'I/O Java, è necessario conoscere i flussi di decorazione. Suggerisco una sottoclasse InputStream che prende un altro InputStream e riduce la velocità del flusso. (È possibile creare sottoclasse FileInputStream ma tale approccio è altamente soggetto a errori e inflessibile.)

L'implementazione esatta dipenderà dai requisiti esatti dell'utente. In genere si desidera prendere nota del tempo in cui è stata restituita l'ultima lettura (System.nanoTime). Nella lettura corrente, dopo la lettura sottostante, wait fino a quando è trascorso un tempo sufficiente per la quantità di dati trasferiti. Un'implementazione più sofisticata può eseguire il buffer e restituire (quasi) immediatamente solo la quantità di dati richiesta dalla frequenza (fare attenzione a restituire una lunghezza di lettura pari a 0 se il buffer è di lunghezza zero).

4
  • mentre! EOF
    • deposito System.currentTimeMillis() + 1000 (1 sec) per un lungo periodo variabile
    • leggere un 10K tampone
    • controllo se il tempo è trascorso memorizzato
      • se non lo è, Thread.sleep() per il tempo memorizzato - ora corrente

Creazione di ThrottledInputStream che richiede un altro InputStream come suggerito sarebbe una buona soluzione.

11

Una soluzione semplice, creando un ThrottledInputStream.

Questo deve essere utilizzato in questo modo:

 final InputStream slowIS = new ThrottledInputStream(new BufferedInputStream(new FileInputStream("c:\\file.txt"),8000),300); 

300 è il numero di kilobyte al secondo. 8000 è la dimensione del blocco per BufferedInputStream.

Questo dovrebbe naturalmente essere generalizzato implementando read (byte b [], int off, int len), che risparmierà un sacco di chiamate System.currentTimeMillis(). System.currentTimeMillis() viene chiamato una volta per ogni byte letto, che può causare un po 'di overhead. Dovrebbe anche essere possibile memorizzare il numero di byte che può essere letto senza essere chiamato System.currentTimeMillis().

Assicuratevi di mettere un BufferedInputStream tra in mezzo, altrimenti il ​​FileInputStream verrà interrogato in byte singoli anziché in blocchi. Ciò ridurrà il modulo di carico CPU al 10% a quasi 0. Rischi di superare la velocità di trasmissione del numero di byte nella dimensione del blocco.

import java.io.InputStream; 
import java.io.IOException; 

public class ThrottledInputStream extends InputStream { 
    private final InputStream rawStream; 
    private long totalBytesRead; 
    private long startTimeMillis; 

    private static final int BYTES_PER_KILOBYTE = 1024; 
    private static final int MILLIS_PER_SECOND = 1000; 
    private final int ratePerMillis; 

    public ThrottledInputStream(InputStream rawStream, int kBytesPersecond) { 
     this.rawStream = rawStream; 
     ratePerMillis = kBytesPersecond * BYTES_PER_KILOBYTE/MILLIS_PER_SECOND; 
    } 

    @Override 
    public int read() throws IOException { 
     if (startTimeMillis == 0) { 
      startTimeMillis = System.currentTimeMillis(); 
     } 
     long now = System.currentTimeMillis(); 
     long interval = now - startTimeMillis; 
     //see if we are too fast.. 
     if (interval * ratePerMillis < totalBytesRead + 1) { //+1 because we are reading 1 byte 
      try { 
       final long sleepTime = ratePerMillis/(totalBytesRead + 1) - interval; // will most likely only be relevant on the first few passes 
       Thread.sleep(Math.max(1, sleepTime)); 
      } catch (InterruptedException e) {//never realized what that is good for :) 
      } 
     } 
     totalBytesRead += 1; 
     return rawStream.read(); 
    } 
} 
+1

FYI : L'eccezione interrotta serve a garantire che Thread possa rispondere immediatamente a una richiesta di interruzione, anche se sta dormendo. – Simiil

0

È possibile utilizzare un RateLimiter. E realizza la tua implementazione della lettura in InputStream. Un esempio di questo può essere visto muggito

public class InputStreamFlow extends InputStream { 
    private final InputStream inputStream; 
    private final RateLimiter maxBytesPerSecond; 

    public InputStreamFlow(InputStream inputStream, RateLimiter limiter) { 
     this.inputStream = inputStream; 
     this.maxBytesPerSecond = limiter; 
    } 

    @Override 
    public int read() throws IOException { 
     maxBytesPerSecond.acquire(1); 
     return (inputStream.read()); 
    } 

    @Override 
    public int read(byte[] b) throws IOException { 
     maxBytesPerSecond.acquire(b.length); 
     return (inputStream.read(b)); 
    } 

    @Override 
    public int read(byte[] b, int off, int len) throws IOException { 
     maxBytesPerSecond.acquire(len); 
     return (inputStream.read(b,off, len)); 
    } 
} 

se si desidera limitare il flusso da 1 MB/s è possibile ottenere il flusso di input in questo modo:

final RateLimiter limiter = RateLimiter.create(RateLimiter.ONE_MB); 
final InputStreamFlow inputStreamFlow = new InputStreamFlow(originalInputStream, limiter); 
Problemi correlati