在Java中将字符串拆分为等长子字符串

153

如何在Java中将字符串"Thequickbrownfoxjumps"拆分为相等大小的子字符串。 例如,当子字符串大小为4时,则输出应为"Theq", "uick", "brow", "nfox", "jump""s"

["Theq","uick","brow","nfox","jump","s"]

类似问题:

如何在Scala中将字符串分成等长的子字符串


4
你尝试了什么?为什么那个方法行不通? - Thilo
2
你需要使用正则表达式吗?我只是问一下因为这个标签是关于正则表达式的... - Tim Pietzcker
@Thilo 发布的链接是Scala相关的,他正在询问Java方面的内容。 - Jaydeep Patel
@Thilo:我想知道如何在Java中实现,就像Scala的答案一样。 - Emil
23个回答

272

这是一个正则表达式的单行代码版本:

System.out.println(Arrays.toString(
    "Thequickbrownfoxjumps".split("(?<=\\G.{4})")
));

\G 是一个零宽度断言,匹配上一次匹配结束的位置。如果之前没有匹配,它将会匹配输入的开头,与 \A 相同。包含的向后查找匹配距离上一次匹配结束位置四个字符的位置。

向后查找和 \G 都是高级正则表达式功能,不是所有版本都支持。此外,\G 在支持它的版本中实现不一致。这个技巧可以在 Java、Perl、.NET 和 JGSoft 中使用,但不能在 PHP(PCRE)、Ruby 1.9+ 或 TextMate(Oniguruma)中使用。JavaScript 的 /y(粘性标志)不如 \G 灵活,并且即使 JS 支持向后查找,也无法以这种方式使用。

我应该提到,如果你有其他选择,我不一定推荐这个解决方案。其他答案中的非正则表达式解决方案可能更长,但它们也是自我说明的;这个解决方案恰恰相反。 ;)

此外,这在 Android 中不起作用,因为 Android 不支持在向后查找中使用 \G


2
在PHP 5.2.4中,以下代码有效:return preg_split('/(?<=\G.{'.$len.'})/u', $str,-1,PREG_SPLIT_NO_EMPTY); - Igor
10
记录一下,使用String.substring()而不是正则表达式,虽然需要多写几行代码,但运行速度会快大约5倍... - drew moore
2
在Java中,对于带有换行符的字符串,这种方法不起作用。它只会检查到第一个换行符,如果该换行符恰好在分割大小之前,则字符串将不会被分割。或者我错过了什么? - joensson
6
为了完整起见:将文本分割到多行需要在正则表达式中加上前缀 (?s)(?s)(?<=\\G.{4}) - bobbel
2
@JeffreyBlattman 我怀疑你在编译时得到了异常... - Holger
显示剩余11条评论

160

使用简单的算术和字符串操作很容易做到这一点:

public static List<String> splitEqually(String text, int size) {
    // Give the list the right capacity to start with. You could use an array
    // instead if you wanted.
    List<String> ret = new ArrayList<String>((text.length() + size - 1) / size);

    for (int start = 0; start < text.length(); start += size) {
        ret.add(text.substring(start, Math.min(text.length(), start + size)));
    }
    return ret;
}

注意:这种做法假设UTF-16代码单元(实际上就是char)与“字符”一一对应。对于超出基本多语言平面的字符(如表情符号)和(根据计数方法)组合字符,该假设不成立。

我认为没有必要使用正则表达式。

编辑:我不使用正则表达式的原因:

  • 这并不涉及正则表达式的任何实际模式匹配。它只是在计数。
  • 怀疑上述方法更有效率,尽管在大多数情况下并不重要。
  • 如果您需要在不同位置使用不同大小的变量,则必须重复编写或使用帮助函数来基于参数构建正则表达式 - 这太糟糕了。
  • 另一个答案中提供的正则表达式首先无法编译(无效转义),然后也无法工作。我的代码可以直接运行。这更多地证明了正则表达式相对于纯代码的易用性,我个人这样认为。

11
@Emil:实际上,你并没有要求一个正则表达式。虽然标签中提到了,但问题本身并没有要求使用正则表达式。你可以将这个方法放在一个地方,然后在代码中的任何位置用只有一行非常易读的语句来拆分字符串。 - Jon Skeet
3
Emil,这不是正则表达式的正确使用方式。句号。 - Chris
4
如果你想要一行代码来分割字符串,我建议使用Guava的Splitter.fixedLength(4),就像seanizer所建议的那样。 - ColinD
3
@Jay:别这么讽刺嘛,我相信可以用正则表达式在一行内完成。固定长度的子字符串也是一种模式。你对这个回答有什么看法?https://dev59.com/4m865IYBdhLWcg3wi_M2#3761521。我确定可以使用正则表达式在一行内完成操作。固定长度的子字符串也是一种模式。你对这个答案有何看法? - Emil
5
@Emil: 我当时的意思不是要表现粗鲁,只是想要有点幽默感。我说严肃的话是,虽然你可以设计一个正则表达式来实现这个功能,我看Alan Moore已经有一个他声称有效的正则表达式了,但它很晦涩难懂,因此对于以后的程序员来说,理解和维护都会困难。使用子字符串的方法则更加直观易读。参见Jon Skeet的第四点:我完全同意他的观点。 - Jay
显示剩余13条评论

80

使用Google Guava非常容易:

for(final String token :
    Splitter
        .fixedLength(4)
        .split("Thequickbrownfoxjumps")){
    System.out.println(token);
}

输出:

Theq
uick
brow
nfox
jump
s

如果你需要将结果作为数组返回,可以使用以下代码:

String[] tokens =
    Iterables.toArray(
        Splitter
            .fixedLength(4)
            .split("Thequickbrownfoxjumps"),
        String.class
    );

