2009-05-16 12 views
5

¿Hay algún artículo/algoritmo sobre cómo puedo leer un archivo largo a cierta velocidad?Lea el archivo a una velocidad determinada en Java

Digamos que no quiero pasar 10 KB/seg mientras publico lecturas.

+1

La pregunta es ¿por qué quieres leer un archivo a cierta velocidad? Parece que quiere leer datos a pedido, por lo tanto, comprender su "demanda" podría permitirnos apuntarlo a una mejor solución. – EFraim

+0

Voy a descargar un archivo grande de Internet, pero no quiero que mi aplicación supere el límite establecido por el usuario. –

+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

Respuesta

4

La solución bruta es solo leer un trozo a la vez y luego dormir, por ejemplo, 10k y luego dormir un segundo. Pero la primera pregunta que tengo que hacer es: ¿por qué? Hay un par de respuestas posibles:

  1. No desea crear el trabajo más rápido de lo que se puede hacer; o
  2. No desea crear demasiada carga en el sistema.

Mi sugerencia es no controlarlo en el nivel de lectura. Eso es un poco sucio e inexacto. En lugar de controlarlo en el extremo del trabajo. Java tiene muchas herramientas de simultaneidad para manejar esto. Hay algunas formas alternativas de hacer esto.

Me gusta usar un patrón producer consumer para solucionar este tipo de problema. Le brinda excelentes opciones para poder monitorear el progreso al tener un hilo de informes y demás, y puede ser una solución realmente limpia.

Algo como un ArrayBlockingQueue se puede utilizar para el tipo de regulación necesaria tanto para (1) como para (2). Con una capacidad limitada, el lector eventualmente bloqueará cuando la cola esté llena, por lo que no se llenará demasiado rápido. Los trabajadores (consumidores) pueden controlarse para que solo trabajen tan rápido como para regular también la tasa que cubre (2).

1

Depende un poco de si quiere decir "no superar una determinada tasa" o "mantenerse cerca de una cierta tasa".

Si se refiere a "no superan", se puede garantizar que con un simple bucle:

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

La cantidad de tiempo que esperar es una simple función del tamaño de la memoria intermedia; si el tamaño del búfer es 10K bytes, quiere esperar un segundo entre lecturas.

Si quiere acercarse más, probablemente necesite usar un temporizador.

  • crear un Ejecutable de hacer la lectura
  • crear un Timer con un TimerTask para hacer la lectura horario
  • los TimerTask n veces por segundo.

Si le preocupa la velocidad a la que transfiere los datos a otra cosa, en lugar de controlar la lectura, coloque los datos en una estructura de datos como una cola o un búfer circular, y controle el Otro final; enviar datos periódicamente. Sin embargo, debe tener cuidado con eso, dependiendo del tamaño del conjunto de datos, ya que puede encontrarse con limitaciones de memoria si el lector es mucho más rápido que el escritor.

1

Si ha utilizado Java I/O, entonces debería estar familiarizado con la decoración de las transmisiones. Sugiero una subclase InputStream que toma otra InputStream y acelera la velocidad de flujo. (Podría subclase FileInputStream pero ese enfoque es altamente propenso a errores e inflexible.)

Su implementación exacta dependerá de sus requisitos exactos. En general, querrá anotar la hora en que se devolvió su última lectura (System.nanoTime). En la lectura actual, después de la lectura subyacente, wait hasta que haya pasado el tiempo suficiente para la cantidad de datos transferidos. Una implementación más sofisticada puede amortiguar y regresar (casi) inmediatamente con solo la cantidad de datos que dicta la tasa (tenga cuidado de que solo devuelva una longitud de lectura de 0 si el búfer no tiene longitud).

4
  • tiempo! EOF
    • tienda System.currentTimeMillis() + 1000 (1 seg) en una variable larga
    • leyó una memoria intermedia 10K
    • verificación si ha pasado el tiempo almacenado
      • si no lo es, Thread.sleep() para el tiempo almacenado - hora actual

Crear ThrottledInputStream que tome otro InputStream como se sugiere sería una buena solución.

11

Una solución simple, al crear un ThrottledInputStream.

Esto se debe utilizar como esto:

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

300 es el número de kilobytes por segundo. 8000 es el tamaño de bloque para BufferedInputStream.

Esto debería generalizarse implementando read (byte b [], int off, int len), lo que le ahorrará un montón de llamadas System.currentTimeMillis(). System.currentTimeMillis() se llama una vez para cada byte leído, lo que puede causar un poco de sobrecarga. También debería ser posible almacenar la cantidad de bytes que se pueden leer salvablemente sin llamar a System.currentTimeMillis().

Asegúrese de poner un BufferedInputStream en el medio, de lo contrario el FileInputStream se sondea en bytes individuales en vez de bloques. Esto reducirá la carga de CPU del 10% a casi 0. Se arriesgará a exceder la velocidad de datos por el número de bytes en el tamaño del bloque.

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 : La excepción interrumpida es para garantizar que el subproceso pueda responder inmediatamente a una solicitud de interrupción, incluso si está inactiva. – Simiil

0

Puede usar un RateLimiter. Y haga su propia implementación de la lectura en InputStream. Un ejemplo de esto se puede ver abajo

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)); 
    } 
} 

si desea limitar el flujo de 1 Mb/s se puede obtener el flujo de entrada como esta:

final RateLimiter limiter = RateLimiter.create(RateLimiter.ONE_MB); 
final InputStreamFlow inputStreamFlow = new InputStreamFlow(originalInputStream, limiter); 
Cuestiones relacionadas