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

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

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

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

Text1 Text2 Text3 Text4

这就是我想要的

Text1 DelimiterA Text2 DelimiterC Text3 DelimiterB Text4

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


当前回答

您可以使用前向和后向,这是正则表达式的特性。

System.out.println(Arrays.toString("a;b;c;d".split("(?<=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("(?=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("((?<=;)|(?=;))")));

你会得到:

[a;, b;, c;, d]
[a, ;b, ;c, ;d]
[a, ;, b, ;, c, ;, d]

最后一个是你想要的。

(?<=;)|(?=;))等于在前面选择一个空字符;或之后;。

编辑:Fabian Steeg关于可读性的评论是有效的。可读性一直是正则表达式的一个问题。为了使正则表达式更具可读性,我做的一件事是创建一个变量,其名称表示正则表达式的功能。您甚至可以放置占位符(例如%1$s)并使用Java的String。Format将占位符替换为您需要使用的实际字符串;例如:

static public final String WITH_DELIMITER = "((?<=%1$s)|(?=%1$s))";

public void someMethod() {
    final String[] aEach = "a;b;c;d".split(String.format(WITH_DELIMITER, ";"));
    ...
}

其他回答

我也会发布我的工作版本(第一个是真的类似Markus)。

public static String[] splitIncludeDelimeter(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    int now, old = 0;
    while(matcher.find()){
        now = matcher.end();
        list.add(text.substring(old, now));
        old = now;
    }

    if(list.size() == 0)
        return new String[]{text};

    //adding rest of a text as last element
    String finalElement = text.substring(old);
    list.add(finalElement);

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

这是第二个解,比第一个快50%

public static String[] splitIncludeDelimeter2(String regex, String text){
    List<String> list = new LinkedList<>();
    Matcher matcher = Pattern.compile(regex).matcher(text);

    StringBuffer stringBuffer = new StringBuffer();
    while(matcher.find()){
        matcher.appendReplacement(stringBuffer, matcher.group());
        list.add(stringBuffer.toString());
        stringBuffer.setLength(0); //clear buffer
    }

    matcher.appendTail(stringBuffer); ///dodajemy reszte  ciagu
    list.add(stringBuffer.toString());

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

我不知道Java API中是否存在这样做的现有函数(这并不是说它不存在),但这是我自己的实现(一个或多个分隔符将作为单个令牌返回;如果你想让每个分隔符作为一个单独的标记返回,它将需要一些适应):

static String[] splitWithDelimiters(String s) {
    if (s == null || s.length() == 0) {
        return new String[0];
    }
    LinkedList<String> result = new LinkedList<String>();
    StringBuilder sb = null;
    boolean wasLetterOrDigit = !Character.isLetterOrDigit(s.charAt(0));
    for (char c : s.toCharArray()) {
        if (Character.isLetterOrDigit(c) ^ wasLetterOrDigit) {
            if (sb != null) {
                result.add(sb.toString());
            }
            sb = new StringBuilder();
            wasLetterOrDigit = !wasLetterOrDigit;
        }
        sb.append(c);
    }
    result.add(sb.toString());
    return result.toArray(new String[0]);
}

调整pattern .split()以将匹配的模式包含到列表中

添加

// add match to the list
        matchList.add(input.subSequence(start, end).toString());

完整的源

public static String[] inclusiveSplit(String input, String re, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<String>();

    Pattern pattern = Pattern.compile(re);
    Matcher m = pattern.matcher(input);

    // Add segments before each match found
    while (m.find()) {
        int end = m.end();
        if (!matchLimited || matchList.size() < limit - 1) {
            int start = m.start();
            String match = input.subSequence(index, start).toString();
            matchList.add(match);
            // add match to the list
            matchList.add(input.subSequence(start, end).toString());
            index = end;
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index, input.length())
                    .toString();
            matchList.add(match);
            index = end;
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] { input.toString() };

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize - 1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}

我喜欢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"]

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

    String expression = "((A+B)*C-D)*E";
    expression = expression.replaceAll("\\+", "~+~");
    expression = expression.replaceAll("\\*", "~*~");
    expression = expression.replaceAll("-", "~-~");
    expression = expression.replaceAll("/+", "~/~");
    expression = expression.replaceAll("\\(", "~(~"); //also you can use [(] instead of \\(
    expression = expression.replaceAll("\\)", "~)~"); //also you can use [)] instead of \\)
    expression = expression.replaceAll("~~", "~");
    if(expression.startsWith("~")) {
        expression = expression.substring(1);
    }

    String[] expressionArray = expression.split("~");
    System.out.println(Arrays.toString(expressionArray));