我有一个多行字符串,由一组不同的分隔符分隔:

(Text1)(DelimiterA)(Text2)(DelimiterC)(Text3)(DelimiterB)(Text4)

我可以使用string将这个字符串分割成各个部分。分裂,但似乎我无法获得与分隔符正则表达式匹配的实际字符串。

换句话说,这就是我得到的结果:

Text1 Text2 Text3 Text4

这就是我想要的

Text1 DelimiterA Text2 DelimiterC Text3 DelimiterB Text4

JDK中是否有任何方法可以使用分隔符正则表达式分割字符串,但同时保留分隔符?


当前回答

我来晚了,但回到最初的问题,为什么不使用搜索呢?

Pattern p = Pattern.compile("(?<=\\w)(?=\\W)|(?<=\\W)(?=\\w)");
System.out.println(Arrays.toString(p.split("'ab','cd','eg'")));
System.out.println(Arrays.toString(p.split("boo:and:foo")));

输出:

[', ab, ',', cd, ',', eg, ']
[boo, :, and, :, foo]

编辑:您在上面看到的是我运行该代码时命令行上出现的内容,但我现在看到它有点令人困惑。很难跟踪哪些逗号是结果的一部分,哪些是由Arrays.toString()添加的。SO的语法高亮显示也没有帮助。为了让突出显示与我一起工作而不是反对我,下面是我在源代码中声明这些数组的样子:

{ "'", "ab", "','", "cd", "','", "eg", "'" }
{ "boo", ":", "and", ":", "foo" }

我希望这更容易理解。谢谢你的提醒,@finnw。

其他回答

我知道这是一个非常非常古老的问题,答案也被接受了。但我仍然想对最初的问题提出一个非常简单的答案。考虑下面的代码:

String str = "Hello-World:How\nAre You&doing";
inputs = str.split("(?!^)\\b");
for (int i=0; i<inputs.length; i++) {
   System.out.println("a[" + i + "] = \"" + inputs[i] + '"');
}

输出:

a[0] = "Hello"
a[1] = "-"
a[2] = "World"
a[3] = ":"
a[4] = "How"
a[5] = "
"
a[6] = "Are"
a[7] = " "
a[8] = "You"
a[9] = "&"
a[10] = "doing"

我只是使用单词边界\b来分隔单词,除非它是文本的开始。

我喜欢StringTokenizer的想法,因为它是可枚举的。 但它也是过时的,可以用String代替。split返回一个单调的String[](并且不包括分隔符)。

所以我实现了一个StringTokenizerEx,它是一个Iterable,它接受一个真正的regexp来分割字符串。

一个真正的regexp意味着它不是一个重复的'字符序列'来形成分隔符: 'o'只匹配'o',并将'ooo'分成三个分隔符,其中有两个空字符串:

[o], '', [o], '', [o]

但是regexp o+在拆分“aooob”时将返回预期的结果

[], 'a', [ooo], 'b', []

使用StringTokenizerEx:

final StringTokenizerEx aStringTokenizerEx = new StringTokenizerEx("boo:and:foo", "o+");
final String firstDelimiter = aStringTokenizerEx.getDelimiter();
for(String aString: aStringTokenizerEx )
{
    // uses the split String detected and memorized in 'aString'
    final nextDelimiter = aStringTokenizerEx.getDelimiter();
}

该类的代码可以在DZone snippet中找到。

与通常的代码挑战响应(包含测试用例的自包含类)一样,复制粘贴它(在“src/test”目录中)并运行它。它的main()方法说明了不同的用法。


注:(2009年底编辑)

《Final Thoughts: Java Puzzler: Splitting hair》这篇文章很好地解释了String.split()中的奇怪行为。 乔希·布洛赫(Josh Bloch)甚至在回应那篇文章时评论道:

是的,这很痛苦。FWIW,这样做有一个很好的理由:与Perl的兼容性。 做这件事的人是Mike "madbot" McCloskey,他现在在谷歌和我们一起工作。Mike确保Java的正则表达式几乎通过了所有30K Perl正则表达式测试(并且运行得更快)。

谷歌公共库Guava还包含一个Splitter,它是:

使用更简单 由谷歌(而不是你)维护

所以它可能值得一看。从他们最初的粗略文件(pdf):

