Wat is de gemakkelijkste/beste/meest correcte manier om door de karakters van een string in Java te bladeren?

StringTokenizer? Converteer de String naar een char[] en herhaal dat? Iets anders?


Antwoord 1, autoriteit 100%

Ik gebruik een for-lus om de string te herhalen en gebruik charAt() om elk teken het te laten onderzoeken. Aangezien de String is geïmplementeerd met een array, is de methode charAt() een bewerking met een constante tijd.

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

Dat is wat ik zou doen. Het lijkt mij het gemakkelijkst.

Wat de juistheid betreft, geloof ik niet dat dat hier bestaat. Het is allemaal gebaseerd op je persoonlijke stijl.


Antwoord 2, autoriteit 58%

Twee opties

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

of

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

De eerste is waarschijnlijk sneller, de tweede is waarschijnlijk beter leesbaar.


Antwoord 3, autoriteit 23%

Merk op dat de meeste andere technieken die hier worden beschreven, niet werken als je te maken hebt met tekens buiten de BMP (Unicode Basis meertalig vlak), dwz codepunten die buiten de u0000 liggen -uFFFF-bereik. Dit zal slechts zelden gebeuren, omdat de codepunten daarbuiten meestal worden toegewezen aan dode talen. Maar daarbuiten zijn er enkele nuttige tekens, bijvoorbeeld enkele codepunten die worden gebruikt voor wiskundige notatie, en sommige die worden gebruikt om eigennamen in het Chinees te coderen.

In dat geval is uw code:

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

De Character.charCount(int) methode vereist Java 5+.

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


Antwoord 4, autoriteit 7%

In Java 8 kunnen we het oplossen als:

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

De methode chars() retourneert een IntStream zoals vermeld in doc:

Retourneert een stroom van int nul die de char-waarden hiervan uitbreidt
volgorde. Elke char die is toegewezen aan een surrogaatcodepunt wordt doorgegeven
door ongeinterpreteerd. Als de sequentie is gemuteerd terwijl de stream is
wordt gelezen, is het resultaat niet gedefinieerd.

De methode codePoints() retourneert ook een IntStream volgens doc:

Retourneert een stroom codepuntwaarden uit deze reeks. Ieder
surrogaatparen die in de reeks worden aangetroffen, worden gecombineerd alsof
Character.toCodePoint en het resultaat wordt doorgegeven aan de stream. Ieder
andere code-eenheden, inclusief gewone BMP-tekens, ongepaard
surrogaten en ongedefinieerde code-eenheden zijn nul-uitgebreid naar int-waarden
die vervolgens worden doorgegeven aan de stream.

Hoe verschillen char en codepoint? Zoals vermeld in dit artikel:

Unicode 3.1 heeft aanvullende tekens toegevoegd, waardoor het totale aantal is bereikt
van tekens tot meer dan de 2^16 = 65536 tekens die kunnen zijn
onderscheiden door een enkele 16-bits char. Daarom is een char waarde no
langer heeft een één-op-één mapping naar de fundamentele semantische eenheid in
Unicode. JDK 5 is bijgewerkt om de grotere tekenset te ondersteunen
waarden. In plaats van de definitie van het type char te wijzigen, hebben sommige
de nieuwe aanvullende karakters worden vertegenwoordigd door een surrogaatpaar
van twee char waarden. Om naamsverwarring te verminderen, wordt een codepunt:
gebruikt om te verwijzen naar het nummer dat een bepaalde Unicode vertegenwoordigt
karakter, inclusief aanvullende.

Ten slotte, waarom forEachOrdered en niet forEach ?

Het gedrag van forEach is expliciet niet-deterministisch waarbij de forEachOrdered een actie uitvoert voor elk element van deze stream, in de ontmoetingsvolgorde van de stream als de stream een ​​gedefinieerde ontmoetingsvolgorde heeft. Dus forEach garandeert niet dat de bestelling behouden blijft. Bekijk ook deze vraag voor meer informatie.

Voor verschil tussen een teken, een codepunt, een glyph en een grafeem controleer dit vraag.


Antwoord 5, autoriteit 7%

Ik ben het ermee eens dat StringTokenizer hier overdreven is. Eigenlijk heb ik de bovenstaande suggesties uitgeprobeerd en de tijd genomen.

Mijn test was vrij eenvoudig: maak een StringBuilder met ongeveer een miljoen karakters, converteer het naar een String, en doorloop elk van hen met charAt() / na duizend keer converteren naar een char array / met een CharacterIterator (natuurlijk ervoor zorgen dat je iets aan de string doet, zodat de compiler de hele lus niet kan optimaliseren 🙂 ).

Het resultaat op mijn 2.6 GHz Powerbook (dat is een mac 🙂 ) en JDK 1.5:

  • Test 1: charAt + String –> 3138msec
  • Test 2: String geconverteerd naar array –> 9568msec
  • Test 3: StringBuilder charAt –> 3536msec
  • Test 4: CharacterIterator en String –> 12151msec

Omdat de resultaten aanzienlijk verschillen, lijkt de eenvoudigste manier ook de snelste te zijn. Interessant is dat charAt() van een StringBuilder iets langzamer lijkt te zijn dan die van String.

