2010-12-05 28 views
11

Como dice el título, estoy buscando la forma más rápida posible para escribir matrices de enteros en los archivos. Las matrices variarán en tamaño, y en realidad contendrán entre 2500 y 25 000 000 ints.¿La forma más rápida de escribir una matriz de enteros en un archivo en Java?

Aquí está el código que estoy usando actualmente:

DataOutputStream writer = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename))); 

for (int d : data) 
    writer.writeInt(d); 

Dado que DataOutputStream tiene un método para escribir arrays de bytes, lo he intentado convertir el int matriz a una matriz de bytes de esta manera:

private static byte[] integersToBytes(int[] values) throws IOException { 
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    DataOutputStream dos = new DataOutputStream(baos); 
    for (int i = 0; i < values.length; ++i) { 
     dos.writeInt(values[i]); 
    } 

    return baos.toByteArray(); 
} 

y como este:

private static byte[] integersToBytes2(int[] src) { 
    int srcLength = src.length; 
    byte[] dst = new byte[srcLength << 2]; 

    for (int i = 0; i < srcLength; i++) { 
     int x = src[i]; 
     int j = i << 2; 
     dst[j++] = (byte) ((x >>> 0) & 0xff); 
     dst[j++] = (byte) ((x >>> 8) & 0xff); 
     dst[j++] = (byte) ((x >>> 16) & 0xff); 
     dst[j++] = (byte) ((x >>> 24) & 0xff); 
    } 
    return dst; 
} 

Ambos parecen dar un aumento de velocidad menor, alrededor del 5%. No los he probado con suficiente rigor para confirmarlo.

¿Hay alguna técnica que acelere esta operación de escritura de archivos, o guías relevantes de mejores prácticas para el rendimiento de escritura IO de Java?

+2

¿Cómo quieres el contenido del archivo que desea formatear, exactamente? –

+0

Al escribir el código usted mismo hará que el código no se haya calentado más rápido. Sin embargo, si ejecuta la prueba durante 5-10 segundos, verá si esto ha mejorado realmente. (Como JVM lo hará por usted) –

+0

@Karl solo una secuencia de entradas sin formato. –

Respuesta

21

que tenía un aspecto en tres opciones:

  1. Usando DataOutputStream;
  2. Usando ObjectOutputStream (para Serializable objetos, que int[] es); y
  3. usando FileChannel.

Los resultados son

DataOutputStream wrote 1,000,000 ints in 3,159.716 ms 
ObjectOutputStream wrote 1,000,000 ints in 295.602 ms 
FileChannel wrote 1,000,000 ints in 110.094 ms 

Así que la versión NIO es el más rápido. También tiene la ventaja de permitir ediciones, lo que significa que puede cambiar fácilmente un int, mientras que el ObjectOutputStream requeriría leer toda la matriz, modificarla y escribirla en un archivo.

Código sigue:

private static final int NUM_INTS = 1000000; 

interface IntWriter { 
    void write(int[] ints); 
} 

public static void main(String[] args) { 
    int[] ints = new int[NUM_INTS]; 
    Random r = new Random(); 
    for (int i=0; i<NUM_INTS; i++) { 
    ints[i] = r.nextInt(); 
    } 
    time("DataOutputStream", new IntWriter() { 
    public void write(int[] ints) { 
     storeDO(ints); 
    } 
    }, ints); 
    time("ObjectOutputStream", new IntWriter() { 
    public void write(int[] ints) { 
     storeOO(ints); 
    } 
    }, ints); 
    time("FileChannel", new IntWriter() { 
    public void write(int[] ints) { 
     storeFC(ints); 
    } 
    }, ints); 
} 

private static void time(String name, IntWriter writer, int[] ints) { 
    long start = System.nanoTime(); 
    writer.write(ints); 
    long end = System.nanoTime(); 
    double ms = (end - start)/1000000d; 
    System.out.printf("%s wrote %,d ints in %,.3f ms%n", name, ints.length, ms); 
} 

private static void storeOO(int[] ints) { 
    ObjectOutputStream out = null; 
    try { 
    out = new ObjectOutputStream(new FileOutputStream("object.out")); 
    out.writeObject(ints); 
    } catch (IOException e) { 
    throw new RuntimeException(e); 
    } finally { 
    safeClose(out); 
    } 
} 

private static void storeDO(int[] ints) { 
    DataOutputStream out = null; 
    try { 
    out = new DataOutputStream(new FileOutputStream("data.out")); 
    for (int anInt : ints) { 
     out.write(anInt); 
    } 
    } catch (IOException e) { 
    throw new RuntimeException(e); 
    } finally { 
    safeClose(out); 
    } 
} 

