2009-05-21 96 views
11

Estoy leyendo en un archivo de texto usando FileInputStream que coloca el contenido del archivo en una matriz de bytes. Luego convierto la matriz de bytes en una cadena usando una nueva cadena (byte).Análisis de archivos de texto en Java

Una vez que tengo la cadena que estoy usando String.split("\n") a dividir el archivo en una matriz de cadenas y luego tomar esa matriz de cadenas y analizarlo haciendo una String.split(",") y almacenar el contenido en un ArrayList.

Tengo un archivo 200MB + y se está quedando sin memoria cuando enciendo la JVM con 1 GB de memoria. Sé que debo estar haciendo algo correctamente en alguna parte, no estoy seguro de si la forma en que estoy analizando es incorrecta o la estructura de datos que estoy usando.

También tardo unos 12 segundos en analizar el archivo, lo que parece mucho tiempo. ¿Alguien puede señalar lo que puedo estar haciendo que me esté quedando sin memoria y qué puede estar causando que mi programa funcione lento?

El contenido del archivo se ve como se muestra a continuación:

"12334", "100", "1.233", "TEST", "TEXT", "1234" 
"12334", "100", "1.233", "TEST", "TEXT", "1234" 
. 
. 
. 
"12334", "100", "1.233", "TEST", "TEXT", "1234" 

Gracias

+0

Aclare: ¿Está diciendo que está utilizando -Xmx1024m para iniciar su JVM y todavía obtiene un OutOfMemoryError? – duffymo

+0

Usar un patrón compilado sería mejor (incluso distribuir su propia división de especialista). Pero parece que tienes muchos datos de objetos. –

+0

duffymo, ese es exactamente el caso. Estoy configurando la JVM con -Xmx1024m y obteniendo OutOfMemoryError –

Respuesta

8

Suena como que estás haciendo algo malo para mí - toda una creación de objetos lotta pasando.

¿Qué tan representativo es ese archivo de "prueba"? ¿Qué estás haciendo realmente con esa información? Si eso es típico de lo que realmente tienes, diría que hay mucha repetición en esa información.

Si todo va a estar en cadenas de todos modos, comience con un BufferedReader para leer cada línea. Asigne previamente esa lista a un tamaño que sea similar al que necesita para no desperdiciar recursos añadiéndole cada vez. Dividir cada una de esas líneas en la coma; asegúrese de quitar las comillas dobles.

Es posible que desee preguntarse: "¿Por qué necesito todo este archivo en la memoria, todo a la vez?" ¿Puedes leer un poco, procesar un poco y nunca tener todo en la memoria a la vez? Solo tú conoces tu problema lo suficiente como para responder.

Quizás pueda iniciar jvisualvm si tiene JDK 6 y ver qué ocurre con la memoria. Esa sería una gran pista.

+0

La forma en que lo hace la persona que pregunta parece crear una gran char [] (en una cadena) y luego cadenas que son partes de eso, lo que sorprendentemente es en realidad la mejor manera de hacerlo. (Imposibilidad de implementación de split. Por supuesto, depende de la implementación) –

+0

Tiene razón en "uber efficient", Tom. Mi consejo en realidad lo empeoraría. Si el problema persiste, creo que es el procesamiento sobre la marcha y jvisualvm lo que más ayudará. – duffymo

+0

Ahora que tenemos streams con Java 8, me pregunto si esto se puede hacer de manera más eficiente usando programación funcional. Para eso nacieron las corrientes. – duffymo

2

Si tiene 200,000,000 de archivos de caracteres y los divide cada cinco caracteres, tiene 40,000,000 de objetos String. Supongamos que comparten datos de caracteres reales con los 400 MB originales String (char tiene 2 bytes). A String es decir 32 bytes, por lo que es 1,280,000,000 bytes de objetos String.

(probablemente vale la pena señalar que esto es muy dependiente de la implementación. split podría crear enteramente cuerdas con enteramente nuevo respaldo char[] o, otoh, compartir algunas String valores comunes. Algunas implementaciones de Java no utilizan el corte en rodajas de char[]. Algunos pueden utilizar una forma compacta similar a UTF-8 y da tiempos de acceso aleatorio muy pobres.)

Incluso asumiendo cadenas más largas, eso es un montón de objetos. Con esa cantidad de datos, es probable que desee trabajar con la mayor parte en forma compacta como la original (solo con índices). Solo convierta a los objetos lo que necesita. La implementación debería ser de base de datos (aunque tradicionalmente no manejan cadenas de longitud variable de manera eficiente).

4

Parece que actualmente tiene 3 copias del archivo completo en la memoria: la matriz de bytes, la cadena y la matriz de las líneas.

En lugar de leer los bytes en una matriz de bytes y luego convertirlos en caracteres usando new String(), sería mejor usar un InputStreamReader, que convertirá a caracteres de forma incremental, en lugar de hacerlo todo por adelantado.

Además, en lugar de utilizar String.split ("\ n") para obtener las líneas individuales, debe leer una línea a la vez. Puede usar el método readLine() en BufferedReader.

intentar algo como esto:

BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8")); 
try { 
    while (true) { 
    String line = reader.readLine(); 
    if (line == null) break; 
    String[] fields = line.split(","); 
    // process fields here 
    } 
} finally { 
    reader.close(); 
} 
+0

La forma original en que las cadenas (deberían) comparten el mismo carácter de respaldo [] y, por lo tanto, son más eficientes. Una división de línea probablemente no es tan mala, porque solo habrá un char [] por línea. –

+0

(Y la matriz de bytes no necesita estar en la memoria al mismo tiempo que la matriz de líneas.) –

+0

Estaba empezando a sentir que tenía que tener muchas copias del contenido del archivo en la memoria. Voy a probar esto y ver la diferencia –

11

No estoy seguro de qué tan eficiente es la memoria-sabia, pero mi primera aproximación sería utilizar un Scanner ya que es muy fácil de usar:

File file = new File("/path/to/my/file.txt"); 
Scanner input = new Scanner(file); 

while(input.hasNext()) { 
    String nextToken = input.next(); 
    //or to process line by line 
    String nextLine = input.nextLine(); 
} 

input.close(); 

Compruebe la API para saber cómo modificar el delimitador que utiliza para dividir los tokens.

5

Eche un vistazo a estas páginas. Contienen muchos analizadores de código abierto CSV. JSaPar es uno de ellos.

+0

¿Alguna sugerencia en particular? –

+0

Bueno, estoy un poco predispuesto aquí ya que soy el autor de la biblioteca JSaPar. Es por eso que lo mencioné en mi respuesta, pero una de las otras bibliotecas podría ser más adecuada para usted, dependiendo del problema que esté tratando de resolver. – stenix

0

Durante la llamada/invocación de su programa que puede utilizar este comando: java [-options] className [...] args
en lugar de [-options] proporcionar más memoria, por ejemplo, -Xmx1024m o más. pero esto es solo una solución, tienes que cambiar tu mecanismo de análisis.