convert - Qual é a maneira mais fácil/melhor/mais correta de iterar os caracteres de uma string em Java?



iteration character (10)

Duas opções

for(int i = 0, n = s.length() ; i < n ; i++) { 
    char c = s.charAt(i); 
}

ou

for(char c : s.toCharArray()) {
    // process c
}

O primeiro provavelmente é mais rápido, depois o segundo provavelmente é mais legível.

StringTokenizer ? Converter a String para um char[] e iterar sobre isso? Algo mais?


Answer #1

Elaborando esta resposta e esta resposta .

As respostas acima apontam o problema de muitas das soluções aqui que não são iteradas pelo valor do ponto de código - elas teriam problemas com quaisquer caracteres substitutos . Os documentos java também descrevem o problema here (consulte "Representações de caracteres Unicode"). De qualquer forma, aqui está um código que usa alguns chars surrogate reais do conjunto suplementar do Unicode e os converte de volta para um String. Note que .toChars () retorna uma matriz de chars: se você está lidando com substitutos, você necessariamente terá dois chars. Este código deve funcionar para qualquer caractere Unicode.

    String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
    supplementary.codePoints().forEach(cp -> 
            System.out.print(new String(Character.toChars(cp))));

Answer #2

Eu concordo que o StringTokenizer é um exagero aqui. Na verdade, tentei as sugestões acima e aproveitei o tempo.

Meu teste foi bastante simples: criar um StringBuilder com cerca de um milhão de caracteres, convertê-lo em uma String e atravessar cada um deles com charAt () / após converter para uma matriz char / com um CharacterIterator mil vezes (claro, certificando-se de faça algo na string para que o compilador não possa otimizar todo o loop :-)).

O resultado no meu Powerbook de 2,6 GHz (que é um mac :-)) e no JDK 1.5:

  • Teste 1: charAt + String -> 3138msec
  • Teste 2: String convertida em array -> 9568msec
  • Teste 3: StringBuilder charAt -> 3536msec
  • Teste 4: CharacterIterator e String -> 12151msec

Como os resultados são significativamente diferentes, o caminho mais direto também parece ser o mais rápido. Curiosamente, charAt () de um StringBuilder parece ser um pouco mais lento que o de String.

BTW, sugiro não usar CharacterIterator como eu considero seu abuso do caractere '\ uFFFF' como "fim da iteração" um hack realmente horrível. Em grandes projetos, há sempre dois caras que usam o mesmo tipo de invasão para duas finalidades diferentes e o código falha de forma realmente misteriosa.

Aqui está um dos testes:

    int count = 1000;
    ...

    System.out.println("Test 1: charAt + String");
    long t = System.currentTimeMillis();
    int sum=0;
    for (int i=0; i<count; i++) {
        int len = str.length();
        for (int j=0; j<len; j++) {
            if (str.charAt(j) == 'b')
                sum = sum + 1;
        }
    }
    t = System.currentTimeMillis()-t;
    System.out.println("result: "+ sum + " after " + t + "msec");

Answer #3

Eu não usaria StringTokenizer como é uma das classes no JDK que é legado.

O javadoc diz:

StringTokenizer é uma classe herdada que é retida por motivos de compatibilidade, embora seu uso seja desencorajado em um novo código. Recomenda-se que qualquer pessoa que busque essa funcionalidade use o método de divisão de String ou o pacote java.util.regex .


Answer #4

Existem algumas classes dedicadas para isso:

import java.text.*;

final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
   // process c
   ...
}

Answer #5

No Java 8 , podemos resolvê-lo como:

String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));

O método chars () retorna um IntStream conforme mencionado no CharSequence#chars :

Retorna um fluxo de int estendendo zero os valores char desta seqüência. Qualquer caractere mapeado para um ponto de código substituto é passado por não interpretado. Se a sequência sofrer mutação enquanto o fluxo estiver sendo lido, o resultado será indefinido.

O método codePoints() também retorna um IntStream conforme o documento:

Retorna um fluxo de valores de ponto de código dessa sequência. Todos os pares substitutos encontrados na sequência são combinados como se fossem por Character.toCodePoint e o resultado é passado para o fluxo. Quaisquer outras unidades de código, incluindo caracteres BMP comuns, substitutos desemparelhados e unidades de código indefinidas, são estendidas como zero para valores int que são então passados ​​para o fluxo.

