如何在字符串中计算字符出现的次数?

621
我有一个字符串
a.b.c.d
我希望以惯用方式计算“.”的出现次数,最好是一行代码解决。
(之前我曾表达过这个限制条件为“不使用循环”,以防你想知道为什么每个人都试图回答而不使用循环)。

1
作业?否则我看不出避免循环的要求。 - PhiLho
26
不是不喜欢使用循环,只是在寻找一个惯用语的一行代码。 - Bart
2
循环语句就是为了解决这样的问题而设计的,在通用工具类中编写循环,然后调用你刚创建的一行代码。 - che javara
字符串相关的类似问题:https://dev59.com/_HRA5IYBdhLWcg3w9ivq - koppor
显示剩余2条评论
48个回答

1122

这个怎么样?它没有使用正则表达式,因此应该比其他解决方案更快,并且不会使用循环。

int count = line.length() - line.replace(".", "").length();

130
最简单的方法。聪明的做法。并且它适用于Android,因为那里没有StringUtils类。 - Jose_GD
47
这是最好的答案。之所以最好,是因为您不需要导入另一个库。 - Alex Spencer
32
非常实用但非常丑陋。我不建议使用,因为它会导致代码混乱。 - Daniel San
37
将丑陋的代码封装在自己的“StringUtils”类中可以将其最小化。然后,丑陋的代码就只存在于一个地方,而其他地方都易读得多。 - RonR
34
循环方法比这种方法快得多,尤其是想要计算字符而不是字符串时(因为没有String.replace(char, char)方法)。在一个15个字符的字符串上,我得到了6049 ns与26,739 ns的巨大差异(平均100次运行)。原始数字相差很大,但百分比上来说……会累加。避免内存分配-使用循环! - Ben
显示剩余12条评论

784

我的“惯用一行代码”是:

int count = StringUtils.countMatches("a.b.c.d", ".");

如果已经有了 commons lang,为什么还要自己编写呢?

Spring Framework 的一行代码解决方案是:

int occurance = StringUtils.countOccurrencesOf("a.b.c.d", ".");

44
Guava 的等价代码:int count = CharMatcher.is('.').countIn("a.b.c.d");,这是在一个重复的问题中由 dogbane 回答的。 - Jonik
29
虽然我不会对此进行投票否决,但这个解决方案需要使用第三方库并且成本较高。 - WestCoastProjects
这只适用于Spring框架,必须进行导入。 - Isuru Madusanka
1
如果有人需要,可以访问以下网址:http://grepcode.com/file/repo1.maven.org/maven2/commons-lang/commons-lang/2.3/org/apache/commons/lang/StringUtils.java。 - cV2
25
在我工作的每家公司中,花费高昂的是拥有许多写得很差、维护不好的“*Utils”类。你的工作之一就是了解Apache Commons中提供了什么。 - AbuNassar
显示剩余2条评论

328

总结其他答案和我所知道的所有方法,用一行代码实现以下操作:

   String testString = "a.b.c.d";

1) 使用 Apache Commons

int apache = StringUtils.countMatches(testString, ".");
System.out.println("apache = " + apache);

2) 使用Spring Framework

int spring = org.springframework.util.StringUtils.countOccurrencesOf(testString, ".");
System.out.println("spring = " + spring);

3) 使用 replace

int replace = testString.length() - testString.replace(".", "").length();
System.out.println("replace = " + replace);

4) 使用 replaceAll (情况 1)

int replaceAll = testString.replaceAll("[^.]", "").length();
System.out.println("replaceAll = " + replaceAll);

5)使用 replaceAll(第二种情况)

int replaceAllCase2 = testString.length() - testString.replaceAll("\\.", "").length();
System.out.println("replaceAll (second case) = " + replaceAllCase2);

6) 使用split

int split = testString.split("\\.",-1).length-1;
System.out.println("split = " + split);

7) 使用Java8(情况1)

long java8 = testString.chars().filter(ch -> ch =='.').count();
System.out.println("java8 = " + java8);

8) 使用Java8(情况2)可能比情况1更适合Unicode。

long java8Case2 = testString.codePoints().filter(ch -> ch =='.').count();
System.out.println("java8 (second case) = " + java8Case2);

9) Using StringTokenizer

int stringTokenizer = new StringTokenizer(" " +testString + " ", ".").countTokens()-1;
System.out.println("stringTokenizer = " + stringTokenizer);
来自评论:要小心StringTokenizer,对于a.b.c.d,它可以工作,但对于a...b.c....d或...a.b.c.d或a....b......c.....d...等情况,它将无法工作。它只会在字符之间计算一次“.”。

更多信息请参见Github

性能测试(使用JMH,模式= AverageTime,得分0.010优于0.351):

Benchmark              Mode  Cnt  Score    Error  Units
1. countMatches        avgt    5  0.010 ±  0.001  us/op
2. countOccurrencesOf  avgt    5  0.010 ±  0.001  us/op
3. stringTokenizer     avgt    5  0.028 ±  0.002  us/op
4. java8_1             avgt    5  0.077 ±  0.005  us/op
5. java8_2             avgt    5  0.078 ±  0.003  us/op
6. split               avgt    5  0.137 ±  0.009  us/op
7. replaceAll_2        avgt    5  0.302 ±  0.047  us/op
8. replace             avgt    5  0.303 ±  0.034  us/op
9. replaceAll_1        avgt    5  0.351 ±  0.045  us/op

