Was ist der einfachste / besten / richtige Weg durch die Zeichen einer Zeichenfolge in Java zu wiederholen?
Frage
StringTokenizer
? Konvertieren Sie die String
zu einem char[]
und iterieren das? Etwas anderes?
Lösung
Ich verwende eine for-Schleife den String iterieren und verwenden charAt()
jedes Zeichen, um es zu untersuchen. Da der String mit einem Array implementiert wird, ist die Methode charAt()
ein konstanter Zeitbetrieb.
String s = "...stuff...";
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
//Process char
}
Das ist, was ich tun würde. Es scheint, die am einfachsten zu mir.
Soweit Korrektheit geht, ich glaube nicht, dass existiert hier. Es ist ganz auf Ihren persönlichen Stil.
Andere Tipps
Zwei Optionen
for(int i = 0, n = s.length() ; i < n ; i++) {
char c = s.charAt(i);
}
oder
for(char c : s.toCharArray()) {
// process c
}
Die erste ist wahrscheinlich schneller, dann zweite ist wahrscheinlich besser lesbar.
Beachten Sie die meisten anderen hier beschriebenen Techniken zusammenbrechen, wenn Sie mit den Charakteren zu tun hat außerhalb der BMP (Unicode Basic Multilingual Plane ), dh Codepunkte , die außerhalb des u0000 sind -uFFFF Bereich. Dies wird nur selten vorkommen, da die Codepunkte außerhalb dieser meist zu toten Sprachen zugeordnet sind. Aber es gibt einige nützliche Zeichen außerhalb dieser, zum Beispiel einige Codepunkte für mathematische Schreibweise verwendet, und einige verwendet, um Eigennamen in chinesischen zu kodieren.
In diesem Fall Ihr Code sein:
String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
int curChar = str.codePointAt(offset);
offset += Character.charCount(curChar);
// do something with curChar
}
Die Character.charCount(int)
Methode erfordert Java 5 +.
Ich bin damit einverstanden, dass StringTokenizer Overkill hier. Eigentlich habe ich versucht, über die Vorschläge aus und nahm sich die Zeit.
war mein Test recht einfach: einen String mit etwa einer Million Zeichen erzeugt, wandelt es in einen String, und durchquert jeweils mit charAt () / nach zu einem char-Array konvertieren / mit einem CharacterIterator tausendmal (natürlich dafür, dass etwas auf der Saite zu tun, damit der Compiler die ganze Schleife :-) nicht optimiert entfernt werden).
Das Ergebnis auf meinem 2,6 GHz Powerbook (das ist ein Mac :-)) und JDK 1.5:
- Test 1: charAt + String -> 3138msec
- Test 2: String Array konvertiert -> 9568msec
- Test 3: String charAt -> 3536msec
- Test 4: CharacterIterator und String -> 12151msec
Wie die Ergebnisse signifikant verschieden sind, die einfachste Art und Weise scheint auch die schnellste zu sein. Interessanterweise charAt () ein String etwas langsamer zu sein scheint als die von String.
BTW schlage ich nicht CharacterIterator zu verwenden, wie ich seinen Missbrauch des ‚\ uFFFF‘ Charakter als „Ende der Iteration“ eine wirklich schreckliche Hack betrachten. In großen Projekten gibt es immer zwei Typen, die die gleiche Art von Hack für zwei unterschiedliche Zwecke und der Code stürzt wirklich auf mysteriöse Weise verwenden.
Hier ist einer der 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");
Es gibt einige spezielle Klassen für diese:
import java.text.*;
final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
// process c
...
}
Wenn Sie Guava auf Ihrem Classpath, ist die folgende eine ziemlich lesbare Alternative . Guava hat sogar eine ziemlich vernünftige benutzerdefinierte Liste Implementierung für diesen Fall so sollte dies nicht ineffizient sein.
for(char c : Lists.charactersOf(yourString)) {
// Do whatever you want
}
UPDATE: Wie @ Alex erwähnt, mit Java 8 gibt es auch CharSequence#chars
zu verwenden. Auch der Typ IntStream, so kann es zu Zeichen abgebildet werden wie:
yourString.chars()
.mapToObj(c -> Character.valueOf((char) c))
.forEach(c -> System.out.println(c)); // Or whatever you want
In Java 8 können wir es lösen wie:
String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));
Die Methode Zeichen () gibt ein IntStream
wie in doc :
Gibt einen Strom von int Null-Erweitern der char Werte von diesem Reihenfolge. Alle Zeichen, die Karten zu einem Surrogat-Codepunkt geführt werden durch uninterpretiert. Wenn die Sequenz mutiert ist, während der Stream gelesen wird, ist das Ergebnis nicht definiert ist.
Die Methode codePoints()
gibt auch eine IntStream
gemäß doc:
Gibt einen Strom von Codepunktwerten aus dieser Sequenz. Irgendein Ersatzpaar in der Sequenz angetroffen werden kombiniert, wie von Character.toCodePoint und das Ergebnis wird in den Stream übergeben. Irgendein andere Codeeinheiten, einschließlich gewöhnlichen BMP Zeichen, ungepaarten Surrogate und undefinierte Codeeinheiten sind Null erweitert Werte in int die dann in den Stream übergeben.
Wie ist char und Codepunkt anders? Wie bereits erwähnt in diese Artikel:
Unicode 3.1 hinzugefügt zusätzliche Zeichen, die Gesamtzahl bringen von Zeichen auf mehr als die 216 Zeichen, die sein kann, durch einen einzigen 16-Bit-
char
unterschieden. Daher ist einchar
Wert nein mehr hat eine Eins-zu-Eins-Zuordnung zu der Grundeinheit in semantischen Unicode. JDK 5 wurde aktualisiert, um die größere Menge von Zeichen zu unterstützen Werte. Anstatt die Definition derchar
Art zu ändern, einig die neuen zusätzlichen Zeichen werden durch ein Ersatzpaar repräsentiert von zweichar
Werte. Zur Reduzierung der Benennung Verwirrung, wird ein Codepunkt sein verwendet, um die Zahl zu entnehmen, die eine bestimmte Unicode darstellt Charakter, einschließlich dem ergänzend denjenigen.
Schließlich warum forEachOrdered
und nicht forEach
?
Das Verhalten von forEach
ist ausdrücklich nicht deterministisch, wo als die forEachOrdered
für jedes Element dieses Stroms eine Aktion ausführt, in der Begegnung Reihenfolge des Stroms , wenn der Strom eine definierte Begegnung Ordnung hat. So forEach
garantiert nicht, dass die Reihenfolge gehalten werden würde. Überprüfen Sie auch diese Frage für mehr.
Unterschied zwischen einem Charakter, ein Codepunkt, eine Glyphe und einem Graphem check this Frage .
Wenn Sie durch die Codepunkte eines String
iterieren müssen (siehe diese Antwort ) eine kürzere / lesbare Weise ist die CharSequence#codePoints
Verfahren hinzugefügt in Java 8:
for(int c : string.codePoints().toArray()){
...
}
oder den Strom direkt anstelle einer Verwendung für die Schleife:
string.codePoints().forEach(c -> ...);
Es gibt auch CharSequence#chars
, wenn Sie einen Stream der Charaktere wollen (obwohl es ein IntStream
ist, da es keine CharStream
ist).
Ich würde nicht verwenden StringTokenizer
, da es eines der Klassen in der JDK ist die Legacyinhalte.
Die javadoc sagt:
StringTokenizer
ist ein Vermächtnis-Klasse, wird aus Kompatibilitätsgründen beibehalten obwohl seine Verwendung in neuen entmutigt Code. Es wird empfohlen, dass jeder sucht diese Funktionalität nutzen die Spaltverfahren vonString
oderjava.util.regex
Paket statt.
Wenn Sie die Leistung benötigen, dann Sie testen müssen auf Ihre Umgebung. Keine andere Art und Weise.
Hier Beispielcode:
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);
Java Online ich:
1 10349420
2 526130
3 484200
0
Auf Android-x86-API 17 erhalte ich:
1 9122107
2 13486911
3 12700778
0
Siehe Die 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);
}
}
Setzen Sie die Länge in int len
und for
Schleife verwenden.
StringTokenizer ist völlig ungeeignet für die Aufgabe eine Zeichenfolge in seine einzelnen Zeichen zu brechen. Mit String#split()
können Sie das leicht tun, indem Sie einen regulären Ausdruck verwenden, die nichts übereinstimmt, z.
String[] theChars = str.split("|");
Aber StringTokenizer nicht verwendet reguläre Ausdrücke, und es gibt keine Trennzeichenfolge Sie, dass angeben können die nichts zwischen den Zeichen übereinstimmen. Es ist ein nette kleines Hack Sie die gleiche Sache zu tun verwenden können: Verwenden Sie die Zeichenfolge selbst als Trennzeichen-String (was jedes Zeichen in ihm ein Trennzeichen) und hat es die Trennzeichen zurück:
StringTokenizer st = new StringTokenizer(str, str, true);
Allerdings habe ich erwähnen, diese Optionen nur für den Zweck, sie zu entlassen. Beide Techniken brechen die ursprüngliche Zeichenfolge in ein Zeichenketten anstelle von char Primitiven, und beide beinhalten sehr viel Aufwand in Form von Objekterstellung und String-Manipulation. Vergleichen Sie das mit Aufruf charAt () in einer for-Schleife, die praktisch kein Aufwand entstehen.
diese Antwort und Surrogat Zeichen . Die Java-docs umreißen auch die Frage hier (siehe "Unicode Zeichendarstellungen"). Wie auch immer, hier ist ein Code, der einige tatsächlichen Surrogat Zeichen aus dem ergänzenden Unicode Satz verwendet, und wandelt sie zurück in einen String. Beachten Sie, dass .toChars () ein Array von Zeichen zurück: Wenn Sie mit Surrogaten zu tun haben, Sie müssen zwei Zeichen haben werden. Dieser Code sollte für Arbeit jeder Unicode-Zeichen.
String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
supplementary.codePoints().forEach(cp ->
System.out.print(new String(Character.toChars(cp))));
Dieses Beispiel-Code wird Ihnen helfen, aus!
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);
}
}