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

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

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

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

Text1 Text2 Text3 Text4

这就是我想要的

Text1 DelimiterA Text2 DelimiterC Text3 DelimiterB Text4

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


当前回答

这里有一个简单干净的实现,它与Pattern#split一致,并且适用于变长模式,后面的查看不支持,而且更容易使用。它类似于@cletus提供的解决方案。

public static String[] split(CharSequence input, String pattern) {
    return split(input, Pattern.compile(pattern));
}

public static String[] split(CharSequence input, Pattern pattern) {
    Matcher matcher = pattern.matcher(input);
    int start = 0;
    List<String> result = new ArrayList<>();
    while (matcher.find()) {
        result.add(input.subSequence(start, matcher.start()).toString());
        result.add(matcher.group());
        start = matcher.end();
    }
    if (start != input.length()) result.add(input.subSequence(start, input.length()).toString());
    return result.toArray(new String[0]);
}

我在这里不做空检查,Pattern#split没有,为什么要我。我不喜欢结尾的if,但它是需要与Pattern#split保持一致的。否则,我将无条件地追加,如果输入字符串以模式结束,则结果的最后一个元素将是空字符串。

我转换为字符串[]与模式#分裂的一致性,我使用新字符串[0]而不是新字符串[result.size()],看看这里为什么。

以下是我的测试:

@Test
public void splitsVariableLengthPattern() {
    String[] result = Split.split("/foo/$bar/bas", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar", "/bas" }, result);
}

@Test
public void splitsEndingWithPattern() {
    String[] result = Split.split("/foo/$bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/", "$bar" }, result);
}

@Test
public void splitsStartingWithPattern() {
    String[] result = Split.split("$foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "", "$foo", "/bar" }, result);
}

@Test
public void splitsNoMatchesPattern() {
    String[] result = Split.split("/foo/bar", "\\$\\w+");
    Assert.assertArrayEquals(new String[] { "/foo/bar" }, result);
}

其他回答

您希望使用查找,并在零宽度匹配时进行分割。下面是一些例子:

public class SplitNDump {
    static void dump(String[] arr) {
        for (String s : arr) {
            System.out.format("[%s]", s);
        }
        System.out.println();
    }
    public static void main(String[] args) {
        dump("1,234,567,890".split(","));
        // "[1][234][567][890]"
        dump("1,234,567,890".split("(?=,)"));   
        // "[1][,234][,567][,890]"
        dump("1,234,567,890".split("(?<=,)"));  
        // "[1,][234,][567,][890]"
        dump("1,234,567,890".split("(?<=,)|(?=,)"));
        // "[1][,][234][,][567][,][890]"

        dump(":a:bb::c:".split("(?=:)|(?<=:)"));
        // "[][:][a][:][bb][:][:][c][:]"
        dump(":a:bb::c:".split("(?=(?!^):)|(?<=:)"));
        // "[:][a][:][bb][:][:][c][:]"
        dump(":::a::::b  b::c:".split("(?=(?!^):)(?<!:)|(?!:)(?<=:)"));
        // "[:::][a][::::][b  b][::][c][:]"
        dump("a,bb:::c  d..e".split("(?!^)\\b"));
        // "[a][,][bb][:::][c][  ][d][..][e]"

        dump("ArrayIndexOutOfBoundsException".split("(?<=[a-z])(?=[A-Z])"));
        // "[Array][Index][Out][Of][Bounds][Exception]"
        dump("1234567890".split("(?<=\\G.{4})"));   
        // "[1234][5678][90]"

        // Split at the end of each run of letter
        dump("Boooyaaaah! Yippieeee!!".split("(?<=(?=(.)\\1(?!\\1))..)"));
        // "[Booo][yaaaa][h! Yipp][ieeee][!!]"
    }
}

是的,在最后一个模式中是三重嵌套断言。

相关问题

Java分裂正在吞噬我的角色。 你可以使用零宽度匹配正则表达式在字符串分割? 如何在Java中将CamelCase转换为人类可读的名称? 向后查找中的反向引用

另请参阅

regular-expressions.info /看看

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

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来分隔单词,除非它是文本的开始。

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

这个问题的一个微妙之处涉及到“前导分隔符”问题:如果要有一个组合的令牌和分隔符数组,则必须知道它是以令牌还是以分隔符开始的。你当然可以假设前导界限应该被丢弃,但这似乎是一个不合理的假设。你可能还想知道你是否有一个拖拽的delim。这将相应地设置两个布尔标志。

用Groovy编写,但Java版本应该相当明显:

            String tokenRegex = /[\p{L}\p{N}]+/ // a String in Groovy, Unicode alphanumeric
            def finder = phraseForTokenising =~ tokenRegex
            // NB in Groovy the variable 'finder' is then of class java.util.regex.Matcher
            def finderIt = finder.iterator() // extra method added to Matcher by Groovy magic
            int start = 0
            boolean leadingDelim, trailingDelim
            def combinedTokensAndDelims = [] // create an array in Groovy

            while( finderIt.hasNext() )
            {
                def token = finderIt.next()
                int finderStart = finder.start()
                String delim = phraseForTokenising[ start  .. finderStart - 1 ]
                // Groovy: above gets slice of String/array
                if( start == 0 ) leadingDelim = finderStart != 0
                if( start > 0 || leadingDelim ) combinedTokensAndDelims << delim
                combinedTokensAndDelims << token // add element to end of array
                start = finder.end()
            }
            // start == 0 indicates no tokens found
            if( start > 0 ) {
                // finish by seeing whether there is a trailing delim
                trailingDelim = start < phraseForTokenising.length()
                if( trailingDelim ) combinedTokensAndDelims << phraseForTokenising[ start .. -1 ]

                println( "leading delim? $leadingDelim, trailing delim? $trailingDelim, combined array:\n $combinedTokensAndDelims" )

            }