[英]What is the easiest/best/most correct way to iterate through the characters of a string in Java?
遍历 Java 中字符串字符的一些方法是:
StringTokenizer
?String
转换为char[]
并对其进行迭代。最简单/最好/最正确的迭代方法是什么?
我使用 for 循环来迭代字符串并使用charAt()
来获取每个字符来检查它。 由于 String 是用数组实现的,因此charAt()
方法是一个常数时间操作。
String s = "...stuff...";
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
//Process char
}
这就是我会做的。 这对我来说似乎是最简单的。
就正确性而言,我认为这里不存在。 这一切都取决于您的个人风格。
两种选择
for(int i = 0, n = s.length() ; i < n ; i++) {
char c = s.charAt(i);
}
或
for(char c : s.toCharArray()) {
// process c
}
第一个可能更快,然后第二个可能更具可读性。
请注意,如果您正在处理 BMP(Unicode 基本多语言平面)之外的字符,即 u0000-uFFFF 范围之外的代码点,则此处描述的大多数其他技术都会失效。 这只会很少发生,因为在此之外的代码点大多分配给死语言。 但是除此之外还有一些有用的字符,例如一些用于数学符号的代码点,一些用于对中文专有名称进行编码。
在这种情况下,您的代码将是:
String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
int curChar = str.codePointAt(offset);
offset += Character.charCount(curChar);
// do something with curChar
}
Character.charCount(int)
方法需要 Java 5+。
在Java 8 中,我们可以将其解决为:
String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));
方法 chars() 返回doc 中提到的IntStream
:
返回一个 int 流,零扩展此序列中的 char 值。 映射到代理代码点的任何字符都未经解释地传递。 如果在读取流时序列发生变异,则结果未定义。
方法codePoints()
还根据文档返回一个IntStream
:
从此序列返回代码点值流。 序列中遇到的任何代理对都像通过 Character.toCodePoint 组合在一起,并将结果传递给流。 任何其他代码单元,包括普通 BMP 字符、未配对的代理和未定义的代码单元,都零扩展为 int 值,然后将其传递给流。
字符和代码点有何不同? 正如提到的这个文章:
Unicode 3.1 添加了补充字符,使字符总数超过 2^16 = 65536 个字符,可以通过单个 16 位
char
进行区分。 因此,char
值不再具有到 Unicode 中基本语义单元的一对一映射。 JDK 5 已更新以支持更大的字符值集。 一些新的补充字符没有改变char
类型的定义,而是由两个char
值的代理对表示。 为减少命名混淆,将使用代码点来指代代表特定 Unicode 字符(包括补充字符)的数字。
最后为什么是forEachOrdered
而不是forEach
?
的行为forEach
是明确地不确定性,其中作为forEachOrdered
执行用于该流的每个元件的操作,在该流的遭遇顺序如果流具有规定的遭遇顺序。 所以forEach
不保证订单会被保留。 另请查看此问题以获取更多信息。
有关字符、代码点、字形和字素之间的差异,请检查此问题。
我同意 StringTokenizer 在这里是矫枉过正。 实际上,我尝试了上述建议并花时间。
我的测试相当简单:创建一个包含大约一百万个字符的 StringBuilder,将其转换为字符串,然后使用 charAt() / 在转换为字符数组 / 使用 CharacterIterator 遍历每个字符一千次(当然要确保对字符串做一些事情,这样编译器就不能优化整个循环:-))。
在我的 2.6 GHz Powerbook(那是 mac :-))和 JDK 1.5 上的结果:
由于结果明显不同,最直接的方式似乎也是最快的方式。 有趣的是,StringBuilder 的 charAt() 似乎比 String 的稍慢。
顺便说一句,我建议不要使用 CharacterIterator,因为我认为它滥用 '\' 字符作为“迭代结束”是一个非常糟糕的黑客攻击。 在大型项目中,总是有两个人为了两个不同的目的使用同一种 hack,而代码崩溃真的很神秘。
这是其中一项测试:
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");
有一些专门的课程:
import java.text.*;
final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
// process c
...
}
如果您的类路径上有Guava ,以下是一个非常易读的替代方案。 在这种情况下,番石榴甚至有一个相当合理的自定义 List 实现,所以这应该不会效率低下。
for(char c : Lists.charactersOf(yourString)) {
// Do whatever you want
}
更新:正如@Alex 所指出的,Java 8 还可以使用CharSequence#chars
。 甚至类型是 IntStream,所以它可以映射到字符,如:
yourString.chars()
.mapToObj(c -> Character.valueOf((char) c))
.forEach(c -> System.out.println(c)); // Or whatever you want
如果您需要遍历String
的代码点(请参阅此答案),则更短/更易读的方法是使用 Java 8 中添加的CharSequence#codePoints
方法:
for(int c : string.codePoints().toArray()){
...
}
或直接使用流而不是 for 循环:
string.codePoints().forEach(c -> ...);
还有CharSequence#chars
,如果你想要的字符流(虽然它是一个IntStream
,因为没有CharStream
)。
我不会使用StringTokenizer
因为它是 JDK 中遗留的类之一。
javadoc 说:
StringTokenizer
是一个遗留类,出于兼容性原因保留,但不鼓励在新代码中使用它。 建议任何寻求此功能的人改用String
的 split 方法或java.util.regex
包。
如果您需要性能,那么您必须在您的环境中进行测试。 没有别的办法。
这里示例代码:
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 在线我得到:
1 10349420
2 526130
3 484200
0
在 Android x86 API 17 上,我得到:
1 9122107
2 13486911
3 12700778
0
public class Main {
public static void main(String[] args) {
String myStr = "Hello";
String myStr2 = "World";
for (int i = 0; i < myStr.length(); i++) {
char result = myStr.charAt(i);
System.out.println(result);
}
for (int i = 0; i < myStr2.length(); i++) {
char result = myStr2.charAt(i);
System.out.print(result);
}
}
}
Output:
H
e
l
l
o
World
请参阅Java 教程:字符串。
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);
}
}
将长度放入int len
并使用for
循环。
StringTokenizer 完全不适合将字符串分解为单个字符的任务。 使用String#split()
您可以通过使用不匹配任何内容的正则表达式轻松做到这一点,例如:
String[] theChars = str.split("|");
但是 StringTokenizer 不使用正则表达式,并且没有您可以指定的分隔符字符串将匹配字符之间的任何内容。 有一个可爱的小砍你可以用它来完成同样的事情:使用字符串本身作为分隔符字符串(使得在它的每一个字符分隔符),并使其返回分隔符:
StringTokenizer st = new StringTokenizer(str, str, true);
但是,我只是为了忽略它们而提到这些选项。 这两种技术都将原始字符串分解为一个字符的字符串而不是 char 原语,并且都涉及大量的对象创建和字符串操作形式的开销。 将其与在 for 循环中调用 charAt() 进行比较,后者几乎不会产生任何开销。
上面的答案指出了这里许多不按代码点值迭代的解决方案的问题——它们在处理任何代理字符时都会遇到问题。 Java 文档还概述了此处的问题(请参阅“Unicode 字符表示”)。 无论如何,这里有一些代码使用补充 Unicode 集中的一些实际代理字符,并将它们转换回字符串。 请注意, .toChars() 返回一个字符数组:如果您正在处理代理,则必须有两个字符。 此代码适用于任何Unicode 字符。
String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
supplementary.codePoints().forEach(cp ->
System.out.print(new String(Character.toChars(cp))));
此示例代码将帮助您!
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);
}
}
所以通常有两种方法可以在java中迭代字符串,这个线程中已经有很多人回答了这个问题,只需添加我的版本首先使用
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
如果性能受到威胁,那么我会建议在恒定时间内使用第一个,如果不是,那么考虑到 java 中字符串类的不变性,使用第二个会使您的工作更容易。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.