JDK有:

String[] pieces = "foo.bar".split("\\.");

如果你想要它所做的事情,使用它是很好的: -正则表达式 - result作为数组 -它处理空碎片的方式 小谜题:",a,,b,".split(",")返回…

(a) "", "a", "", "b", ""
(b) null, "a", null, "b", null
(c) "a", null, "b"
(d) "a", "b"
(e) None of the above

答案:(e)以上都不是。

",a,,b,".split(",")
returns
"", "a", "", "b"

只跳过尾随空!(谁知道防止跳过的变通方法?这是一个有趣的…) 在任何情况下,我们的Splitter都更加灵活:默认行为很简单:

Splitter.on(',').split(" foo, ,bar, quux,")
--> [" foo", " ", "bar", " quux", ""]

如果您想要额外的功能,请提出要求!

Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.split(" foo, ,bar, quux,")
--> ["foo", "bar", "quux"]

配置方法的顺序并不重要——在分割过程中,在检查空之前进行修整。

import java.util.regex.*;
import java.util.LinkedList;

public class Splitter {
    private static final Pattern DEFAULT_PATTERN = Pattern.compile("\\s+");

    private Pattern pattern;
    private boolean keep_delimiters;

    public Splitter(Pattern pattern, boolean keep_delimiters) {
        this.pattern = pattern;
        this.keep_delimiters = keep_delimiters;
    }
    public Splitter(String pattern, boolean keep_delimiters) {
        this(Pattern.compile(pattern==null?"":pattern), keep_delimiters);
    }
    public Splitter(Pattern pattern) { this(pattern, true); }
    public Splitter(String pattern) { this(pattern, true); }
    public Splitter(boolean keep_delimiters) { this(DEFAULT_PATTERN, keep_delimiters); }
    public Splitter() { this(DEFAULT_PATTERN); }

    public String[] split(String text) {
        if (text == null) {
            text = "";
        }

        int last_match = 0;
        LinkedList<String> splitted = new LinkedList<String>();

        Matcher m = this.pattern.matcher(text);

        while (m.find()) {

            splitted.add(text.substring(last_match,m.start()));

            if (this.keep_delimiters) {
                splitted.add(m.group());
            }

            last_match = m.end();
        }

        splitted.add(text.substring(last_match));

        return splitted.toArray(new String[splitted.size()]);
    }

    public static void main(String[] argv) {
        if (argv.length != 2) {
            System.err.println("Syntax: java Splitter <pattern> <text>");
            return;
        }

        Pattern pattern = null;
        try {
            pattern = Pattern.compile(argv[0]);
        }
        catch (PatternSyntaxException e) {
            System.err.println(e);
            return;
        }

        Splitter splitter = new Splitter(pattern);

        String text = argv[1];
        int counter = 1;
        for (String part : splitter.split(text)) {
            System.out.printf("Part %d: \"%s\"\n", counter++, part);
        }
    }
}

/*
    Example:
    > java Splitter "\W+" "Hello World!"
    Part 1: "Hello"
    Part 2: " "
    Part 3: "World"
    Part 4: "!"
    Part 5: ""
*/

我不太喜欢另一种方式,前后都有一个空元素。分隔符通常不在字符串的开头或结尾,因此通常会浪费两个良好的数组插槽。

编辑:固定的限制情况。带有测试用例的注释源代码可以在这里找到:http://snippets.dzone.com/posts/show/6453

我不认为这是可能的String#split,但你可以使用一个StringTokenizer,虽然它不允许你定义你的分隔符作为一个正则表达式,但只能作为一个类的个位数字符:

new StringTokenizer("Hello, world. Hi!", ",.!", true); // true for returnDelims

如果负担得起,可以使用Java的replace(CharSequence target, CharSequence replacement)方法,并填充另一个分隔符以进行分割。 例子: 我想拆分字符串“boo:and:foo”,并将“:”放在它的右边。

String str = "boo:and:foo";
str = str.replace(":","newdelimiter:");
String[] tokens = str.split("newdelimiter");

重要提示:这只在你的字符串中没有进一步的“newdelimiter”时才有效!因此,这不是一个通解。 但是如果你知道一个CharSequence,你可以确定它永远不会出现在String中,这是一个非常简单的解决方案。