在Java中迭代字符串字符的最简单/最好/最正确的方法是什么?

时间:2008-10-13 06:10:16

标签: java string iteration character tokenize

StringTokenizer?将String转换为char[]并对其进行迭代?还有别的吗?

15 个答案:

答案 0 :(得分:295)

我使用for循环迭代字符串并使用charAt()让每个字符检查它。由于String是使用数组实现的,因此charAt()方法是一个常量时间操作。

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

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

这就是我要做的。这对我来说似乎最容易。

就正确性而言,我不相信存在于此。这完全取决于你的个人风格。

答案 1 :(得分:178)

两个选项

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

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

第一个可能更快,然后第二个可能更具可读性。

答案 2 :(得分:88)

注意,如果您处理的是BMP之外的字符(Unicode Basic Multilingual Plane),即超出u0000-uFFFF范围的code points,则此处描述的大多数其他技术都会被破坏。这种情况很少发生,因为此外的代码点主要分配给死语言。但是除此之外还有一些有用的字符,例如用于数学符号的一些代码点,还有一些用于用中文编码专有名称。

在这种情况下,您的代码将是:

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 +。

来源:http://mindprod.com/jgloss/codepoint.html

答案 3 :(得分:24)

我同意StringTokenizer在这里有点过分。实际上我尝试了上面的建议,并花时间。

我的测试非常简单:创建一个大约有一百万个字符的StringBuilder,将它转换为一个String,然后使用charIt()/在转换为char数组/使用CharacterIterator一千遍后遍历每个字符串(当然确保对字符串执行某些操作,以便编译器无法优化整个循环:-))。

我的2.6 GHz Powerbook(这是一个mac :-))和JDK 1.5的结果:

  • 测试1:charAt + String - &gt; 3138msec
  • 测试2:转换为数组的字符串 - &gt; 9568毫秒
  • 测试3:StringBuilder charAt - &gt; 3536毫秒
  • 测试4:CharacterIterator和String - &gt; 12151msec

由于结果显着不同,最直接的方式似乎也是最快的方式。有趣的是,StringBuilder的charAt()似乎比String更慢。

BTW我建议不要使用CharacterIterator,因为我认为它滥用'\ uFFFF'字符作为“迭代结束”是一个非常糟糕的黑客。在大项目中,总有两个人使用相同类型的黑客用于两个不同的目的,代码崩溃真的很神秘。

以下是其中一项测试:

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

答案 4 :(得分:19)

有一些专门的课程:

import java.text.*;

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

答案 5 :(得分:17)

如果您的类路径中有Guava,则以下是一个非常易读的替代方案。对于这种情况,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

答案 6 :(得分:12)

如果您需要遍历String的代码点(请参阅此answer),则使用Java 8中添加的CharSequence#codePoints方法可以使用更短/更易读的方法: / p>

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

或直接使用流而不是for循环:

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

如果你想要一个角色流,还有CharSequence#chars(虽然它是IntStream,因为没有CharStream)。

答案 7 :(得分:11)

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()也会根据doc:

返回IntStream
  

从此序列返回代码点值流。任何   在序列中遇到的代理对被组合起来,如同   Character.toCodePoint和结果传递给流。任何   其他代码单元,包括普通的BMP字符,不成对   代理和未定义的代码单元被零扩展为int值   然后传递给流。

字符和代码有何不同?正如this文章中所述:

  

Unicode 3.1添加了补充字符,带来了总数   字符超过216个字符即可   由一个16位char区分。因此,char值为   更长的是与基本语义单元的一对一映射   Unicode格式。 JDK 5已更新,以支持更大的字符集   值。一些,而不是改变char类型的定义   新的补充字符由代理对表示   两个char值。为了减少命名混淆,代码点将是   用于表示代表特定Unicode的数字   性格,包括补充性质。

最后为什么forEachOrdered而不是forEach

forEach的行为明确是不确定的,因为forEachOrdered对此流的每个元素执行操作,如果流具有遇到流的顺序定义的遭遇订单。所以forEach并不保证订单会被保留。另请查看question以获取更多信息。

