cortar archivos enormes con java...

buen dia comunidad
quisiera su ayuda con alguna idea para resolver un problema que tengo.
tengo un archivo.txt el cual se genera cada 5 minutos trayendome informacion x
este archivo varia su tamaño desde 1kb hasta varios GB, quiero dividir este archivo en varios de acuerdo a ciertos parametros que explico a continuacion:

dentro de ese archivo se repite de 1 a n veces cierta linea la cual ocupo como parametro para dividir, por ejemplo:

encabezado
linea
linea
linea
linea
encabezado
linea
linea
encabezado
linea
linea
...y asi sucesivamente, como veran de ese ejemplo me tendria q traer 3 archivos de 5, 3 y 3 lineas, esto de algun modo ya lo hace bien, el problema reside en cuando el archivo viene muy grande o sea de mas. de 500 mb...me manda un error por java heap space, ya trate de aumentarlo a 1.5 gb ya que corre en un windows server 2008 y no me permite usar mas de 2 gb de los 8 disponibles que tengo...supongo que marca el error porque carga todo el archivo en memoria, la pregunta es..hay alguna forma de leer el archivo sin tener que cargarlo todo en memoria?...trate de explicarlo lo mejor que pude, si pueden ayudarme se los agradecere mucho.. quedo a sus ordenes.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

BufferedReader

Si utilizas   podras ir leyendo linea por linea

Imagen de ezamudio

System.gc()

Este es uno de esos casos en donde invocar   te puede ayudar. Invócalo cada que hayas leído algunas miles de líneas, para liberar memoria que está siendo utilizada por cadenas que leíste y no vas a volver a utilizar.

Lee y escribe la linea

Lee y escribe la linea inmediatamente, no la guardes en ningún lado.

Algo como:

 

Así no usaras mas que la memoria de la linea que estas leyendo en ese momento.

Si quieres mejorar el desempeño puedes hacer un buffer de varias lineas y escribir ahí y luego llegado el máximo escribir todo el buffer.

 

Pero haz eso solo hasta que tengas una versión que funcione bien porque la ganancia podría ser irrelevante o podría mejorarse el desempeño simplemente usando algo como un BufferedWriter.

detalles

señores muchas gracias por su respuesta...he utilizado el System.gc() pero después de un rato me sale este error

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at java.util.Arrays.copyOfRange(Unknown Source)

at java.lang.String.(Unknown Source)

at java.lang.String.substring(Unknown Source)

at java.lang.String.trim(Unknown Source)

at CortaConsolas2.CortaConsolasCTLM(CortaConsolas2.java:88)

at CortaConsolas2.main(CortaConsolas2.java:259)

me parece que es algo con el arraylist pero aun no detecto que puede estarlo causando...me podrian ayudar?

El gc te ayuda pero no

El gc te ayuda pero no resuelve si tu problema esta mal planteado.

El problema es que estas queriendo usar mas memoria de la que tienes, tan simple como eso.

Intenta con lo que te comente.

ok reviso ese punto muchas

ok reviso ese punto muchas gracias

Imagen de ezamudio

ah

No había leído bien el post original, mencionan que cargan (intentan cargar) todo el archivo en memoria. Error garrafal, debe ser como comenta Oscar, leer línea por línea y actuar de acuerdo a eso, sin almacenar de más. Mi consejo de invocar System.gc() sólo sirve cuando se hace de esa forma (cosa que pensé que ya estaban haciendo).

mas detalles

ok veran señores, lo que hago en si es lo siguiente:

mi archivo de origen lo leo,linea por linea, y en ese proceso extraigo ciertos parametros que usare para el nombre de los fragmentos....dependiendo del tamaño del archivo origen manda error del,heap space... adjunto las lineas principales...quisiera orientación para ver que me puede estar fallando

 

Pues al parecer lo que sucede

Pues al parecer lo que sucede es que nunca sales del ciclo.

Tome el contenido que posteaste y le di formato porque me costaba trabajo leerlo ( la siguiente vez postealo entre <code> ) y lo que puedo ver a simple vista es que la llave que cierra tu ciclo principal no esta en este código. Donde tienes el comentario "// Fin while principal" en realidad estas cerrando un if de "sCadena.contains(sRefLinea)" y el while principal no se cierra por o que posiblemente estes creando archivos por cada linea que tienes en tu archivo de entrada.

Suponiendo que fue un error de copy/paste el código aún así puede mejorarse para ser leído.

Por ejemplo, el codigo donde creas y escribes al archivo es idéntico dentro del while que fuera del while, hiciste copy/paste, en vez de eso crea una función y llamala:

 

Eso ayudará a que tu código sea más legible.

Luego, otra cosa que no tiene mucho sentido es esta condición:

 

Por lo que se puede ver, esta siempre será verdadera, porque no simplemente limpias las lista al terminar de escribirla. Así también de deshaces del primer ciclo que solamente cuenta cuantas divisiones tienes pero para ello tienes que leer todo el archivo y luego desecharlo.

