Buscar
Social
Ofertas laborales ES

Foro sobre Java SE > Api para reemplazar palabras en un archivo .txt

Buenas tardes.

Estoy intentando crear una sencilla aplicación en Java para reemplazar palabras dentro de un archivo de texto plano.

El chiste es remplazar las palabras contenidas en una arreglo de tamaño variable, estas palabras pueden estar formadas por una sola o más palabras.

Y lo que estoy haciendo es manejar el archivo con BufferedReader, para después leer linea por linea con readLine(), buscar si existe la palabra con indexOf y remplazarla con replaceAll para después almacenarlas en un archivo.

El problema es que las palabras que remplazo son de un tamaño variable en unidad y en cantidad ya que como puede ser solo una palabra, también pueden ser 1000 por ejemplo.

Para pocas palabras es un algoritmo eficaz, sin embargo pierde eficiencia cuando aumentan el numero de palabras, puesto que tengo que ir comparando linea por linea (readline) cada una de las palabras del arreglo.


Existe alguna otra forma de hacer esto, quizá exista alguna API que solo se le envíen como parámetros un arreglo de las palabras y ella haga todo el trabajo.

marzo 5, 2013 | Registered Commentercyberserver

Buenas,

Y no seria mejor leer el fichero completo en un String en lugar de ir comparando linea a linea? Sobre ese String luego haces de una sola vez el replaceall de lo que tengas en array y una vez listo guardas el contenido en el fichero.

Un saludo,

marzo 5, 2013 | Unregistered CommenterUnoPorAhi

Buenos días, gracias por contestar.

Lo que sucede es que es un texto enorme, incluso podrían meter la biblia.

Lo que no quiero por que pienso que igual se podría volcar java es cargar todo el contenido de una vez para buscar las palabras, es por esto que estoy leyendo linea a linea.

marzo 5, 2013 | Registered Commentercyberserver

Que incluso creo que estoy diciendo una tontería, puesto que al crear el

BufferedReader( new FileReader( flLeer) );
BufferedWriter( new FileWriter( flEscribir))

Automáticamente estoy cargando todo el archivo, no ahorraría nada si lo voy leyendo con readline verda?

marzo 5, 2013 | Registered Commentercyberserver

Buenas,

La biblia en texto plano ocupa menos de 2 megas, así que no tendrias ningun problema.

Tu metodo introduce un coste cuadratico que sabes que es incompatible con la ley de moore de cara a la escalabilidad de tu solución. Primero recorres linea a linea O(n) y por cada una de ellas recorres todo tu array comprobando O(n), por lo que el coste total es O(n^2). Ese coste es inaceptable y hace tu algoritmo inviable.

Si cargas el fichero en memoria luego solo tienes que comprobar los elementos del array una vez. Esta operación tiene un coste lineal, que es alto pero aceptable y que puede escalarse. Cargar un fichero de 100 megas (una barbaridad) en memoria en una maquina domestica normal tarda menos de medio segundo y te ocupara poco mas de esa cantidad en el heap del JVM. Para cargar el fichero rapidamente en un String lo puedes hacer con la clase Scanner, que utiliza NIO por debajo y ofrece buen rendimiento:
String contenidoFichero = new Scanner(file).useDelimiter("\\Z").next();

Un saludo

marzo 5, 2013 | Unregistered CommenterUnoPorAhi

Buenas,

Para probar lo que te comento he utilizado JUnitBenchmarks y he hecho estos tests:


@Test
public void test1() throws FileNotFoundException {
File miFile = new File(this.getClass()
.getResource("txtData.txt").getFile());
Scanner miScanner = new Scanner(miFile);
String contenidoFichero = null;
try {
contenidoFichero = miScanner.useDelimiter("\\Z").next();
} finally {
miScanner.close();
}

for (int i = 0; i<replaces.length; i++) {
contenidoFichero = contenidoFichero.replaceAll(replaces[i], replaces[i] + "t");
}
}

