如何拆分字符串,还要保留分隔符?

Dan*_*ski 224 java

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

(Text1)(DelimiterA)(Text2)(DelimiterC)(Text3)(DelimiterB)(Text4)
Run Code Online (Sandbox Code Playgroud)

我可以将这个字符串拆分成它的部分,String.split但是看起来我无法获得与分隔符正则表达式匹配的实际字符串.

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

  • Text1
  • Text2
  • Text3
  • Text4

这就是我要的

  • Text1
  • DelimiterA
  • Text2
  • DelimiterC
  • Text3
  • DelimiterB
  • Text4

是否有任何JDK方法使用分隔符正则表达式拆分字符串,但也保留分隔符?

Naw*_*Man 338

您可以使用Lookahead和Lookbehind.像这样:

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("((?<=;)|(?=;))")));
Run Code Online (Sandbox Code Playgroud)

你会得到:

[a;, b;, c;, d]
[a, ;b, ;c, ;d]
[a, ;, b, ;, c, ;, d]
Run Code Online (Sandbox Code Playgroud)

最后一个是你想要的.

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

希望这可以帮助.

编辑 Fabian Steeg对可读性的评论是有效的.可读性始终是RegEx的问题.有一点,我做的是帮助缓解这个问题,就是创建一个变量,其名称代表正则表达式的作用,并使用Java String格式来帮助实现.像这样:

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, ";"));
...
}
...
Run Code Online (Sandbox Code Playgroud)

这有点帮助.:-D

  • 我刚刚遇到的一个复杂因素是你想要完全匹配的可变长度分隔符(比如`[\\ s,] +`).所需的正则表达甚至更长,因为你需要额外的负面外观{前面,后面}以避免在中间匹配它们,例如.(?<= [\\ S,] +)(?![\\ S,])`|(?<![\\ S,])(= [\\ S,] +?)`. (7认同)
  • 那应该是:`String.format(WITH_DELIMITER,";");`格式是静态方法. (3认同)
  • 这对于包含重复的分隔符模式不起作用,对吧? (3认同)
  • 如果我想分成两个分隔符怎么办?让我们说';' 要么 '.' (3认同)
  • 非常好!在这里,我们可以再次看到正则表达式的力量!! (2认同)
  • 很高兴看到有一种方法可以使用 String#split 来做到这一点,尽管我希望有一种方法可以像 StringTokenizer 一样包含分隔符 - `split(";", true)` 将比`split("((?&lt;=;)|(?=;))")`。 (2认同)
  • 我同意一半;我得到一个“后视组没有明显的最大长度”试图将其与表示所有实数的正则表达式一起使用。 (2认同)

pol*_*nts 75

你想使用lookarounds,并在零宽度匹配上拆分.这里有些例子:

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][!!]"
    }
}
Run Code Online (Sandbox Code Playgroud)

是的,这是最后一种模式中的三重嵌套断言.

相关问题

也可以看看

  • 请注意,这仅适用于相对简单的表达式;我得到一个“后视组没有明显的最大长度”试图将其与表示所有实数的正则表达式一起使用。 (3认同)
  • 仅供参考:从http://stackoverflow.com/questions/275768/is-there-a-way-to-split-strings-with-string-split-and-include-the-delimiters合并 (2认同)

chi*_*ien 29

一个非常天真的解决方案,不涉及正则表达式,将在您的分隔符上执行字符串替换(假设逗号分隔符):

string.replace(FullString, "," , "~,~")
Run Code Online (Sandbox Code Playgroud)

在哪里可以用适当的唯一分隔符替换tilda(〜).

然后,如果你对新的分隔符进行拆分,那么我相信你会得到所需的结果.

  • 聪明的骗子!:) (2认同)

Mar*_*rot 22

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: ""
*/
Run Code Online (Sandbox Code Playgroud)

我不喜欢另一种方式,你在前面和后面都得到一个空元素.分隔符通常不在字符串的开头或末尾,因此您最常浪费两个好的数组插槽.

编辑:固定极限情况.可以在此处找到带有测试用例的注释源:http://snippets.dzone.com/posts/show/6453


Has*_*oon 13

将第三个论证传递为“真”。它也会返回分隔符。

StringTokenizer(String str, String delimiters, true);
Run Code Online (Sandbox Code Playgroud)


Ala*_*ore 11

我来晚了,但回到原来的问题,为什么不只是使用外观?

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")));
Run Code Online (Sandbox Code Playgroud)

输出:

[', ab, ',', cd, ',', eg, ']
[boo, :, and, :, foo]
Run Code Online (Sandbox Code Playgroud)

编辑:你在上面看到的是我运行该代码时命令行上出现的内容,但我现在看到它有点令人困惑.很难跟踪哪些逗号是结果的一部分,哪些是添加的Arrays.toString().SO的语法突出显示也没有帮助.希望突出显示我一起工作而不是反对我,这就是那些数组看起来如何我在源代码中声明它们:

{ "'", "ab", "','", "cd", "','", "eg", "'" }
{ "boo", ":", "and", ":", "foo" }
Run Code Online (Sandbox Code Playgroud)

我希望这更容易阅读.感谢单挑,@ finnw.


anu*_*ava 10

我知道这是一个非常古老的问题,答案也已被接受.但我仍然想提出一个非常简单的答案原始问题.考虑以下代码:

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] + '"');
}
Run Code Online (Sandbox Code Playgroud)

