2010-12-05 6 views
11

Comme le titre l'indique, je cherche le moyen le plus rapide possible d'écrire des tableaux entiers dans des fichiers. Les matrices varieront en taille et contiendront de manière réaliste entre 2500 et 25 000 000 ints.Le moyen le plus rapide d'écrire un tableau d'entiers dans un fichier Java?

Voici le code que je utilise actuellement:

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

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

Étant donné que DataOutputStream a une méthode pour écrire des tableaux d'octets, j'ai essayé de convertir le tableau int à un tableau d'octets comme ceci:

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

et comme ceci:

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

Les deux semblent donner une légère augmentation de la vitesse, environ 5%. Je ne les ai pas testés assez rigoureusement pour le confirmer.

Y a-t-il des techniques qui accélèreront cette opération d'écriture de fichier ou des guides pertinents sur les meilleures pratiques pour les performances d'écriture Java IO?

+2

Comment voulez-vous que le contenu du fichier à formater, exactement? –

+0

Si vous insérez le code vous-même, le code ne s'est pas réchauffé plus rapidement. Cependant si vous exécutez le test pendant 5-10 secondes, vous verrez si cela a fait une réelle amélioration. (Comme il JVM le fera pour vous) –

+0

@Karl juste une séquence d'ints sans formatage. –

Répondre

21

J'ai eu un coup d'oeil à trois options:

  1. en utilisant DataOutputStream;
  2. en utilisant ObjectOutputStream (pour Serializable objets, int[] est); et
  3. en utilisant FileChannel.

Les résultats sont

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 

Ainsi, la version NIO est le plus rapide. Il a aussi l'avantage de permettre les modifications, ce qui signifie que vous pouvez facilement changer un int alors que le ObjectOutputStream nécessiterait de lire tout le tableau, de le modifier et de l'écrire dans un fichier.

Code suit:

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

Bons tests, mais j'ai une erreur avec le FileChannel: java.nio.channels.NonReadableChannelException. Est-ce que tu sais pourquoi? –

+2

J'ai utilisé la méthode @ dacwe pour écrire sur le FileChannel, le code modifié est ici http://pastebin.com/HhpcS7HX –

+0

je reçois la même exception, une idée pourquoi quelqu'un? – steveh

0

La matrice est sérialisable - ne pouvez-vous pas simplement utiliser writer.writeObject(data);? Cela va certainement être plus rapide que les appels individuels writeInt.

Si vous avez d'autres exigences sur le format de données de sortie que la récupération dans int[], c'est une question différente.

+1

writeObject a un surdébit significatif et utilise writeInt à la fin. C'est un moyen très convivial d'écrire des objets et je pense que c'est un meilleur choix dans la plupart des situations. –

3

La principale amélioration que vous pouvez apporter à l'écriture de int [] est soit;

  • augmenter la taille du tampon. La taille est correcte pour la plupart des flux, mais l'accès aux fichiers peut être plus rapide avec un tampon plus grand. Cela pourrait entraîner une amélioration de 10 à 20%.

  • Utilisez NIO et un tampon direct. Cela vous permet d'écrire des valeurs de 32 bits sans conversion en octets. Cela peut donner une amélioration de 5%.

BTW: Vous devriez pouvoir écrire au moins 10 millions de valeurs int par seconde. Avec la mise en cache du disque, vous augmentez cette valeur à 200 millions par seconde.

6

J'utiliserais FileChannel du paquet nio et ByteBuffer.Cette approche semble (sur mon ordinateur) donner 2 à 4 fois plus de performance en écriture:

sortie du programme:

normal time: 2555 
faster time: 765 

Voici le programme:

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

Pouvez-vous re-tester en utilisant un tampon de mémoire directe? Cela devrait rendre l'écriture plus rapide (car il faut copier dans un tampon direct sinon) –

+0

Essayez aussi un BufferOutputStream avec une taille de buffer 64K –

+1

Merci, l'approche FileChannel est beaucoup plus rapide. –

Questions connexes