String Vs StringBuilder
Pues bien hace unos días estaba estudiando como funciona StringBuilder ya que a diferencia de crear una cadena con String, este no crea uno nuevo por cada cambio que se haga si no que trabaja con el que se crea la primer vez, por ejemplo
StringBuilder sb=new StringBuilder("hola");
sb.append(" -");//trabajara sobre el que ya se había creado no crea uno nuevo
A diferencia de crear un String s="Hola";
si yo hago un s=s+"-"; se crea uno nuevo y la referencia del anterior se pierde para apuntar a este nuevo
Mi duda es la siguiente con un String yo puedo aplicarle el metodo replace(char oldChar, char newChar) que remplazaria todos los caracteres que contengan lo que se quiera remplazar por lo nuevo, y en un StringBuilder tengo este replace(int start, int end, String str) que remplzaza el string especificado de una posicion inicial a una posicion final,¿ si yo no conozco las posiciones y solo quisiera remplazar supongamos todas las a por b como podria hacerle?
- sakura's blog
- Inicie sesión o regístrese para enviar comentarios
Comentarios
indexOf
Tendrías que encontrar cada b y reemplazarla en el StringBuilder:
String viejo = "y";
int pos = sb.indexOf(viejo);
while (pos >= 0) {
sb.replace(pos, pos+1, nuevo);
pos = sb.indexOf(viejo, pos);
}
Vuele el objeto String o remplaza directamente
StringBuilder c = new StringBuilder("haooooala");
String viejo="oo";
String nuevo="b";
StringBuilder f= new StringBuilder(c.toString().replace(viejo,nuevo));//rapido de implementar pero consumo de memoria mas alto
System.out.println(c);
System.out.println(f);
for(int i=0;i<c.length();i++){
if(viejo.substring(0,1).equals(c.substring(i,i+1))){
if(viejo.length()+i <=c.length() && viejo.equals(c.substring(i,i+viejo.length())))
c.replace(i,i+viejo.length(),nuevo);
}
}
System.out.println(c);
@BT aunque con eso pierdes el
@BT aunque con eso pierdes el beneficio que describe sakura inicialmente, que es el de no tener que crear nuevos objetos, que dependiendo del contexto donde se use puede ser significativo o no. Por ejemplo si solo se usa 1 - 1000 veces, la direrencia de desempeño es imperceptible, pero si se usa intensamente crea una diferencia notable solo para terminar haciendo lo que menciona Enrique.
Por ejemplo, esta es la implementación de replace(char,char) en Apache Harmony ( una implementacion de Java ) ( dar click en el + para ver el código fuente )
Haciendo un micro-BenchMark
El resultado me deja bastante impresionado
En menos de 1000 la solución que usa el for es mas rápida , pero con datos sobre 10000 o 1000000 la solucion simple es muchísimo mas rápida mas la otra solución es realmente lenta.
String viejo="a";
String nuevo="b";
for(int i=0;i<1000;i++)
c.append("a");
long inicio=System.currentTimeMillis();
StringBuilder f= new StringBuilder(c.toString().replace(viejo,nuevo));//rapido de implementar pero consumo de memoria mas alto
System.out.println( System.currentTimeMillis()-inicio);
inicio=System.currentTimeMillis();
for(int i=0;i<c.length();i++){
if(viejo.substring(0,1).equals(c.substring(i,i+1))){
if(viejo.length()+i <=c.length() && viejo.equals(c.substring(i,i+viejo.length()))){
c.replace(i,i+viejo.length(),nuevo);//esta instruccion parece O(n)
}
}
}
System.out.println( System.currentTimeMillis()-inicio);
Ya no te entendí ¿cual es
Ya no te entendí ¿cual es cual?
Ese for...
Ese for con dos if's... creo que los dos if's sobran (bueno uno sobra realmente). Si usas
indexOf
en vez de esas comparaciones, el performance mejorará muchísimo. Sobretodo porque estás usandosubstring
3 veces en cada iteración, y ese método crea una nueva cadena cada vez que lo invocas, de modo que estás creando 3 cadenas nuevas, temporales, en cada iteración del ciclo. Yo por eso uséindexOf
porque ese método hace internamente toda la búsqueda y te da la primera posición donde aparece el código.BT, Haz una comparación entre el performance de ese for que pusiste en el último comentario, y el algoritmo que puse en mi primer comentario. Debe haber una gran diferencia con cadenas largas que tengan varias ocurrencias de la cadena a reemplazar.
caracter
Por otra parte, si sabes que vas a reemplazar un solo caracter por otro caracter distinto, puedes hacer una búsqueda como la de tu for pero optimizada para un solo caracter:
char nuevo = 'b';
for (int i=0; i < c.length(); i++) {
if (viejo == c.charAt(i)) {
c.setCharAt(i, nuevo);
}
}
micro-Benchmarck
String nuevo="b";
StringBuilder c = new StringBuilder("");
int tam=100000;
//algoritmo A
for(int i=0;i<tam;i++)
c.append("a");
long inicio=System.currentTimeMillis();
StringBuilder f= new StringBuilder(c.toString().replace(viejo,nuevo));//rapido de implementar pero consumo de memoria mas alto
System.out.println( System.currentTimeMillis()-inicio);
//algoritmo B
inicio=System.currentTimeMillis();
for(int i=0;i<c.length();i++){
if(viejo.substring(0,1).equals(c.substring(i,i+1))){
if(viejo.length()+i <=c.length() && viejo.equals(c.substring(i,i+viejo.length()))){
c.replace(i,i+viejo.length(),nuevo);
}
}
}
System.out.println( System.currentTimeMillis()-inicio);
c =new StringBuilder("");
for(int i=0;i<tam;i++)
c.append("a");
//algoritmo C
inicio=System.currentTimeMillis();
int pos = c.indexOf(viejo);
while (pos >= 0) {
c.replace(pos, pos+1, nuevo);
pos = c.indexOf(viejo, pos);
}
System.out.println( System.currentTimeMillis()-inicio);
tamaño de cadena1000
A 37ms
B 22ms
C 10ms
10000
A 70ms
B 88ms
C 73ms
100000
A 186 ms
B 4094 ms
C 5010 ms
200000
A 288ms
B 17628ms
C 19705ms
replace de StringBuilder es bastante lento para cadenas grandes.
replace
String.replace
solamente reemplaza un caracter, cierto? Entonces la versión que deberías usar con StringBuilder es el último algoritmo que puse, el que trabaja directo con chars, a ver qué tal funciona ese:for (int i=0; i < c.length(); i++) {
if (viejo == c.charAt(i)) {
c.setCharAt(i, nuevo);
}
}
y para los benchmarks, recuerda que siempre es necesario correr una vez todo el código que vas a benchmarkear y desechar esos tiempos, porque la primera ejecución es más lenta debido al JIT; luego ya corres el benchmark bien (todo eso en la misma ejecución del programa, para que sea la misma JVM obviamente).
mini-benchmark
public static void main(String args []){
String viejo="a";
String nuevo="b";
StringBuilder f = new StringBuilder("hola");
A(f,"a","b");
B(f,"a","b");
C(f,"a","b");
D(f,'a','b');
String [] casos = {"aaaaaaaaaaaa","aaaabbbbaaaa","dwqdqwdqddda","dqwwrdsfgdgg"};
int tam=100;
for(int n=0;n<10;n++){
for(int i=0; i<4;i++){
long a=0;
long b=0;
long c=0;
long d=0;
for(int j=0; j<20;j++){
a+= A(Gen(casos[i],tam),"a","b");
}
for(int j=0; j<20;j++){
b+= B(Gen(casos[i],tam),"a","b");
}
for(int j=0; j<20;j++){
c+= C(Gen(casos[i],tam),"a","b");
}
for(int j=0; j<20;j++){
d+= D(Gen(casos[i],tam),'a','b');
}
System.out.println(n+" "+"Tamaño string "+tam*casos[i].length()+" "+casos[i]);
System.out.println("A"+" "+ a);
System.out.println("B"+" "+ b);
System.out.println("C"+" "+ c);
System.out.println("D"+" "+d);
System.out.println();
}
tam*=2;
}
}
public static StringBuilder Gen(String patron,int rep){
StringBuilder f= new StringBuilder();
for(int i=0;i<rep;i++){
f.append(patron);
}
return f;
}
public static long A(StringBuilder c,String nuevo,String viejo){
long inicio=System.currentTimeMillis();
StringBuilder f= new StringBuilder(c.toString().replace(viejo,nuevo));
return System.currentTimeMillis()-inicio;
}
public static long B(StringBuilder c,String nuevo,String viejo){
long inicio=System.currentTimeMillis();
for(int i=0;i<c.length();i++){
if(viejo.substring(0,1).equals(c.substring(i,i+1))){
if(viejo.length()+i <=c.length() && viejo.equals(c.substring(i,i+viejo.length()))){
c.replace(i,i+viejo.length(),nuevo);
}
}
}
return ( System.currentTimeMillis()-inicio);
}
public static long C(StringBuilder c,String nuevo,String viejo){
long inicio=System.currentTimeMillis();
int pos = c.indexOf(viejo);
while (pos >= 0) {
c.replace(pos, pos+1, nuevo);
pos = c.indexOf(viejo, pos);
}
return System.currentTimeMillis()-inicio;
}
public static long D(StringBuilder c,char nuevo,char viejo){
long inicio=System.currentTimeMillis();
for (int i=0; i < c.length(); i++) {
if (viejo == c.charAt(i)) {
c.setCharAt(i, nuevo);
}
}
return System.currentTimeMillis()-inicio;
}
}
D es realmente rapido, ahora usara setCharAt.