OUTPUT:

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"
Run Code Online (Sandbox Code Playgroud)

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

  • 例如,这不适用于以`de`为分隔符的`abcdef`,但是您可以使用`(?!^ | $)(?:(?&lt;= de)(?! de)|(? &lt;!de)(?= de))` (2认同)

cle*_*tus 9

我看了上面的答案,老实说,没有一个我觉得满意.你想要做的是基本上模仿Perl分割功能.为什么Java不允许这个并且在某个地方有一个join()方法超出了我,但我离题了.你甚至不需要一个真正的课程.它只是一个功能.运行此示例程序:

一些早期的答案有过多的空检查,我最近在这里写了一个问题的回答:

/sf/users/1287541/

无论如何,代码:

public class Split {
    public static List<String> split(String s, String pattern) {
        assert s != null;
        assert pattern != null;
        return split(s, Pattern.compile(pattern));
    }

    public static List<String> split(String s, Pattern pattern) {
        assert s != null;
        assert pattern != null;
        Matcher m = pattern.matcher(s);
        List<String> ret = new ArrayList<String>();
        int start = 0;
        while (m.find()) {
            ret.add(s.substring(start, m.start()));
            ret.add(m.group());
            start = m.end();
        }
        ret.add(start >= s.length() ? "" : s.substring(start));
        return ret;
    }

    private static void testSplit(String s, String pattern) {
        System.out.printf("Splitting '%s' with pattern '%s'%n", s, pattern);
        List<String> tokens = split(s, pattern);
        System.out.printf("Found %d matches%n", tokens.size());
        int i = 0;
        for (String token : tokens) {
            System.out.printf("  %d/%d: '%s'%n", ++i, tokens.size(), token);
        }
        System.out.println();
    }

    public static void main(String args[]) {
        testSplit("abcdefghij", "z"); // "abcdefghij"
        testSplit("abcdefghij", "f"); // "abcde", "f", "ghi"
        testSplit("abcdefghij", "j"); // "abcdefghi", "j", ""
        testSplit("abcdefghij", "a"); // "", "a", "bcdefghij"
        testSplit("abcdefghij", "[bdfh]"); // "a", "b", "c", "d", "e", "f", "g", "h", "ij"
    }
}
Run Code Online (Sandbox Code Playgroud)


Von*_*onC 7

我喜欢StringTokenizer的想法,因为它是Enumerable.
但它也是过时的,并由String.split替换,它返回一个无聊的String [](并且不包括分隔符).

所以我实现了一个StringTokenizerEx,它是一个Iterable,它采用一个真正的正则表达式来分割一个字符串.

一个真正的正则表达式意味着它不是重复形成分隔符的'字符序列':
'o'只匹配'o',并将'ooo'分成三个分隔符,里面有两个空字符串:

[o], '', [o], '', [o]
Run Code Online (Sandbox Code Playgroud)

但是,当分裂"aooob"时,regexp o +将返回预期的结果

[], 'a', [ooo], 'b', []
Run Code Online (Sandbox Code Playgroud)

要使用此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();
}
Run Code Online (Sandbox Code Playgroud)

DZone Snippets提供此类代码.

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


注:( 2009年末编辑)

文章最后的思考:Java的益智游戏:鸡蛋里挑骨头做了很好的工作,地名释义的怪异行为String.split().
乔什布洛赫甚至评论说:

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

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

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

所以值得一试.从最初的粗略文档(pdf):

JDK有这个:

String[] pieces = "foo.bar".split("\\.");
Run Code Online (Sandbox Code Playgroud)

如果你想要它完全正确的话,可以使用它: - 正则表达式 - 结果作为数组 - 它处理空块的方式

迷你益智游戏:",a,b,".split(",")返回......

(a) "", "a", "", "b", ""
(b) null, "a", null, "b", null
(c) "a", null, "b"
(d) "a", "b"
(e) None of the above
Run Code Online (Sandbox Code Playgroud)

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

",a,,b,".split(",")
returns
"", "a", "", "b"
Run Code Online (Sandbox Code Playgroud)

只会跳过尾随空!(谁知道防止跳绳的解决方法?这是一个有趣的......)

无论如何,我们的Splitter更灵活:默认行为过于简单:

Splitter.on(',').split(" foo, ,bar, quux,")
--> [" foo", " ", "bar", " quux", ""]
Run Code Online (Sandbox Code Playgroud)

如果您想要额外的功能,请索取它们!

Splitter.on(',')
.trimResults()
.omitEmptyStrings()
.split(" foo, ,bar, quux,")
--> ["foo", "bar", "quux"]
Run Code Online (Sandbox Code Playgroud)

配置方法的顺序无关紧要 - 在拆分期间,在检查空箱之前进行修整.


Tom*_*zyk 5

我也会发布我的工作版本(第一个与马库斯非常相似)。

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()]);
}
Run Code Online (Sandbox Code Playgroud)

这是第二个解决方案,其速度比第一个解决方案快 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()]);
}
Run Code Online (Sandbox Code Playgroud)


Jul*_*ian 5

这是一个简单的干净实现,它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]);
}
Run Code Online (Sandbox Code Playgroud)

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

我转换为 String[] 以与 保持一致Pattern#split,我使用new String[0]而不是new String[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);
}
Run Code Online (Sandbox Code Playgroud)