O mejor aún ( y como te decía al principio ) en vez de guardar la linea en una lista para después escribirla, porque no simplemente la escribes? Lo que seguramente esta pasando es que el archivo que tienes es muy grande y simplemente estas llenando una lista hasta que se te acaba la memoria.

Si lo haces así, lo que tendrías que hacer es tener un archivo donde estas escribiendo ( el archivo actual ) y cada vez que encuentres un nuevo divisor cierras el archivo actual y creas el nuevo ( justo como en el psudo-codigo que puse )

(tiempo después y gracias a que tenía insomnio)

Aquí esta una versión modificada de tu código.

Lo que hice es crear un nuevo método para cada parte relevante de funcionalidad. Por ejemplo, si al principio todos esos ifs, se usan para obtener el nombre del nuevo archivo, se crea una función que se llame "extractFileName", si hay una sección para cerrar el archivo y renombrarlo, se crea una función para eso.

Si agregas nueva funcionalidad, en vez de agregarlo a tu lógica actual y complicar el código intenta crear un nuevo método y llámalo. Puedes ir agregando y quitando metodos según convenga, la idea es que tu código quede simple y fácil de leer.

Esta es la versión donde solo se lee una linea y se escribe al archivo temporal actual. Cuando se encuentra un divisor, se cierra el archivo y se crea uno nuevo ( justo como lo puse al principio )

Obviamente no va a funcionar a la primera pero tomate un buen tiempo para entender esta nueva estructura y ver porque existe cada nuevo metodo.

 

Si tienes mucha mas funcionalidad dentro de ese while, extraela a un nuevo método, la ganancia es que cada método hará una sola cosa y puedes arreglarlo o modificarlo mas fácilmente sin tener que estar navegando entre muchos if anidados y variables temporales/auxiliares.

Espero que esto te ayude.

Imagen de ezamudio

sí sale

Según entiendo, ese código ha sido probado con archivos cortos y funciona, pero con archivos muy grandes es cuando se queda sin memoria... es correcto, David?

Cuando metiste la llamada a System.gc() dónde fue? Obvio debe ser dentro del ciclo, no en cada iteración sino cada varios miles, para eso puedes tener un contador afuera:

 

Aun así, si el archivo mide

Aun así, si el archivo mide gigas puede ser que este codigo de problemas

 

Llamar a gc no le ayudaría mucho porque la lista seguiría manteniendo

En todo caso

 

Daria mejores resultados ( en caso de que hubiera muchos strings repetidos ) porque bf.readLine() crea siempre un nuevo objeto, aunque probablemente eso resulte en tener que aumentar el tamanio del PermGen

Imagen de ezamudio

mta

no había visto que tiene una lista donde va agregando cosas, pues sí eso podría causar problemas también.

ok

es correcto, el archivo a veces es de varios GB,y es ahi donde queda sin memoria, probé uno de los métodos que me sugirieron al principio, quité la lista y simplemente escribo la linea en el archivo tal cual, me funcionó bien con un archivo de 990MB (antes tronaba con solo 300 MB), así que de momento no ha habido falla, les compartiré el código tan pronto me sea posible y muchas gracias...

Imagen de ezamudio

varios GB

Interesante saber que Windows ya soporta archivos de más de 2GB...

Tema de FS

Pero eso es bronca del filesystem no?
También sería interesante saber si Java se permite la escritura de archivos que rebasen el tamaño del permitido por el FS (que bueeeno, hoy en dia ya son TB), pero aun veo discos en FAT-32 por ejemplo con capacidad máxima de 4GB aprox por archivo.

Imagen de ezamudio

archivos en Java

Java no tiene problemas para leer archivos de más de 4GB, a nivel lenguaje; depende de la implementación de la JVM pero sobre todo depende del filesystem usado. La única limitante a nivel lenguaje es el tamaño de un long con el que puedes manejar archivos hasta de (si no me equivoco en mis cálculos) 8 exabytes.

¿Cómo aluciné esto?
Los long en Java tienen signo, por lo que puedes contar hasta 9223372036854775807.   da el tamaño de un archivo en bytes, así que eso sería lo máximo que puede medir un archivo, conceptualmente, en Java. Eso entre 1024 da el tamaño en kilobytes; dividir entre 1000 o 1024 nuevamente da megabytes, luego gigabytes, terabytes, petabytes y cuando dividimos por sexta vez nos da exabytes, 7.999999 (si dividieron entre 1024 cada vez).

Los únicos filesystems que llegan a ese tamaño son NTFS (16EB), ReFS (16EB) HFS+ (8EB), GFS(8EB), NILFS (8EB) y algunos otros listados aquí.

Para un archivo de ese

Para un archivo de ese tamaño definitivamente no es recomendable guardar cada linea en un arreglo, juar juar.