对于字符,代码点,字形和字素之间的差异,请检查此question

答案 8 :(得分:3)

我不会使用StringTokenizer,因为它是JDK遗留下来的类之一。

javadoc说:

  

StringTokenizer是一个遗留类   因兼容性原因而保留   虽然在新的情况下不鼓励使用它   码。任何人都可以推荐   寻求此功能使用   String的分割方法   而是java.util.regex包。

答案 9 :(得分:0)

请参阅The 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);
    }
}

将长度放入int len并使用for循环。

答案 10 :(得分:0)

StringTokenizer完全不适合将字符串分解为单个字符的任务。使用String#split(),您可以使用不匹配的正则表达式轻松完成此操作,例如:

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

但是StringTokenizer不使用正则表达式,并且没有可以指定的分隔符字符串,它将匹配字符之间的任何内容。 一个可爱的小黑客你可以用来完成同样的事情:使用字符串本身作为分隔符字符串(使其中的每个字符都是分隔符)并让它返回分隔符:

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

但是,我只是提到这些选项以解雇它们。这两种技术都将原始字符串分解为单字符字符串而不是char原语,并且两者都以对象创建和字符串操作的形式涉及大量开销。将其与for循环中的charAt()进行比较,几乎不会产生任何开销。

答案 11 :(得分:0)

阐述this answerthis answer

以上答案指出了许多解决方案的问题,这些解决方案不会按代码点值进行迭代 - 它们会遇到任何问题surrogate chars。 java文档还概述了问题here(参见&#34; Unicode字符表示和#34;)。无论如何,这里有一些代码使用来自补充Unicode集的一些实际代理字符,并将它们 back 转换为字符串。请注意.toChars()返回一个chars数组:如果你正在处理代理,你必须有两个字符。此代码适用于任何 Unicode字符。

npm http GET https://registry.npmjs.org/onoff

npm ERR! Error: failed to fetch from registry: onoff
npm ERR!     at /usr/share/npm/lib/utils/npm-registry-client/get.js:139:12
npm ERR!     at cb (/usr/share/npm/lib/utils/npm-registry-client/request.js:31:9)
npm ERR!     at Request._callback (/usr/share/npm/lib/utils/npm-registry-client/request.js:136:18)
npm ERR!     at Request.callback (/usr/lib/nodejs/request/main.js:119:22)
npm ERR!     at Request.<anonymous> (/usr/lib/nodejs/request/main.js:212:58)
npm ERR!     at Request.emit (events.js:88:20)
npm ERR!     at ClientRequest.<anonymous> (/usr/lib/nodejs/request/main.js:412:12)
npm ERR!     at ClientRequest.g (events.js:156:14)
npm ERR!     at ClientRequest.emit (events.js:67:17)
npm ERR!     at HTTPParser.parserOnIncomingClient [as onIncoming] (http.js:1256:7)
npm ERR! You may report this log at:
npm ERR!     <http://bugs.debian.org/npm>
npm ERR! or use
npm ERR!     reportbug --attach /home/pi/homeautomation/npm-debug.log npm
npm ERR! 
npm ERR! System Linux 4.1.19+
npm ERR! command "/usr/bin/nodejs" "/usr/bin/npm" "install" "onoff" "--save"
npm ERR! cwd /home/pi/homeautomation
npm ERR! node -v v0.6.19
npm ERR! npm -v 1.1.4
npm ERR! message failed to fetch from registry: onoff
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /home/pi/homeautomation/npm-debug.log
npm not ok

答案 12 :(得分:0)

此示例代码将帮助您!

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

答案 13 :(得分:0)

如果需要性能,则必须必须进行测试。没有其他方法。

以下示例代码:

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上,我得到:

1 10349420
2 526130
3 484200
0

在Android x86 API 17上,我得到:

1 9122107
2 13486911
3 12700778
0

答案 14 :(得分:0)

因此,通常有两种方法可以遍历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中字符串类的不变性,第二个就可以简化您的工作。