打印的字符串与上面的不匹配,并且顺序是最快的第一,这使得查找至少有些棘手。其他方面很好的答案! - Maarten Bodewes
1
针对需要多个UTF-16代码单元的代码点进行通用的第二种情况:"123 has 2".codePoints().filter((c) -> c == "".codePointAt(0)).count() - Tom Blodget
Apache Commons的StringUtils.countMatches循环遍历索引并使用charAt; Spring Framework的StringUtils.countOccurencesOf重复使用indexOf。OpenJDK的String.indexOf基本上在循环中使用charAt - Solomon Ucko

184

迟早会有 某些东西 需要循环。你更容易编写(非常简单的)循环,而不是使用像 split 这样比你需要的强大得多的东西。

可以将循环封装在单独的方法中,例如:

public static int countOccurrences(String haystack, char needle)
{
    int count = 0;
    for (int i=0; i < haystack.length(); i++)
    {
        if (haystack.charAt(i) == needle)
        {
             count++;
        }
    }
    return count;
}

那么你的主代码中就不需要循环,但循环必须在某个地方存在。


6
对于以下代码:for (int i=0,l=haystack.length(); i < l; i++),请仁慈对待您的堆栈。 - Chris
12
我甚至不确定评论中的"stack"是从哪里来的。这个答案并不像我的递归答案那样对堆栈造成严重影响。 - Jon Skeet
3
不仅如此,而且这可能是一种反优化,如果不查看JIT的操作,则无法实现最佳效果。例如,如果在数组循环中执行上述操作,则可能会使情况变得更糟。 - ShuggyCoUk
5
@sulai:在一种 微不足道的 JIT 优化技术面前,克里斯的担忧是没有根据的,我个人认为。请问这条评论在三年之后为什么引起了你的关注?只是好奇。 - Jon Skeet
1
@njlarsson:那么你就有一个循环内部的循环,只是不太明显。而且我认为这样的代码要难读得多。 - Jon Skeet
显示剩余7条评论

62

我有一个类似于Mladen的想法,但是相反的...

String s = "a.b.c.d";
int charCount = s.replaceAll("[^.]", "").length();
println(charCount);

replaceAll使用正则表达式,对于它要执行的计算来说非常庞大。最好使用简单的循环... - robob
2
我认为使用正则表达式并创建一个新字符串进行计数不是一个好主意。我会创建一个静态方法,循环遍历字符串中的每个字符来计数。 - mingfai
1
@mingfai:确实,但原问题是如何制作一行代码,而且没有循环(你可以在一行中使用循环,但会很丑!)。质疑问题,而不是答案... :-) - PhiLho
我喜欢这个答案,因为它简洁明了。 - user626607
我简直不敢相信一个正则表达式的答案竟然有37个赞。你们都被解雇了! - MK.
显示剩余3条评论

39
String s = "a.b.c.d";
int charCount = s.length() - s.replaceAll("\\.", "").length();

ReplaceAll(".") 将替换所有字符。

PhiLho's solution 使用 ReplaceAll("[^.]",""),无需转义,因为 [.] 表示字符 'dot',而不是 '任何字符'。


我喜欢这个。当然,还有一个循环在那里,因为必须有一个。 - The Archetypal Paul
请注意,如果您想查找长度大于1的子字符串,则需要将此数字除以相应的数值。 - rogerdpack

35
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();

1
请为本地解决方案投+票。 - Scadge

32

我的“惯用单行代码”解决方案:

int count = "a.b.c.d".length() - "a.b.c.d".replace(".", "").length();

不知道为什么使用StringUtils的解决方案被接受了。


5
这篇文章中有一个类似于这个解决方案的旧解决方案。 - JCalcines
8
由于这个解决方案效率很低。 - András
这会创建一个额外的字符串来生成计数。不知道为什么有人会喜欢这个而不是StringUtils,如果StringUtils是一个选项的话。如果它不是一个选项,他们应该在一个实用类中创建一个简单的for循环。 - crush

27
一个更简短的例子是什么?
String text = "a.b.c.d";
int count = text.split("\\.",-1).length-1;

4
这个似乎有相对较大的开销,请注意它可能会创建许多小字符串。通常这并不会有太大影响,但使用时需要小心。 - Maarten Bodewes

18

这里有一个不需要循环的解决方案:

public static int countOccurrences(String haystack, char needle, int i){
    return ((i=haystack.indexOf(needle, i)) == -1)?0:1+countOccurrences(haystack, needle, i+1);}


System.out.println("num of dots is "+countOccurrences("a.b.c.d",'.',0));

好的,有一个循环,但它是隐形的 :-)

-- Yonatan


3
除非你的字符串太长导致出现OutOfMemoryError错误。 - Spencer Kormos
问题听起来很牵强,像是作业题,如果是的话,这个递归可能就是你被要求找到的答案。 - erickson
使用indexOf,这将循环...但是一个好主意。一分钟内发布一个真正的“仅递归”解决方案... - Jon Skeet
1
如果它的出现次数超过了您可用的堆栈空间,您将会遇到堆栈溢出异常 ;) - Luca C.
1
循环并不危险。无限递归才是真正的危险。 - Hardest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接