如何分割一个string,但也保留分隔符?

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

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

我可以使用String.split将这个string拆分成它的部分,但似乎我无法得到与分隔符正则expression式匹配的实际string。

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

  • Text1
  • Text2
  • Text3
  • Text4

这是我想要的

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

有没有任何使用分隔符正则expression式分割string的JDK方式,但也保留分隔符?

您可以使用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("((?<=;)|(?=;))"))); 

你会得到:

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

最后一个是你想要的。

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

希望这可以帮助。

编辑 Fabian Steeg对可读性的评论是有效的。 可读性始终是RegEx的问题。 有一件事,我可以帮助简化这一点,就是创build一个名称代表正则expression式的variables,并使用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, ";")) ; ... } ... 

这有一点帮助。 😀

你想使用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::::bb::c:".split("(?=(?!^):)(?<!:)|(?!:)(?<=:)")); // "[:::][a][::::][bb][::][c][:]" dump("a,bb:::c d..e".split("(?!^)\\b")); // "[a][,][bb][:::][c][ ][d][..][e]" dump("ArrayIndexOutOfBoundsException".split("(?<=[az])(?=[AZ])")); // "[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分裂正在吃我的angular色。
  • 你可以在string拆分中使用零宽度匹配正则expression式吗?
  • 如何将CamelCase转换为Java中的可读名称?
  • 在向后看的反向引用

也可以看看

  • regular-expressions.info/Lookarounds
 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: "" */ 

我真的不喜欢另一种方式,你在哪里得到一个空的元素在前面和后面。 分隔符通常不在string的开头或末尾,因此最经常会浪费两个好的数组插槽。

编辑:固定极限情况。 可以在这里find评论的testing用例源代码: http : //snippets.dzone.com/posts/show/6453

一个非常天真的解决scheme,不涉及正则expression式将执行一个stringreplace你的分隔符(假设逗号分隔符):

 string.replace(FullString, "," , "~,~") 

你可以用适当的独特分隔符代替蒂尔达(〜)。

那么如果你对新的分隔符进行分割,那么我相信你会得到期望的结果。

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

 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。

我看了上面的答案,诚实地说,他们中没有一个我觉得满意。 你想要做的就是模仿Perl的分割function。 为什么Java不允许这样做,并有一个join()方法在某处超出了我,但我离题了。 你甚至不需要为此而上课。 它只是一个function。 运行这个示例程序:

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

https://stackoverflow.com/users/18393/cletus

无论如何,代码:

 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" } } 

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

 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] + '"'); } 

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" 

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

我喜欢StringTokenizer的想法,因为它是Enumerable。
但它也是过时的,并由String.splitreplace返回一个枯燥的String [](不包括分隔符)。

所以我实现了一个StringTokenizerEx,它是一个Iterable,它需要一个真正的正则expression式来分割一个string。

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

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

但是正则expression式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 Snippets中find

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


注意:( 2009年末编辑)

文章Final Thoughts:Java Puzzler:Splitting Hairs做了一个很好的解释String.split()奇怪行为的工作。
乔希·布洛赫甚至在回应这篇文章时评论道:

是的,这是一个痛苦。 FWIW,这是完成了一个很好的理由:与Perl的兼容性。
这个人是Mike“madbot”McCloskey,他现在在Google工作。 Mike确信Java的正则expression式实际上通过了每个30K Perl正则expression式testing(并且运行得更快)。

谷歌共库番石榴也包含一个分离器是:

  • 更简单的使用
  • 由Google维护(而不是由您)

所以它可能值得检查。 从最初的粗略文档(pdf) :

JDK有这样的:

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

如果你想要的确如此,就可以使用它: – 正则expression式 – 作为一个数组的结果 – 处理空件的方式

迷你益智游戏:“,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", ""] 

如果你想要额外的function,请为他们!

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

configuration方法的顺序并不重要 – 在分割过程中,在检查容器之前会发生修剪。

传递第三个观点为“真实”。 它也会返回分隔符。

 StringTokenizer(String str, String delimiters, true); 

我不知道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]); } 

我build议使用模式和匹配器,这几乎肯定会达到你想要的。 你的正则expression式需要比在String.split中使用的要复杂一些。

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

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

如果你能负担得起,使用Java的replace(CharSequence目标,CharSequencereplace)方法,并填写另一个分隔符来分割。 例如:我想分割string“boo:和:foo”,并在右边的string处保留“:”。

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

重要提示:这只有在你的string中没有更多的“newdelimiter”时才有效! 因此,这不是一个通用的解决scheme。 但是如果你知道一个CharSequence,你可以肯定它不会出现在string中,这是一个非常简单的解决scheme。

我也会发布我的工作版本(首先是非常类似于马库斯)。

 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()]); } 

这里是第二个解决scheme,比第一个解决scheme快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()]); } 

