Qual é a melhor maneira mais fácil / / mais correta para percorrer os caracteres de uma string em Java?

StackOverflow https://stackoverflow.com/questions/196830

Pergunta

StringTokenizer? Converter o String a um char[] e iterar sobre isso? Outra coisa?

Foi útil?

Solução

Eu uso um loop for para percorrer a corda e uso charAt() para obter cada personagem para examiná-lo. Desde a String é implementado com uma matriz, o método charAt() é uma operação de tempo constante.

String s = "...stuff...";

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

Isso é o que eu faria. Parece que o mais fácil para mim.

Quanto correção vai, eu não acredito que existe aqui. É tudo baseado em seu estilo pessoal.

Outras dicas

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, então segundo é provavelmente mais legível.

Nota maioria das outras técnicas descritas aqui quebrar se você está lidando com caracteres fora do BMP (Unicode Multilingual Básico Plane ), ou seja, pontos de código que estão fora do u0000 -uFFFF gama. Isso só vai acontecer raramente, uma vez que os pontos de código fora deste são principalmente atribuídos a línguas mortas. Mas existem alguns caracteres úteis fora isso, por exemplo, alguns pontos de código utilizado para notação matemática, e alguns usado para codificar os nomes próprios em chinês.

Nesse caso, o 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 Java 5 +.

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

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

Meu teste era bastante simples: criar um StringBuilder com cerca de um milhão de caracteres, convertê-lo em um String, e percorrer cada um deles com charAt () / após a conversão para uma matriz de char / com um CharacterIterator mil vezes (claro certificando-se de fazer algo na corda para que o compilador não pode otimizar afastado todo o ciclo :-)).

O resultado no meu 2.6 GHz Powerbook (que é um mac :-)) e JDK 1.5:

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

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

BTW eu não sugerem a utilização CharacterIterator como eu considerar o seu abuso do '\ uFFFF' personagem como "fim da iteração" um hack realmente horrível. Em grandes projetos há sempre dois caras que usam o mesmo tipo de corte para duas finalidades diferentes e o código de falha realmente misteriosamente.

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");

Existem algumas classes dedicados para isso:

import java.text.*;

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

Se você tiver Goiaba no seu classpath, o seguinte é uma alternativa bastante legível . Goiaba ainda tem uma implementação de lista personalizada bastante sensata para este caso, de modo que este não deve ser ineficiente.

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

UPDATE: Como @Alex observou, com Java 8 também há CharSequence#chars de usar. Mesmo o tipo é IntStream, por isso podem ser mapeados para caracteres como:

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

Em 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));

Os caracteres método () retorna uma IntStream como mencionado em doc :

Devolve uma corrente de int zero estendendo-se os valores de resíduo carbonado deste seqüência. Qualquer caractere que mapeia para um ponto de código substituto é passado através uninterpreted. Se a sequência é mutado, enquanto a corrente é sendo lido, o resultado é indefinido.

O método codePoints() também devolve um IntStream como por doc:

Retorna um fluxo de valores de ponto de código a partir desta sequência. Qualquer pares de substituição encontrados na sequência são combinados como se pela Character.toCodePoint eo resultado é passado para o fluxo. Qualquer outras unidades de código, incluindo caracteres BMP comuns, não pareado substitutos, e unidades de código indeterminado, são zero estendido para valores int que são então passados ??para o fluxo.

Como é char e código de ponto diferente? Como mencionado em este artigo :

Unicode 3.1 caracteres suplementares adicionados, elevando o número total de caracteres para mais do que 216 caracteres que podem ser distingue-se por uma única char 16 bits. Portanto, um valor char não já tem um mapeamento um-para-um para a unidade fundamental na rede semântica Unicode. JDK 5 foi atualizado para suportar o maior conjunto de caracteres valores. Em vez de mudar a definição do tipo char, alguns dos os novos caracteres suplementares são representados por um par substituto de dois valores char. Para reduzir a confusão de nomes, um ponto de código será utilizado para referir o número que representa um determinado Unicode caráter, inclusive complementares.