Trouwens, ik stel voor om CharacterIterator niet te gebruiken omdat ik het misbruik van het ‘\uFFFF’-teken als “einde van de iteratie” een echt vreselijke hack beschouw. In grote projecten zijn er altijd twee jongens die dezelfde soort hack voor twee verschillende doeleinden gebruiken en de code crasht echt mysterieus.

Hier is een van de tests:

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

Antwoord 6, autoriteit 5%

Hier zijn enkele speciale lessen voor:

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

Antwoord 7, autoriteit 5%

Als je Guava op je klassenpad hebt staan, is het volgende een redelijk leesbaar alternatief . Guava heeft zelfs een redelijk verstandige aangepaste List-implementatie voor dit geval, dus dit zou niet inefficiënt moeten zijn.

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

UPDATE: Zoals @Alex opmerkte, is er met Java 8 ook CharSequence#chars te gebruiken. Zelfs het type is IntStream, dus het kan worden toegewezen aan tekens zoals:

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

Antwoord 8, autoriteit 3%

Als u de codepunten van een String moet doorlopen (zie dit antwoord ) een kortere / beter leesbare manier is om de CharSequence#codePoints methode toegevoegd in Java 8:

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

of de stream rechtstreeks gebruiken in plaats van een for-lus:

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

Er is ook CharSequence#chars als u een stroom van de tekens wilt (hoewel het een IntStream is, aangezien er geen CharStream is).


Antwoord 9

Ik zou StringTokenizer niet gebruiken omdat het een van de klassen in de JDK is die verouderd is.

De javadoc zegt:

StringTokenizer is een legacy-klasse die
wordt bewaard om compatibiliteitsredenen
hoewel het gebruik ervan wordt afgeraden in nieuwe
code. Het wordt aanbevolen dat iedereen
op zoek naar deze functionaliteit gebruik de
split-methode van String of de
java.util.regex pakket in plaats daarvan.


Antwoord 10

Als je prestaties nodig hebt, moet je testen op je omgeving. Geen andere manier.

Hier voorbeeldcode:

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

Op Java online krijg ik:

1 10349420
2 526130
3 484200
0

Op Android x86 API 17 krijg ik:

1 9122107
2 13486911
3 12700778
0

Antwoord 11

Zie De Java-tutorials: 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);
    }
}

Voer de lengte in int len en gebruik de for lus.


Antwoord 12

StringTokenizer is totaal ongeschikt om een ​​string op te splitsen in zijn individuele karakters. Met String#split() kun je dat eenvoudig doen door een regex te gebruiken die nergens mee overeenkomt, bijvoorbeeld:

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

Maar StringTokenizer gebruikt geen regexes, en er is geen scheidingstekenreeks die u kunt specificeren die overeenkomt met het niets tussen tekens. Er is één schattige kleine hack die je kunt gebruiken om hetzelfde te bereiken: gebruik de tekenreeks zelf als de scheidingstekenreeks (waardoor elk teken erin een scheidingsteken wordt) en laat deze de scheidingstekens retourneren:

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

Ik noem deze opties echter alleen om ze af te wijzen. Beide technieken splitsen de originele string op in strings van één teken in plaats van char-primitieven, en beide brengen veel overhead met zich mee in de vorm van het maken van objecten en het manipuleren van strings. Vergelijk dat met het aanroepen van charAt() in een for-lus, wat vrijwel geen overhead met zich meebrengt.


Antwoord 13

Uitwerken van dit antwoord en dit antwoord.

Bovenstaande antwoorden wijzen op het probleem van veel van de oplossingen hier die niet op codepuntwaarde worden herhaald — ze zouden problemen hebben met alle surrogaattekens. De Java-documenten beschrijven het probleem ook hier (zie “Unicode-tekenweergaven”). Hoe dan ook, hier is wat code die enkele echte surrogaattekens uit de aanvullende Unicode-set gebruikt en deze terug naar een tekenreeks converteert. Merk op dat .toChars() een array van tekens retourneert: als je te maken hebt met surrogaten, heb je noodzakelijkerwijs twee tekens. Deze code zou moeten werken voor elk Unicode-teken.

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

Antwoord 14

Deze voorbeeldcode zal je helpen!

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

Antwoord 15

Dus meestal zijn er twee manieren om door een string in Java te bladeren die al door meerdere mensen hier in deze thread is beantwoord, gewoon door mijn versie ervan toe te voegen
De eerste is het gebruik van

String s = sc.next() // assuming scanner class is defined above
for(int i=0; i<s.length; i++){
     s.charAt(i)   // This being the first way and is a constant time operation will hardly add any overhead
  }
char[] str = new char[10];
str = s.toCharArray() // this is another way of doing so and it takes O(n) amount of time for copying contents from your string class to character array

Als de prestatie op het spel staat, raad ik aan om de eerste in constante tijd te gebruiken, als dat niet het geval is, maakt het werken met de tweede je werk gemakkelijker gezien de onveranderlijkheid met stringklassen in java.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes