2012-06-19 23 views
6

Tengo un archivo enorme con millones de columnas,, dividida por el espacio, pero sólo tiene un número limitado de filas:Cómo leer la segunda columna en un archivo grande

examples.txt:

1 2 3 4 5 ........ 
3 1 2 3 5 ......... 
l 6 3 2 2 ........ 

Ahora, sólo quiero leer en la segunda columna:

2 
1 
6 

¿Cómo hago eso en java con un alto rendimiento.

Gracias

Actualizar: el archivo es generalmente 1.4G que contiene cientos de filas.

+0

¿Cada fila contiene exactamente la misma cantidad de caracteres? – cheeken

+0

en realidad no ... – Frank

+0

Estoy perdido. ¿El formato tiene 1 dígito seguido de 1 espacio, etc. con exactamente el mismo número de caracteres en cada línea? – Gene

Respuesta

2

Si el archivo no está estructurado de forma estática, su única opción es la ingenua uno: leer a través de la secuencia de bytes del archivo de secuencia de bytes en busca de nuevas líneas y apoderarse de la segunda columna después de cada uno. Use FileReader.

Si su archivo estaba estructurado estáticamente, podría calcular en qué parte del archivo estaría la segunda columna para una línea determinada y seek() directamente.

+2

no leas cada línea ... solo lee muchos bytes e itera sobre ella ... si la línea es larga, bloqueas un largo tiempo mientras lees y ¡el ariete está lleno de ella! – headgrowe

+0

No estoy seguro de lo que quieres decir. Fue bastante claro al decir "por byby" buscando caracteres nuevos, no por línea. – Gene

+0

sí, quería ser más específico ... – headgrowe

0

Aquí es un poco de máquina de estado que utiliza un FileInputStream como su entrada y se ocupa de su propio almacenamiento en búfer. No hay conversión de configuración regional.

En mis 7 años de edad portátil de 1,4 GHz con 1/2 GB de memoria se tarda 48 segundos para pasar por 1,28 mil millones de bytes de datos. Los búferes de más de 4 KB parecen funcionar más lento.

En un nuevo 1-año de edad MacBook con 4 GB que se ejecuta en 14 segundos. Una vez que el archivo está en caché, se ejecuta en 2.7 segundos. Nuevamente, no hay diferencia con los almacenamientos intermedios superiores a 4 Kb. Este es el mismo archivo de datos de 1.2 billones de bytes.

Espero que IO con memoria asignada sea mejor, pero esto es probablemente más portátil.

Traerá cualquier columna que le diga.

import java.io.*; 
import java.util.Random; 

public class Test { 

public static class ColumnReader { 

    private final InputStream is; 
    private final int colIndex; 
    private final byte [] buf; 
    private int nBytes = 0; 
    private int colVal = -1; 
    private int bufPos = 0; 

    public ColumnReader(InputStream is, int colIndex, int bufSize) { 
     this.is = is; 
     this.colIndex = colIndex; 
     this.buf = new byte [bufSize]; 
    } 

    /** 
    * States for a tiny DFA to recognize columns. 
    */ 
    private static final int START = 0; 
    private static final int IN_ANY_COL = 1; 
    private static final int IN_THE_COL = 2; 
    private static final int WASTE_REST = 3; 

    /** 
    * Return value of colIndex'th column or -1 if none is found. 
    * 
    * @return value of column or -1 if none found. 
    */ 
    public int getNext() { 
     colVal = -1; 
     bufPos = parseLine(bufPos); 
     return colVal; 
    } 

    /** 
    * If getNext() returns -1, this can be used to check if 
    * we're at the end of file. 
    * 
    * Otherwise the column did not exist. 
    * 
    * @return end of file indication 
    */ 
    public boolean atEoF() { 
     return nBytes == -1; 
    } 