private static void storeFC(int[] ints) { 
    FileOutputStream out = null; 
    try { 
    out = new FileOutputStream("fc.out"); 
    FileChannel file = out.getChannel(); 
    ByteBuffer buf = file.map(FileChannel.MapMode.READ_WRITE, 0, 4 * ints.length); 
    for (int i : ints) { 
     buf.putInt(i); 
    } 
    file.close(); 
    } catch (IOException e) { 
    throw new RuntimeException(e); 
    } finally { 
    safeClose(out); 
    } 
} 

private static void safeClose(OutputStream out) { 
    try { 
    if (out != null) { 
     out.close(); 
    } 
    } catch (IOException e) { 
    // do nothing 
    } 
} 
+1

Pruebas agradables, pero obtengo un error con el FileChannel: java.nio.channels.NonReadableChannelException. ¿Sabes por qué? –

+2

Utilicé el método @ dacwe para escribir en el FileChannel, el código modificado está aquí http://pastebin.com/HhpcS7HX –

+0

obtengo la misma excepción, ¿una idea de por qué alguien? – steveh

0

La matriz se puede serializar, ¿no puede simplemente usar writer.writeObject(data);? Eso definitivamente va a ser más rápido que las llamadas individuales writeInt.

Si tiene otros requisitos en el formato de datos de salida que la recuperación en int[], esa es una pregunta diferente.

+1

writeObject tiene una sobrecarga significativa y usa writeInt al final. Es una forma muy amigable de escribir objetos y sospecho que es una mejor opción en la mayoría de las situaciones. –

3

La principal mejora que puede tener para escribir int [] es para cualquiera;

  • aumentar el tamaño del búfer. El tamaño es correcto para la mayoría de las transmisiones, pero el acceso a los archivos puede ser más rápido con un búfer más grande. Esto podría producir una mejora del 10-20%.

  • Use NIO y un búfer directo. Esto le permite escribir valores de 32 bits sin convertirlos a bytes. Esto puede producir una mejora del 5%.

BTW: Debería poder escribir al menos 10 millones de valores int por segundo. Con el almacenamiento en caché de disco, aumenta esto a 200 millones por segundo.

6

Yo usaría FileChannel del paquete nio y ByteBuffer.Este enfoque parece (en mi equipo) dan 2 a 4 veces mejor rendimiento de escritura:

salida del programa:

normal time: 2555 
faster time: 765 

Este es el programa:

public class Test { 

    public static void main(String[] args) throws IOException { 

     // create a test buffer 
     ByteBuffer buffer = createBuffer(); 

     long start = System.currentTimeMillis(); 
     { 
      // do the first test (the normal way of writing files) 
      normalToFile(new File("first"), buffer.asIntBuffer()); 
     } 
     long middle = System.currentTimeMillis(); 
     { 
      // use the faster nio stuff 
      fasterToFile(new File("second"), buffer); 
     } 
     long done = System.currentTimeMillis(); 

     // print the result 
     System.out.println("normal time: " + (middle - start)); 
     System.out.println("faster time: " + (done - middle)); 
    } 

    private static void fasterToFile(File file, ByteBuffer buffer) 
    throws IOException { 

     FileChannel fc = null; 

     try { 

      fc = new FileOutputStream(file).getChannel(); 
      fc.write(buffer); 

     } finally { 

      if (fc != null) 
       fc.close(); 

      buffer.rewind(); 
     } 
    } 

    private static void normalToFile(File file, IntBuffer buffer) 
    throws IOException { 

     DataOutputStream writer = null; 

     try { 
      writer = 
       new DataOutputStream(new BufferedOutputStream(
         new FileOutputStream(file))); 

      while (buffer.hasRemaining()) 
       writer.writeInt(buffer.get()); 

     } finally { 
      if (writer != null) 
       writer.close(); 

      buffer.rewind(); 
     } 
    } 

    private static ByteBuffer createBuffer() { 
     ByteBuffer buffer = ByteBuffer.allocate(4 * 25000000); 
     Random r = new Random(1); 

     while (buffer.hasRemaining()) 
      buffer.putInt(r.nextInt()); 

     buffer.rewind(); 

     return buffer; 
    } 
} 
+0

¿Se puede volver a realizar la prueba con un búfer de memoria directo? Eso debería hacer que la escritura sea más rápida (ya que tiene que copiarse a un búfer directo de lo contrario) –

+0

Pruebe también un BufferOutputStream con un tamaño de memoria intermedia de 64 KB –

+1

Gracias, el enfoque de FileChannel es mucho más rápido. –

Cuestiones relacionadas