¿Eliminar el último carácter de un StringBuilder?

423

Cuando tiene que recorrer una colección y hacer una cadena de cada dato separada por un delimitador, siempre termina con un delimitador adicional al final, por ej.

for (String serverId : serverIds) {
  sb.append(serverId);
   sb.append(",");
}

Da algo como: serverId_1, serverId_2, serverId_3,

Me gustaría eliminar el último carácter en el StringBuilder (sin convertirlo porque todavía lo necesito después de este ciclo).

Mateo
fuente
11
Si al unir cadenas quiere decir "concatenación de cadenas", depende de la cantidad de cadenas y sus longitudes. El uso de un generador de cadenas es más eficiente si va a martillar muchas cadenas independientemente de su tamaño, ya que las cadenas son inmutables. Cada vez que concatena cadenas juntas, está creando una nueva cadena resultante (que es realmente una matriz de caracteres). Los constructores de cadenas son esencialmente una lista de caracteres que no se convierten en una cadena inmutable hasta que llame al método toString ().
dyslexicanaboko
44
Si está utilizando Java 8, simplemente use StringJoiner: stackoverflow.com/a/29169233/901641
ArtOfWarfare

Respuestas:

640

Otros han señalado el deleteCharAtmétodo, pero aquí hay otro enfoque alternativo:

String prefix = "";
for (String serverId : serverIds) {
  sb.append(prefix);
  prefix = ",";
  sb.append(serverId);
}

Alternativamente, use la Joinerclase de Guava :)

A partir de Java 8, StringJoinerforma parte del JRE estándar.

Jon Skeet
fuente
77
@Coronatus: No, porque "" es la ausencia de caracteres, no un solo carácter.
Jon Skeet
31
no ejecutará el prefijo = ","; Cada ciclo de bucle afecta el rendimiento?
Harish
21
@ Harish: Posiblemente, un poquito, un poquito, aunque es muy poco probable que sea significativo.
Jon Skeet
44
@Harish, y posiblemente no, si el optimizador desenrolla la primera iteración del bucle.
Stephen C
66
Apache Commons también tiene otra alternativa a la guayaba Joineren su StringUtils. commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… , java.lang.String)
GoRoS
419

Otra solución simple es:

sb.setLength(sb.length() - 1);

Una solución más complicada:

La solución anterior supone que sb.length() > 0... es decir, hay un "último carácter" para eliminar. Si no puede hacer esa suposición, y / o no puede lidiar con la excepción que se produciría si la suposición es incorrecta, primero verifique la longitud del StringBuilder; p.ej

// Readable version
if (sb.length() > 0) {
   sb.setLength(sb.length() - 1);
}

o

// Concise but harder-to-read version of the above.
sb.setLength(Math.max(sb.length() - 1, 0));
Stephen C
fuente
23
Muy buena solución. Impacto más bajo en el rendimiento y menos código requerido :)
Alain O'Dea
186
if(sb.length() > 0){
    sb.deleteCharAt(sb.length() - 1);
}
fanfarrón
fuente
33
Esto se votó demasiado, pero no es eficiente, hace una system.arraycopy. Lo que dijo @Rohit Reddy Korrapolu.
alianos-
13
Es inseguro para sb.length() == 0también
Matthias
¿Es esto seguro con personajes sustitutos en juego?
rogerdpack
Suponiendo que el último carácter es el separador de coma (como en el ejemplo), los sustitutos no hacen ninguna diferencia. Si necesita generalizar, reste en separator.length()lugar de 1.
Stephen C
61

A partir de Java 8, la clase String tiene un método estático join. El primer argumento es una cadena que desea entre cada par de cadenas, y el segundo es un Iterable<CharSequence>(que son ambas interfaces, por lo que algo así List<String>funciona. Así que puede hacer esto:

String.join(",", serverIds);

También en Java 8, podría usar la nueva StringJoinerclase, para escenarios en los que desea comenzar a construir la cadena antes de tener la lista completa de elementos para incluir.

ArtOfWarfare
fuente
nvm editado para usted si no le importa, también eliminó mi comentario
Eugene
@Eugene: reescribí la respuesta por completo para centrarme en String.joinlugar de hacerlo StringJoiner.
ArtOfWarfare
37

Simplemente obtenga la posición de la última aparición del personaje.

for(String serverId : serverIds) {
 sb.append(serverId);
 sb.append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));

Ya que lastIndexOf que realizará una búsqueda inversa, y sabe que lo encontrará en el primer intento, el rendimiento no será un problema aquí.

EDITAR

Dado que sigo recibiendo mi respuesta (gracias amigos 😊), vale la pena considerar eso:

En Java 8 en adelante, sería más legible y explícito usar StringJoiner . Tiene un método para un separador simple y una sobrecarga para el prefijo y el sufijo.

Ejemplos tomados de aquí: ejemplo

Ejemplo usando un separador simple:

    StringJoiner mystring = new StringJoiner("-");    

    // Joining multiple strings by using add() method  
    mystring.add("Logan");  
    mystring.add("Magneto");  
    mystring.add("Rogue");  
    mystring.add("Storm");  

    System.out.println(mystring);