参考资料:

注意:上面的示例中直接展示了Splitter的构建方式,但由于Splitters是不可变的且可重用的,最好将它们存储在常量中以便复用。

private static final Splitter FOUR_LETTERS = Splitter.fixedLength(4);

// more code

for(final String token : FOUR_LETTERS.split("Thequickbrownfoxjumps")){
    System.out.println(token);
}

1
感谢您的帖子(让我知道了Guava库的方法),但我必须接受正则表达式的答案https://dev59.com/4m865IYBdhLWcg3wi_M2#3761521,因为它不需要任何第三方库且只需一行代码。 - Emil
1
为了执行这个简单的任务,包含数百KB的库代码几乎肯定不是正确的做法。 - Jeffrey Blattman
2
@JeffreyBlattman 专门为此使用Guava可能有些过度了,但我无论如何都将其用作所有Java代码中的通用库,所以为什么不再使用这一个额外的功能呢? - Sean Patrick Floyd
有没有办法用分隔符重新连接起来? - Aquarius Power
2
@AquariusPower String.join(separator, arrayOrCollection) @AquariusPower String.join(separator, arrayOrCollection) - Holger
不支持所有Unicode字符。尝试使用Guava 30.1.1,将输入中的q替换为FACE WITH MEDICAL MASK:“Theuickbrownfoxjumps”,得到结果:The? ?uic kbro ... - Basil Bourque

15

如果你正在使用Google的guava通用库(老实说,任何新的Java项目都应该使用),那么可以使用Splitter类轻松实现:

for (String substring : Splitter.fixedLength(4).split(inputString)) {
    doSomethingWith(substring);
}

就是这样。 简单易行!


不支持所有Unicode字符。尝试使用Guava 30.1.1,将输入中的q替换为FACE WITH MEDICAL MASK:“Theuickbrownfoxjumps”,得到结果:The? ?uic kbro ... - Basil Bourque

8
public static String[] split(String src, int len) {
    String[] result = new String[(int)Math.ceil((double)src.length()/(double)len)];
    for (int i=0; i<result.length; i++)
        result[i] = src.substring(i*len, Math.min(src.length(), (i+1)*len));
    return result;
}

由于 src.length()len 都是 int 类型,所以您的调用 ceiling 并没有实现您想要的效果 - 请查看其他回答中的一些方法:(src.length() + len - 1) / len - Michael Brewer-Davis
@Michael:说得好。我没有测试过非倍数长度的字符串。现在已经修复了。 - Saul

6
public String[] splitInParts(String s, int partLength)
{
    int len = s.length();

    // Number of parts
    int nparts = (len + partLength - 1) / partLength;
    String parts[] = new String[nparts];

    // Break into parts
    int offset= 0;
    int i = 0;
    while (i < nparts)
    {
        parts[i] = s.substring(offset, Math.min(offset + partLength, len));
        offset += partLength;
        i++;
    }

    return parts;
}

6
你是否对“for”循环有偏见? - Jon Skeet
一个for循环确实是更自然的选择来使用它:-)感谢您指出这一点。 - Grodriguez

5
以下是翻译的结果:

这是一个一行代码的版本,使用 Java 8IntStream 来确定切片开始的索引:

String x = "Thequickbrownfoxjumps";

String[] result = IntStream
                    .iterate(0, i -> i + 4)
                    .limit((int) Math.ceil(x.length() / 4.0))
                    .mapToObj(i ->
                        x.substring(i, Math.min(i + 4, x.length())
                    )
                    .toArray(String[]::new);

3

我更喜欢这个简单的解决方案:

String content = "Thequickbrownfoxjumps";
while(content.length() > 4) {
    System.out.println(content.substring(0, 4));
    content = content.substring(4);
}
System.out.println(content);

1
不要这样做!字符串是不可变的,因此您的代码需要每4个字符复制整个剩余字符串。因此,您的片段在字符串大小方面需要二次而不是线性时间。 - Tobias
@Tobias:即使String是可变的,这段代码仍会执行所提到的冗余复制,除非涉及到复杂的编译过程。使用此代码片段的唯一原因是代码简洁性。 - Cheetah Coder
你自从第一次发布代码以来有改动吗?最新版本实际上不会复制 - substring() 运行效率高(至少在旧版 Java 上是常数时间);它保留了对整个字符串的 char[] 的引用(至少在旧版 Java 上),但在这种情况下,由于您保留了所有字符,所以这没问题。因此,您在这里拥有的最新代码实际上是可以的(除了如果内容以空字符串开头,则您的代码会打印一个空行,这可能不是人们想要的)。 - Tobias
@Tobias:我不记得有任何改变。 - Cheetah Coder
1
@Tobias,Java 7更新6在2012年中期更改了substring的实现方式,当时从String类中删除了offsetcount字段。因此,在此答案发布之前,substring的复杂度已经变为线性。但对于像示例这样的小字符串,它仍然运行得足够快,而对于更长的字符串...嗯,这种任务在实践中很少发生。 - Holger

3

一个 StringBuilder 版本:

public static List<String> getChunks(String s, int chunkSize)
{
 List<String> chunks = new ArrayList<>();
 StringBuilder sb = new StringBuilder(s);

while(!(sb.length() ==0)) 
{           
   chunks.add(sb.substring(0, chunkSize));
   sb.delete(0, chunkSize);

}
return chunks;

}


2
我使用以下Java 8的解决方案:
public static List<String> splitString(final String string, final int chunkSize) {
  final int numberOfChunks = (string.length() + chunkSize - 1) / chunkSize;
  return IntStream.range(0, numberOfChunks)
                  .mapToObj(index -> string.substring(index * chunkSize, Math.min((index + 1) * chunkSize, string.length())))
                  .collect(toList());
}

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