Como o char e o ponto de código são diferentes? Como mencionado this artigo:

O Unicode 3.1 adicionou caracteres suplementares, elevando o número total de caracteres a mais do que os 216 caracteres que podem ser distinguidos por um único caractere de 16 bits. Portanto, um valor char não possui mais um mapeamento um para um para a unidade semântica fundamental em Unicode. O JDK 5 foi atualizado para suportar o maior conjunto de valores de caracteres. Em vez de alterar a definição do tipo char , alguns dos novos caracteres suplementares são representados por um par substituto de dois valores char . Para reduzir a confusão de nomenclatura, um ponto de código será usado para se referir ao número que representa um caractere Unicode específico, incluindo os adicionais.

Finalmente por que forEachOrdered e não forEach ?

O comportamento de forEach é explicitamente não determinístico em que o forEachOrdered executa uma ação para cada elemento desse fluxo, na ordem de encontro do fluxo, se o fluxo tiver uma ordem de encontro definida. Então, para forEach não garante que a ordem seria mantida. Além disso, verifique esta question para mais.

Para a diferença entre um caractere, um ponto de código, um glifo e um grafema, verifique esta question .


Answer #6

Observe que a maioria das outras técnicas descritas aqui se divide se você estiver lidando com caracteres fora do BMP (Unicode Basic Multilingual Plane ), isto é, pontos de código que estão fora do intervalo u0000-uFFFF. Isso só acontecerá raramente, já que os pontos de código fora dele são principalmente atribuídos a idiomas mortos. Mas existem alguns caracteres úteis fora disso, por exemplo, alguns pontos de código usados ​​para notação matemática e alguns usados ​​para codificar nomes próprios em chinês.

Nesse caso, seu código será:

String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
  int curChar = str.codePointAt(offset);
  offset += Character.charCount(curChar);
  // do something with curChar
}

O método Character.charCount(int) requer o Java 5+.

Fonte: http://mindprod.com/jgloss/codepoint.html


Answer #7

Se você precisa de desempenho, então você deve testar em seu ambiente. Não há outro jeito.

Aqui código de exemplo:

int tmp = 0;
String s = new String(new byte[64*1024]);
{
    long st = System.nanoTime();
    for(int i = 0, n = s.length(); i < n; i++) {
        tmp += s.charAt(i);
    }
    st = System.nanoTime() - st;
    System.out.println("1 " + st);
}

{
    long st = System.nanoTime();
    char[] ch = s.toCharArray();
    for(int i = 0, n = ch.length; i < n; i++) {
        tmp += ch[i];
    }
    st = System.nanoTime() - st;
    System.out.println("2 " + st);
}
{
    long st = System.nanoTime();
    for(char c : s.toCharArray()) {
        tmp += c;
    }
    st = System.nanoTime() - st;
    System.out.println("3 " + st);
}
System.out.println("" + tmp);

No Java online eu recebo:

1 10349420
2 526130
3 484200
0

Na API do Android x86 17, recebo:

1 9122107
2 13486911
3 12700778
0

Answer #8

Se você tiver o Guava no seu caminho de classe, o seguinte é uma alternativa bastante legível. Guava até tem uma implementação de lista customizada razoavelmente sensata para este caso, então isso não deve ser ineficiente.

for(char c : Lists.charactersOf(yourString)) {
    // Do whatever you want     
}

ATUALIZAÇÃO: Como o @Alex observou, com o Java 8, também há CharSequence#chars para usar. Mesmo o tipo é IntStream, então ele pode ser mapeado para caracteres como:

yourString.chars()
        .mapToObj(c -> Character.valueOf((char) c))
        .forEach(c -> System.out.println(c)); // Or whatever you want

Answer #9

Veja os Tutoriais Java: Strings .

public class StringDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
        int len = palindrome.length();
        char[] tempCharArray = new char[len];
        char[] charArray = new char[len];

        // put original string in an array of chars
        for (int i = 0; i < len; i++) {
            tempCharArray[i] = palindrome.charAt(i);
        } 

        // reverse array of chars
        for (int j = 0; j < len; j++) {
            charArray[j] = tempCharArray[len - 1 - j];
        }

        String reversePalindrome =  new String(charArray);
        System.out.println(reversePalindrome);
    }
}

Coloque o comprimento em int len e use for loop.





tokenize