Salida:

Logan-Magneto-Rogue-Storm

Ejemplo con sufijo y prefijo:

    StringJoiner mystring = new StringJoiner(",", "(", ")");    

    // Joining multiple strings by using add() method  
    mystring.add("Negan");  
    mystring.add("Rick");  
    mystring.add("Maggie");  
    mystring.add("Daryl");  

    System.out.println(mystring);

Salida

(Negan, Rick, Maggie, Daryl)

Reuel Ribeiro
fuente
Estarás seguro de que el último personaje es un ,porque fue la última declaración del for loop. Esto lastInfexOfes más para la legibilidad y para que sea obvio si no desea recordar si está indexado 0 o no. Además, no necesita entrometerse con la longitud del generador de cadenas. Es solo por conveniencia.
Reuel Ribeiro
34

En este caso,

sb.setLength(sb.length() - 1);

es preferible ya que solo asigna el último valor a '\0'mientras que eliminar el último carácterSystem.arraycopy

Rohit Reddy Korrapolu
fuente
1
La setLengthllamada no está asignando nada al último valor. Los búferes de cadenas Java no son nulos / terminados en cero. De hecho, setLengthes simplemente actualizar un lengthcampo.
Stephen C
@Rohit Reddy Korrapolu: Pero arraycopycopia 0 elementos, así que supongo que se puede optimizar.
maaartinus el
2
Si el argumento newLength es mayor o igual que la longitud actual, se añaden suficientes caracteres nulos ('\ u0000') para que la longitud se convierta en el argumento newLength. Que no es el caso.
fglez
11

Otra alternativa

for(String serverId : serverIds) {
   sb.append(",");
   sb.append(serverId); 
}
sb.deleteCharAt(0);
Rafiq
fuente
2
Debería ser mejor que eliminar el último carácter ya que esto requiere cálculos de tamaño. A menos que se elimine el primer carácter, los datos se
moverán
8

Alternativamente,

StringBuilder result = new StringBuilder();
for(String string : collection) {
    result.append(string);
    result.append(',');
}
return result.substring(0, result.length() - 1) ;
Zaki
fuente
Se puede usar como puede agregar un "." al final.
artfullyContrived
6
StringBuilder sb = new StringBuilder();
sb.append("abcdef");
sb.deleteCharAt(sb.length() - 1);
assertEquals("abcde",sb.toString());
// true
Antoine
fuente
5

Otra alternativa más:

public String join(Collection<String> collection, String seperator) {
    if (collection.isEmpty()) return "";

    Iterator<String> iter = collection.iterator();
    StringBuilder sb = new StringBuilder(iter.next());
    while (iter.hasNext()) {
        sb.append(seperator);
        sb.append(iter.next());
    }

    return sb.toString();
}
Jason Day
fuente
3

Para evitar reiniciar (afectar el rendimiento) del prefixuso TextUtils.isEmpty:

            String prefix = "";
            for (String item : list) {
                sb.append(prefix);
                if (TextUtils.isEmpty(prefix))
                    prefix = ",";
                sb.append(item);
            }
NickUnuchek
fuente
¿A qué tipo de paquete pertenece TestUtils?
Markus
@Markus android.text.TextUtils
NickUnuchek
1

Puede intentar usar la clase 'Joiner' en lugar de eliminar el último carácter del texto generado;

                List<String> textList = new ArrayList<>();
                textList.add("text1");
                textList.add("text2");
                textList.add("text3");

                Joiner joiner = Joiner.on(",").useForNull("null");
                String output = joiner.join(textList);

               //output : "text1,text2,text3"
oguzhan
fuente
1

Estoy haciendo algo como a continuación:

    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < value.length; i++) {
        stringBuilder.append(values[i]);
        if (value.length-1) {
            stringBuilder.append(", ");
        }
    }
Vikasdeep Singh
fuente
0

Aquí hay otra solución:

for(String serverId : serverIds) {
   sb.append(",");
   sb.append(serverId); 
}

String resultingString = "";
if ( sb.length() > 1 ) {
    resultingString = sb.substring(1);
}
Stephan
fuente
1
Oh ya veo. Está llamando a la subcadena en StringBuilder, no a una cadena.
Stephen C
Pero de todos modos, esta es solo una pequeña variante de la solución de Zaki de 2010.
Stephen C
0

Personalmente, me gusta agregar un carácter de retroceso (o más para un "delimitador" más largo al final:

for(String serverId : serverIds) {
    sb.append(serverId);
    sb.append(",");
}

sb.append('\b');

Tenga en cuenta que tiene problemas:

  • cómo \bse muestra depende del entorno,
  • el length()del Stringcontenido puede ser diferente de la longitud de caracteres "visibile"

Cuando se \bve bien y la longitud no importa, como iniciar sesión en una consola, esto me parece lo suficientemente bueno.

Atacante
fuente
-1

stringBuilder.Remove (stringBuilder.Length - 1, 1);

Mohamed Farook Mohamed Fazrin
fuente