@Test
public void test2() throws IOException {
File miFile = new File(this.getClass()
.getResource("txtData.txt").getFile());
BufferedReader bufferedReader = null;
StringBuilder contenidoFichero = new StringBuilder();

try {
bufferedReader = new BufferedReader(new FileReader(miFile));

String line = null;

while ((line = bufferedReader.readLine()) != null) {
for (int i = 0; i<replaces.length; i++) {
contenidoFichero.append("\n" + line.replaceAll(replaces[i], replaces[i] + "t"));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
bufferedReader.close();
}
}

Siendo test1 mi algoritmo y test2 el tuyo.
He utilizado un fichero de texto de 4 megas y he hecho pruebas cambiando el numero de palabras a reemplazar. Los resultados son estos:

Con 2 palabras a reemplazar
private String[] replaces = {"ata1", "fdsfaa"};
TestLoadFileAndReplace.test1: [measured 10 out of 15 rounds, threads: 1 (sequential)]
round: 0.13 [+- 0.01], round.gc: 0.00 [+- 0.00], GC.calls: 2, GC.time: 0.02, time.total: 2.18, time.warmup: 0.89, time.bench: 1.29
TestLoadFileAndReplace.test2: [measured 10 out of 15 rounds, threads: 1 (sequential)]
round: 0.20 [+- 0.01], round.gc: 0.00 [+- 0.00], GC.calls: 13, GC.time: 0.06, time.total: 3.65, time.warmup: 1.61, time.bench: 2.05

Con 4 palabras a reemplazar
private String[] replaces = {"ata1", "fdsfaa""data", "rrqw", "fdsafds"};
TestLoadFileAndReplace.test1: [measured 10 out of 15 rounds, threads: 1 (sequential)]
round: 0.20 [+- 0.01], round.gc: 0.00 [+- 0.00], GC.calls: 4, GC.time: 0.03, time.total: 3.15, time.warmup: 1.20, time.bench: 1.95
TestLoadFileAndReplace.test2: [measured 10 out of 15 rounds, threads: 1 (sequential)]
round: 0.43 [+- 0.02], round.gc: 0.00 [+- 0.00], GC.calls: 28, GC.time: 0.27, time.total: 6.95, time.warmup: 2.67, time.bench: 4.28

Con 8 palabras a reemplazar
private String[] replaces = {"ata1", "fdsfaa""data", "rrqw", "fdsafds", "fdf", "fdsafds", "fdf"};
TestLoadFileAndReplace.test1: [measured 10 out of 15 rounds, threads: 1 (sequential)]
round: 0.30 [+- 0.01], round.gc: 0.00 [+- 0.00], GC.calls: 4, GC.time: 0.03, time.total: 4.64, time.warmup: 1.67, time.bench: 2.96
TestLoadFileAndReplace.test2: [measured 10 out of 15 rounds, threads: 1 (sequential)]
round: 0.79 [+- 0.02], round.gc: 0.00 [+- 0.00], GC.calls: 49, GC.time: 0.52, time.total: 12.57, time.warmup: 4.65, time.bench: 7.92

Como puedes comprobar con dos palabras el mio es 1.5 veces mas rapido y con 8 parabras ya es 3 veces más rapido, por lo que puedes ver que escala mejor. Y eso por no hablar de la memoria, fijate en la llamadas al GC en cada caso...

Un saludo

marzo 5, 2013 | Unregistered CommenterUnoPorAhi

Te agradezco tu tiempo, impresionante respuesta. Más claro ni el agua.

marzo 6, 2013 | Registered Commentercyberserver

Buenas,

Nada, he contestado muy rapido y a lo mejor he metido la pata en algo, pero en cuanto a que es mas rapido y eficiente la cuestion es clara.

Como curiosidad, te dejo un enlace para que te descargues la biblia en txt (me ha hecho gracia) que te puede venir bien para tus pruebas de carga:
http://printkjv.ifbweb.com/AV_txt.zip

Un saludo

marzo 6, 2013 | Unregistered CommenterUnoPorAhi

Buenas noches, te agradesco que me hayas pasado el vinculo para descargar la biblia.

Sim embargo antes de verlo he descargado una biblia parecida en formato txt.

Al cargarla en el Scanner me manda el siguiente error:


java.util.NoSuchElementException


La biblia esta aquí para su libre descarga:
http://www.unoenelsenor.com.ar/biblia.htm

He encontrado que puede ser por un .next no controlado, como en este caso seria miScanner.useDelimiter("\\Z").next().


Pero podria controlar este problema sin tener hacer condiciones next.

marzo 14, 2013 | Registered Commentercyberserver