Finalmente porque forEachOrdered e não forEach?

O comportamento de forEach é explicitamente não-determinístico onde, como os executa forEachOrdered uma acção de cada elemento desta corrente, no ordem encontro do fluxo se o fluxo tem uma ordem de encontro definido. Então forEach não garante que a ordem seria mantida. Além disso, verifique esta pergunta para mais.

Para diferença entre um personagem, um ponto de código, um glifo e um grafema verificar este questão .

Se você precisa para percorrer os pontos de código de um String (ver este resposta ) um caminho mais curto / mais legível é usar a CharSequence#codePoints método adicionado no Java 8:

for(int c : string.codePoints().toArray()){
    ...
}

ou usando o fluxo directamente em vez de um loop:

string.codePoints().forEach(c -> ...);

Há também CharSequence#chars se você quiser um fluxo dos personagens (embora seja uma IntStream, já que não há CharStream).

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

O javadoc diz:

StringTokenizer é uma classe legado que é retida por razões de compatibilidade embora seu uso não é recomendado em nova código. Recomenda-se que qualquer pessoa buscando esse uso da funcionalidade método de separação de String ou o pacote java.util.regex vez.

Se você precisar de performance, então você teste deve em seu ambiente. Não há outra forma.

Aqui exemplo de código:

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);

Na Java on-line eu recebo:

1 10349420
2 526130
3 484200
0

No Android x86 API 17 I get:

1 9122107
2 13486911
3 12700778
0

O Java Tutoriais: 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 ea utilização de loop for.

StringTokenizer é totalmente inadequado para a tarefa de quebrar uma string em seus caracteres individuais. Com String#split() você pode fazer isso facilmente usando um regex que partidas nada, por exemplo:.

String[] theChars = str.split("|");

Mas StringTokenizer não usa expressões regulares, e não há nenhuma corda delimitador você pode especificar que irá coincidir com o nada entre caracteres. Há é um pouco bonito cortar você pode usar para realizar a mesma coisa: usar a própria string, como a cadeia de delimitador (fazendo com que cada personagem em que um delimitador) e tê-lo retornar os delimitadores:

StringTokenizer st = new StringTokenizer(str, str, true);

No entanto, eu só mencionar estas opções para o propósito de demiti-los. Ambas as técnicas quebrar a string original em cordas de um caractere em vez de primitivos CHAR e ambos envolvem uma grande quantidade de sobrecarga na forma de criação de objetos e manipulação de cadeia. Comparar que para chamar charAt () em um loop, que incorre praticamente nenhuma sobrecarga.

esta resposta e esta resposta .

Acima responde apontar o problema de muitas das soluções aqui que não fazer iterate por valor ponto de código - que teria problemas com qualquer surrogate caracteres . Os docs java também destacar a questão aqui (ver "representações de caracteres Unicode"). De qualquer forma, aqui está algum código que usa alguns caracteres substitutos reais do conjunto Unicode suplementar, e converte-os volta para um String. Note-se que .toChars () retorna uma matriz de caracteres: se você está lidando com substitutos, você vai necessariamente ter dois caracteres. Este código deve funcionar para qualquer de caracteres Unicode.

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

Este Código Exemplo vai ajudá-lo!

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Solution {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("a", 10);
        map.put("b", 30);
        map.put("c", 50);
        map.put("d", 40);
        map.put("e", 20);
        System.out.println(map);

        Map sortedMap = sortByValue(map);
        System.out.println(sortedMap);
    }

    public static Map sortByValue(Map unsortedMap) {
        Map sortedMap = new TreeMap(new ValueComparator(unsortedMap));
        sortedMap.putAll(unsortedMap);
        return sortedMap;
    }

}

class ValueComparator implements Comparator {
    Map map;

    public ValueComparator(Map map) {
        this.map = map;
    }

    public int compare(Object keyA, Object keyB) {
        Comparable valueA = (Comparable) map.get(keyA);
        Comparable valueB = (Comparable) map.get(keyB);
        return valueB.compareTo(valueA);
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top