这是一个简单的清理实现,它与Pattern#split一致,并且使用可变长度的模式,后者不支持,并且使用起来更容易。 它类似于@cletus提供的解决scheme 。

 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一致性。 否则,我将无条件地追加,如果inputstring以模式结尾,则会产生一个空string作为结果的最后一个元素。

为了与Pattern#split保持一致,我将其转换为String [],我使用new String[0]而不是new String[result.size()] ,请参阅此处的原因。

这是我的testing:

 @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); } 

快速回答:使用非物理边界(如\ b)来分割。 我会试着去看看它是否有效(在PHP和JS中使用)。

这是可能的,也是一种工作,但可能会分裂得太多。 实际上,这取决于你想要分割的string和你需要的结果。 提供更多的细节,我们会帮助你更好。

另一种方法是进行自己的分割,捕获分隔符(假设它是可变的),然后将其添加到结果中。

我的快速testing:

 String str = "'ab','cd','eg'"; String[] stra = str.split("\\b"); for (String s : stra) System.out.print(s + "|"); System.out.println(); 

结果:

 '|ab|','|cd|','|eg|'| 

有点太… 🙂

调整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); } 

这是一个基于上述代码的常规版本,以防万一。 无论如何,这是短暂的。 有条件地包括头部和尾部(如果不是空的话)。 最后一部分是演示/testing用例。

 List splitWithTokens(str, pat) { def tokens=[] def lastMatch=0 def m = str=~pat while (m.find()) { if (m.start() > 0) tokens << str[lastMatch..<m.start()] tokens << m.group() lastMatch=m.end() } if (lastMatch < str.length()) tokens << str[lastMatch..<str.length()] tokens } [['<html><head><title>this is the title</title></head>',/<[^>]+>/], ['before<html><head><title>this is the title</title></head>after',/<[^>]+>/] ].each { println splitWithTokens(*it) } 

一个非常天真和效率低下的解决scheme仍然工作。使用两次拆分string,然后连接两个数组

 String temp[]=str.split("\\W"); String temp2[]=str.split("\\w||\\s"); int i=0; for(String string:temp) System.out.println(string); String temp3[]=new String[temp.length-1]; for(String string:temp2) { System.out.println(string); if((string.equals("")!=true)&&(string.equals("\\s")!=true)) { temp3[i]=string; i++; } // System.out.println(temp.length); // System.out.println(temp2.length); } System.out.println(temp3.length); String[] temp4=new String[temp.length+temp3.length]; int j=0; for(i=0;i<temp.length;i++) { temp4[j]=temp[i]; j=j+2; } j=1; for(i=0;i<temp3.length;i++) { temp4[j]=temp3[i]; j+=2; } for(String s:temp4) System.out.println(s); 
  String expression = "((A+B)*CD)*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)); 

如果你担心复杂的预见/后视的东西可能会引入,只是想要一个坚如磐石的工具方法,可以应付任何令牌模式和任何分离器你扔在它。 (这可能是这种情况!)

NB惊讶地发现Apache Commons人们似乎没有提供这个,例如在StringUtils

此外,我build议这应该是Pattern一个标志:我.. INCLUDE_SEPARATORS

但是,如果您正确使用PatternMatcher类,这非常简单:

  // NB could be a different spec for identifying tokens, of course! Pattern sepAndTokenPattern = Pattern.compile("(.*?)(\\w+)"); Matcher matcher = sepAndTokenPattern.matcher( stringForTokenising ); List<String> tokenAndSeparatorList = new ArrayList<String>(); // for most processing purposes you are going to want to know whether your // combined list of tokens and separators begins with a token or separator boolean startsWithToken = true; int matchEnd = -1; while (matcher.find()) { String preSep = matcher.group(1); if (!preSep.isEmpty()) { if( tokenAndSeparatorList.isEmpty() ){ startsWithToken = false; } // in implementation you wouldn't want these | characters, of course tokenAndSeparatorList.add("|" + preSep + "|"); // add sep } tokenAndSeparatorList.add("|" + matcher.group(2) + "|"); // add token matchEnd = matcher.end(); } // get trailing separator, if there is one: if( matchEnd != -1 ){ String trailingSep = stringForTokenising.substring( matchEnd ); if( ! trailingSep.isEmpty() ){ tokenAndSeparatorList.add( "|" + trailingSep + "|" ); } } System.out.println(String.format("# starts with token? %b - matchList %s", startsWithToken, tokenAndSeparatorList)); 

我不太了解Java,但是如果你找不到这样做的Split方法,我build议你自己做。

 string[] mySplit(string s,string delimiter) { string[] result = s.Split(delimiter); for(int i=0;i<result.Length-1;i++) { result[i] += delimiter; //this one would add the delimiter to each items end except the last item, //you can modify it however you want } } string[] res = mySplit(myString,myDelimiter); 

它不是太优雅,但它会做的。