    /** 
    * Parse a line. 
    * The buffer is automatically refilled if p reaches the end. 
    * This uses a standard DFA pattern. 
    * 
    * @param p position of line start in buffer 
    * @return position of next unread character in buffer 
    */ 
    private int parseLine(int p) { 
     colVal = -1; 
     int iCol = -1; 
     int state = START; 
     for (;;) { 
      if (p == nBytes) { 
       try { 
        nBytes = is.read(buf); 
       } catch (IOException ex) { 
        nBytes = -1; 
       } 
       if (nBytes == -1) { 
        return -1; 
       } 
       p = 0; 
      } 
      byte ch = buf[p++]; 
      if (ch == '\n') { 
       return p; 
      } 
      switch (state) { 
       case START: 
        if ('0' <= ch && ch <= '9') { 
         if (++iCol == colIndex) { 
          state = IN_THE_COL; 
          colVal = ch - '0'; 
         } 
         else { 
          state = IN_ANY_COL; 
         } 
        } 
        break; 

       case IN_THE_COL: 
        if ('0' <= ch && ch <= '9') { 
         colVal = 10 * colVal + (ch - '0'); 
        } 
        else { 
         state = WASTE_REST; 
        } 
        break; 

       case IN_ANY_COL: 
        if (ch < '0' || ch > '9') { 
         state = START; 
        } 
        break; 

       case WASTE_REST: 
        break; 
      } 
     } 
    } 
} 

public static void main(String[] args) { 
    final String fn = "data.txt"; 
    if (args.length > 0 && args[0].equals("--create-data")) { 
     PrintWriter pw; 
     try { 
      pw = new PrintWriter(fn); 
     } catch (FileNotFoundException ex) { 
      System.err.println(ex.getMessage()); 
      return; 
     } 
     Random gen = new Random(); 
     for (int row = 0; row < 100; row++) { 
      int rowLen = 4 * 1024 * 1024 + gen.nextInt(10000); 
      for (int col = 0; col < rowLen; col++) { 
       pw.print(gen.nextInt(32)); 
       pw.print((col < rowLen - 1) ? ' ' : '\n'); 
      } 
     } 
     pw.close(); 
    } 

    FileInputStream fis; 
    try { 
     fis = new FileInputStream(fn); 
    } catch (FileNotFoundException ex) { 
     System.err.println(ex.getMessage()); 
     return; 
    } 
    ColumnReader cr = new ColumnReader(fis, 1, 4 * 1024); 
    int val; 
    long start = System.currentTimeMillis(); 
    while ((val = cr.getNext()) != -1) { 
     System.out.print('.'); 
    } 
    long stop = System.currentTimeMillis(); 
    System.out.println("\nelapsed = " + (stop - start)/1000.0); 
} 
} 
+0

como me entristece "no leer todas las líneas ... solo leer una gran cantidad de bytes e iterar sobre ella ... si la línea es larga, se bloquea un largo tiempo mientras se lee y ¡el carnero está lleno de eso! " ... por cierto, un entero tiene 4 bytes de largo ... por lo tanto, podrías guardar la fila sin espacios y no como una cadena ... la lectura sin conversión a una cadena es realmente más rápida ... usa un FileInputStream ... – headgrowe

+0

Estamos en acuerdo violento. Escribí para probar BufferedReader y getLine antes de que publicara el tamaño real del archivo. Nunca es bueno hacer una optimización de código complicada antes de estar seguro de que son necesarios. – Gene

0

que tienen que estar de acuerdo con @gene, tratar con un BufferedReader y getLine primer lugar, es simple y fácil de código. Solo tenga cuidado de no aliar el arreglo de respaldo entre el resultado de getLine y cualquier operación de subcadena que use. String.substring() es un culpable particularmente común, y he tenido varios byte-arrays de MB bloqueados en la memoria porque una subcadena de 3 caracteres hacía referencia a él.

Suponiendo ASCII, mi preferencia al hacer esto es dejar caer hasta el nivel de bytes. Use mmap para ver el archivo como ByteBuffer y luego realice un escaneo lineal para 0x20 y 0x0A (suponiendo separadores de línea de estilo Unix). Luego convierta los bytes relevantes a una Cadena. Si está usando un juego de caracteres de 8 bits, es extremadamente difícil ser más rápido que esto.

Si está utilizando Unicode, el problema es bastante más complicado que le recomiendo encarecidamente que use BufferedReader a menos que ese rendimiento realmente no sea aceptable. Si getLine() no funciona, entonces considere simplemente hacer un bucle en una llamada al read().

Independientemente siempre se debe especificar el juego de caracteres para inicializar un String desde una corriente de bytes externo. Esto documenta su supuesto de conjunto de caracteres explícitamente.Así que recomiendo una modificación menor a la sugerencia del gen, por lo que una de:

int i = Integer.parseInt(new String(buffer, start, length, "US-ASCII")); 

int i = Integer.parseInt(new String(buffer, start, length, "ISO-8859-1")); 

int i = Integer.parseInt(new String(buffer, start, length, "UTF-8")); 

según corresponda.

Cuestiones relacionadas